原理

程序运行时通过解释操作码,选择对应的函数(handle)执行

本质上是作者自己实现了一套指令集

比如x86汇编中的0xFF是jmp,而0xE0, 0x48 mov

在虚拟机中既可以自定义指令对应的操作码

还可以自定义数组实现cpu的功能

5

原始代码->前端编译器->虚拟指令(vmcode)->虚拟cpu解释执行->计算结果

0x01从零到一实现虚拟机

先从正向了解虚拟机

1.常量定义

1
2
3
4
5
6
7
8
#define REG_COUNT 8/*通用寄存器的数量*/
#define MEM_SIZE 256/*内存大小(字节)*/
#define STACK_SIZE 64 /*栈深度*/
#define INSN_SIZE 4 /*每条指令的字节数(定长编译)*/
/*标志寄存器可能的取值,由CMP指令设置*/
#define FLAG_EQ 0/*两个操作数相等*/
#define FLAG_LT 1/*dst<src*/
#define FLAG_GT 2/*dst>src*/

2.指令集定定义(Opcode枚举)

最基础的操作码,ida常见于汇编

将在后面给出每个指令的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef enum {
OP_NOP = 0x00, /* 什么也不做,常用作填充 */
OP_MOV = 0x01, /* r[dst] = r[src1] 寄存器间赋值 */
OP_LDI = 0x02, /* r[dst] = (src1<<8)|src2 加载16位立即数 */
OP_LOAD = 0x03, /* r[dst] = mem[r[src1]] ,mem是memory(内存) 从内存读取数据 */
OP_STORE = 0x04, /* mem[r[dst]] = r[src1] 写入内存 */
OP_ADD = 0x05, /* r[dst] = r[src1] + r[src2] */
OP_SUB = 0x06, /* r[dst] = r[src1] - r[src2] */
OP_XOR = 0x07, /* r[dst] = r[src1] ^ r[src2] */
OP_AND = 0x08, /* r[dst] = r[src1] & r[src2] */
OP_OR = 0x09, /* r[dst] = r[src1] | r[src2] */
OP_SHL = 0x0A, /* r[dst] = r[src1] << src2 左移,src2是立即数 */
OP_SHR = 0x0B, /* r[dst] = r[src1] >> src2 右移 */
OP_CMP = 0x0C, /* flag = cmp(r[dst], r[src1]) 比较,设置标志位 */
OP_JMP = 0x0D, /* pc = (src1<<8)|src2 无条件跳转(绝对地址) */
OP_JEQ = 0x0E, /* if flag==FLAG_EQ: pc = (src1<<8)|src2 */
OP_JNE = 0x0F, /* if flag!=FLAG_EQ: pc = (src1<<8)|src2 */
OP_PUSH = 0x10, /* stack[sp++] = r[dst] */
OP_POP = 0x11, /* r[dst] = stack[--sp] */
OP_CALL = 0x12, /* push(pc+INSN_SIZE); pc = addr 子程序调用 */
OP_RET = 0x13, /* pc = pop() 从子程序返回 */
OP_IN = 0x14, /* r[dst] = getchar() 读一个字节输入 */
OP_OUT = 0x15, /* putchar(r[dst]) 输出一个字节 */
OP_HALT = 0xFF, /* 停机 */
} Opcode;

flag是标志寄存器,用于存储CMP的比较结果

1
2
3
4
// OP_CMP: r[dst] 与 r[src1] 比较
if (reg[dst] == reg[src1]) flag = FLAG_EQ; // 0
else if (reg[dst] < reg[src1]) flag = FLAG_LT; // 1
else flag = FLAG_GT; // 2

pc是程序计数器:指向下一条指令的地址

正常执行 pc += INSN_SIZE(即 +4,取下一条指令)
跳转 pc = target(强制改道)
CALL 先压栈返回地址,再 pc = target
RET 从栈弹出返回地址,pc = 返回地址
HALT 停止,pc 不再前进

3.状态结构体

1
2
3
4
5
6
7
8
9
typedef struct{
uint32_t regs[REG_COUNT];/*通用寄存器r0~r8*/
uint32_t pc; /*pc是程序计数器:指向下一条指令的地址*/
uint8_t flag /*flag是标志寄存器,用于存储CMP的比较结果*/
uint8_t mem[MEM_SIZE];/*内存区域:可存数据(输入、中间值、密文)*/
uint32_t stack[STACK_SIZE];/*调用栈*/
int sp;/*栈指针,指向栈的下一个空位*/
int halted;/*是否已经停机(执行了HALT或出错)*/
}VM;

看到状态结构体就想到AES的状态矩阵

4.核心的执行函数:vm_step()

先取指:从内存中读取数据

reference

CTF 逆向 VM 题型:从零到一认识与逆向虚拟机 - wes1’s blog