那CTF,那VMre,那些事(一)
字数 950 2025-08-07 08:22:18
VM虚拟机逆向分析技术详解
一、虚拟机保护技术基础概念
1.1 虚拟机保护定义
虚拟机保护(VM)是一种基于虚拟机的代码保护技术,它将基于x86汇编系统的可执行代码转换为字节码指令系统的代码,以达到不被轻易篡改和逆向的目的。
核心原理:
- 实现一个小型虚拟机
- 将程序代码转换为自定义操作码(opcode)
- 执行时通过解释操作码来执行对应函数
1.2 虚拟机基本结构
VMRUN (入口函数)
↓
Dispatcher (调度器,解释opcode并选择Handler)
↓
Handler (功能模块)
↑
Opcode (操作码)
1.3 关键组件
- 虚拟寄存器:用于传参或存放返回值
- EIP指针:指向正在解释的opcode地址
- Opcode列表:存放所有opcode及其处理函数
二、虚拟机实现技术
2.1 数据结构定义
typedef struct {
unsigned long r1, r2, r3; // 虚拟寄存器
unsigned char *eip; // 指向当前opcode
vm_opcode op_list[OPCODE_N]; // opcode列表
} vm_cpu;
typedef struct {
unsigned char opcode;
void (*handle)(void*);
} vm_opcode;
2.2 虚拟机初始化
void *vm_init() {
vm_cpu *cpu;
cpu->r1 = cpu->r2 = cpu->r3 = 0;
cpu->eip = (unsigned char *)vm_code;
// 关联opcode与处理函数
cpu->op_list[0].opcode = 0xf1;
cpu->op_list[0].handle = (void (*)(void *))mov;
// 其他opcode初始化...
vm_stack = malloc(0x512);
memset(vm_stack, 0, 0x512);
}
2.3 虚拟机入口函数
void vm_start(vm_cpu *cpu) {
cpu->eip = (unsigned char*)opcodes;
while((*cpu->eip) != 0xf4) { // 0xf4为RET操作码
vm_dispatcher(*cpu->eip);
}
}
2.4 解释执行器
void vm_dispatcher(vm_cpu *cpu) {
for(int j=0; j<OPCODE_N; j++) {
if(*cpu->eip == cpu->op_list[j].opcode) {
cpu->op_list[j].handle(cpu);
break;
}
}
}
2.5 处理函数实现示例
MOV指令实现
void mov(vm_cpu *cpu) {
unsigned char *res = cpu->eip + 1; // 寄存器标识
int *offset = (int *)(cpu->eip + 2); // 数据偏移
char *dest = vm_stack;
switch(*res) {
case 0xe1: cpu->r1 = *(dest + *offset); break;
case 0xe2: cpu->r2 = *(dest + *offset); break;
case 0xe3: cpu->r3 = *(dest + *offset); break;
case 0xe4: *(dest + *offset) = cpu->r1; break;
}
cpu->eip += 6; // MOV指令占6字节
}
XOR指令实现
void xor(vm_cpu *cpu) {
int num = cpu->r1 ^ cpu->r2;
num ^= 0x12;
cpu->r1 = num;
cpu->eip += 1; // XOR指令占1字节
}
三、CTF中的VM逆向分析
3.1 解题通用步骤
- 分析VM结构
- 分析opcode
- 编写parser
- 逆向算法
3.2 VM结构常见类型
- 基于栈
- 基于队列
- 基于信号量
3.3 Opcode常见类型
- 与VM数据结构对应的指令:push/pop
- 运算指令:add/sub/mul/xor等
四、实战案例分析
4.1 WxyVM1分析
题目特点:
- 无壳64位程序
- 使用大数组进行运算
关键函数:
__int64 sub_4005B6() {
for (i = 0; i <= 14999; i += 3) {
v2 = byte_6010C0[i + 2];
v3 = byte_6010C0[i + 1];
switch (byte_6010C0[i]) {
case 1: *(&byte_604B80 + v3) += v2; break;
case 2: *(&byte_604B80 + v3) -= v2; break;
case 3: *(&byte_604B80 + v3) ^= v2; break;
// ...
}
}
}
逆向脚本:
from idc_bc695 import *
arr = [4294967236, 52, 34, 4294967217, ...] # 目标数组
addr = 0x6010c0
for i in range(14997, -1, -3): # 逆向处理
v0 = Byte(addr + i)
v3 = Byte(addr + i + 2)
result = Byte(addr + i + 1)
if v0 == 1: arr[result] -= v3
if v0 == 2: arr[result] += v3
if v0 == 3: arr[result] ^= v3
print(''.join(map(chr, [x & 0xff for x in arr])))
4.2 [网鼎杯2020青龙组]signal分析
VM结构分析:
int vm_operad(int *a1, int a2) {
while(op_index < a2) {
switch(a1[op_index]) {
case 1: op_code[v7] = v4; break; // 存储
case 2: v4 = a1[op_index+1] + op_code[v8]; break; // 加
case 3: v4 = op_code[v8] - a1[op_index+1]; break; // 减
case 4: v4 = a1[op_index+1] ^ op_code[v8]; break; // 异或
case 5: v4 = a1[op_index+1] * op_code[v8]; break; // 乘
case 7: if(op_code[v8] != a1[op_index+1]) exit(0); break; // 验证
// ...
}
}
}
逆向脚本:
encrypts = [0x22, 0x3F, 0x34, 0x32, 0x72, 0x33, 0x18, 0xA7,
0x31, 0xF1, 0x28, 0x84, 0xC1, 0x1E, 0x7A]
data = [
[4,0x10,8,3,5,1], [4,0x20,8,5,3,1], [3,2,8,0x0B,1],
# ... 其他操作序列
]
def encrypted(c, li):
for x in range(len(li)):
if li[x] == 2: c += li[x+1]; x+=2
elif li[x] == 3: c -= li[x+1]; x+=2
elif li[x] == 4: c ^= li[x+1]; x+=2
elif li[x] == 5: c *= li[x+1]; x+=2
elif li[x] == 11: c -= 1; x+=1
elif li[x] == 12: c += 1; x+=1
return c
flag = ''
for x in range(len(encrypts)):
for i in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890':
if encrypted(ord(i), data[x]) == encrypts[x]:
flag += i
break
print('flag{%s}' % flag)
五、总结与进阶
5.1 VM逆向特点
- 核心算法通常不复杂
- 主要考察switch分支分析能力
- 通常没有反调试和代码混淆
5.2 学习建议
- 掌握基本虚拟机结构
- 熟练分析switch-case结构
- 练习编写逆向脚本
- 学习使用angr等符号执行工具
5.3 未来发展方向
- 更复杂的虚拟机结构
- 结合反调试技术
- 多层虚拟机嵌套
- 动态生成opcode
六、参考资料
- https://www.cnblogs.com/nigacat/p/13039289.html
- https://www.freebuf.com/column/174623.html
- https://xz.aliyun.com/t/3851
- https://blog.csdn.net/weixin_43876357/article/details/108570305