
VM
指令虚拟化
这篇文章解决什么问题
VM(Virtual Machine,虚拟机)类题目是逆向中常见的代码保护方式。出题人会把原本的逻辑转换成一套自定义字节码,再由解释器循环读取并执行。静态分析时,我们看到的往往不是直接的业务逻辑,而是“取 opcode → 分发 → 执行 handler”的解释器结构。
这篇文章先从一个最小 VM 示例入手,再说明如何定义指令集、实现解释器,以及逆向时应该如何还原 VM 的真实逻辑。
认识虚拟机
虚拟机本质上是用软件模拟一套执行环境。我们可以自定义一套指令,例如 0x01 表示把立即数放入虚拟寄存器,0x02 表示输出寄存器中的字符。程序运行时,解释器根据当前 eip 读取 opcode,再跳转到对应的 handler 执行。
在代码保护中,虚拟化会把原本直接执行的机器码转换成自定义字节码。这样一来,逆向者不能只看普通反编译结果,而需要先理解这套 VM 的指令集和解释器。

VM 的主程序通常是一个循环:不断读取 opcode,执行 opcode 对应的 handler,最后更新虚拟 eip。
最小示例
原始程序:
1 |
|
可以用如下虚拟机思路重写:
1 |
|
这个例子里:
0x01 imm:把立即数imm放入虚拟寄存器eax0x02:输出eax中保存的字符0xff:结束 VM 执行
所以逆向 VM 的第一步,不是急着找 flag,而是先把每个 opcode 的含义整理出来。
正向实现:
想要对抗指令虚拟化,首先要搞清楚用于保护的虚拟机是如何实现的
要想实现虚拟机,需要完成两个目标:
1.定义一套指令集
2.实现对应的解释器
结构体的定义:
真实赛题的VM通常会实现一个类似如下的结构体,用于保存虚拟机状态:
1 | typedef struct |
书写机器码:
假定希望实现的语句如下:
1 |
|
我们定义:
| opcode | 指令格式 | 指令长度 | 指令含义 |
|---|---|---|---|
| 0x10 | 0x10 操作数 | 2 | r1=操作数 |
| 0x11 | 0x11 操作数 | 2 | r2=操作数 |
| 0x20 | 0x20 操作数1 操作数2 | 3 | mem[操作数1]=操作数2 |
| 0x30 | 0x30 | 1 | mem[r1]^=r2 |
| 0x40 | 0x40 | 1 | 接收输入到&mem[0] |
| 0x50 | 0x50 操作数1 操作数2 | 3 | 返回memcmp(&mem[0],&mem[操作数1],操作数2) |
根据我们定义的指令对其进行拆分和重构,可以得到如下代码:
1 |
|
初始化虚拟机:
在实际虚拟机运行之前,需要先对VM结构体进行初始化:
1 | VM* vm_new() { |
解释器编写:
现在就可以来实现每条指令的handle以及dispatcher了:
1 | int vm_run(VM* vm) { |
启动:
于是main函数可以这样写 :
1 | int main() |
解题步骤:
遇到VM类的赛题,我们一般按照如下的步骤来解题:
1.分析VM结构:
结构体大小
有哪些字段
2.分析指令集:
指令长度是否可变
每种指令的构成
每种指令的含义
VM的退出条件
3.编写python版解释器,输出伪汇编代码
4.阅读伪代码,分析程序流程,写出去虚拟化的原始代码
5.书写解题脚本
本例的python解释器如下:
1 | code=b' \x10Y \x100 \x10u \x10\' \x10d \x10_ \x10B \x10e \x10t \x10t \x10e \x10R \x10_ \x10N \x100 \x10t \x10_ \x10b \x10e \x10_ \x10S \x100 \x10m \x10e \x100 \x10N \x10e \x10_ \x10W \x10h \x10o \x10_ \x10a \x10l \x10w \x10a \x10y \x10s \x10_ \x10s \x10h \x100 \x10w \x10s \x10_ \x100 \x10f \x10f@\x11!\x10\x000\x11!\x10\x010\x11!\x10\x020\x11!\x10\x030\x11!\x10\x040\x11!\x10\x050\x11!\x10\x060\x11!\x10\x070\x11!\x10\x080\x11!\x10\t0\x11!\x10\n0\x11!\x10\x0b0\x11!\x10\x0c0\x11!\x10\r0\x11!\x10\x0e0\x11!\x10\x0f0\x11!\x10\x100\x11!\x10\x110\x11!\x10\x120\x11!\x10\x130\x11!\x10\x140\x11!\x10\x150\x11!\x10\x160\x11!\x10\x170\x11!\x10\x180\x11!\x10\x190\x11!\x10\x1a0\x11!\x10\x1b0\x11!\x10\x1c0\x11!\x10\x1d0\x11!\x10\x1e0\x11!\x10\x1f0\x11!\x10 0\x11!\x10!0\x11!\x10"0\x11!\x10#0\x11!\x10$0\x11!\x10%0\x11!\x10&0\x11!\x10\'0\x11!\x10(0\x11!\x10)0\x11!\x10*0\x11!\x10+0\x11!\x10,0\x11!\x10-0\x11!\x10.0\x11!\x10/0\x11!\x1000\x11!\x1010\x11!\x1020\x11!\x1030\x11!\x1040\x11!\x1050\x11!\x1060\x11!\x1070\x11!\x1080\x11!\x1090\x11!\x10:0P\x10;' |



