原理

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

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

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

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

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

5

pc指针是程序计数器,指向下一条指令

原始代码->前端编译器->虚拟指令(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;

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

先取指:从内存中读取数据(类似code段,code段(代码段)就是存放程序指令的地方)

再执行:用switch+case匹配数据与对应字节码的操作,执行后移动指针向后推进一条指令(类似cs(代码段寄存器)、IP(指令指针寄存器

)的执行方式)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
void vm_step(VM *vm, const uint8_t *bytecode, size_t code_len) {

/* ---- 取指阶段(Fetch)----
* 从字节码数组中读出当前 PC 处的 4 个字节。
* 在 IDA 里,这行代码通常长这样:
* rax = vm->pc; opcode = bytecode[rax]; (感觉很类似于链表删除其中一个元素)
* 找到这行,向上追 bytecode 数组的来源,就能定位字节码。
*/
if (vm->pc + INSN_SIZE > code_len) {
fprintf(stderr, "[VM] PC 越界:0x%04X\n", vm->pc);
vm->halted = 1;
return;
}

uint8_t opcode = bytecode[vm->pc]; /* 操作码 */
uint8_t dst = bytecode[vm->pc + 1]; /* 目标操作数 */
uint8_t src1 = bytecode[vm->pc + 2]; /* 第一源操作数 */
uint8_t src2 = bytecode[vm->pc + 3]; /* 第二源操作数 */
//操作码 目标操作数 源操作数1,源操作数2
/* 解码阶段(Decode):提前算好常用组合 */
uint32_t imm16 = ((uint32_t)src1 << 8) | src2; /* 16 位立即数 */

/* 边界检查:寄存器编号合法性 */
#define CHECK_REG(r) do { \
if ((r) >= REG_COUNT) { \
fprintf(stderr, "[VM] 非法寄存器编号 %d\n", (r)); \
vm->halted = 1; return; \
} \
} while(0)

/* ---- 执行阶段(Execute)——大 switch ----
* PC 默认向后推进一条指令,跳转指令会覆盖这个值。
*/
vm->pc += INSN_SIZE;

switch (opcode) {

/* --------------------------------------------------
* NOP:空操作,PC 已经前进,什么都不做
* 常见用途:占位、对齐、迷惑逆向者
* -------------------------------------------------- */
case OP_NOP:
break;

/* --------------------------------------------------
* MOV r[dst], r[src1]
* 寄存器间赋值,最基础的指令
* -------------------------------------------------- */
case OP_MOV:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1];
break;

/* --------------------------------------------------
* LDI r[dst], imm16
* 加载一个 16 位立即数到寄存器
* CTF 题用它把密钥/密文常量嵌入字节码
* -------------------------------------------------- */
case OP_LDI:
CHECK_REG(dst);
vm->regs[dst] = imm16;
break;

/* --------------------------------------------------
* LOAD r[dst], [r[src1]]
* 从内存地址 r[src1] 读一个字节到 r[dst]
* 用于读取预先存在 mem[] 里的 flag 字符
* -------------------------------------------------- */
case OP_LOAD:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[src1] >= MEM_SIZE) {
fprintf(stderr, "[VM] 内存越界读:addr=0x%X\n", vm->regs[src1]);
vm->halted = 1; return;
}
vm->regs[dst] = vm->mem[vm->regs[src1]];
break;

/* --------------------------------------------------
* STORE [r[dst]], r[src1]
* 把 r[src1] 的值写入内存地址 r[dst]
* -------------------------------------------------- */
case OP_STORE:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[dst] >= MEM_SIZE) {
fprintf(stderr, "[VM] 内存越界写:addr=0x%X\n", vm->regs[dst]);
vm->halted = 1; return;
}
vm->mem[vm->regs[dst]] = (uint8_t)vm->regs[src1];
break;

/* --------------------------------------------------
* 算术 / 逻辑运算
* CTF 题的 flag 验证几乎都由这几条组成
* -------------------------------------------------- */
case OP_ADD:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] + vm->regs[src2];
break;

case OP_SUB:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] - vm->regs[src2];
break;

case OP_XOR:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] ^ vm->regs[src2];
break;

case OP_AND:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] & vm->regs[src2];
break;

case OP_OR:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] | vm->regs[src2];
break;

/* --------------------------------------------------
* SHL / SHR:移位,src2 直接作立即数(移位量)
* -------------------------------------------------- */
case OP_SHL:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1] << src2;
break;

case OP_SHR:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1] >> src2;
break;

/* --------------------------------------------------
* CMP r[dst], r[src1]
* 比较两个寄存器,结果存入 flag。
* 这是逆向时的关键分支——flag 决定后续跳转方向。
* 如果你在 IDA 看到 "if (reg_a != reg_b) goto fail_label",
* 那就是这条指令 + JNE 的组合。
* -------------------------------------------------- */
case OP_CMP:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[dst] == vm->regs[src1]) vm->flag = FLAG_EQ;
else if (vm->regs[dst] < vm->regs[src1]) vm->flag = FLAG_LT;
else vm->flag = FLAG_GT;
break;

/* --------------------------------------------------
* 跳转指令:JMP / JEQ / JNE
* 注意:跳转目标是字节码的绝对字节偏移,
* 所以目标地址 = imm16(不需要乘以 INSN_SIZE)
* -------------------------------------------------- */
case OP_JMP:
vm->pc = imm16;
break;

case OP_JEQ: /* 相等时跳转 */
if (vm->flag == FLAG_EQ) vm->pc = imm16;
break;

case OP_JNE: /* 不相等时跳转(最常用:任何一位不对就 fail) */
if (vm->flag != FLAG_EQ) vm->pc = imm16;
break;

/* --------------------------------------------------
* 栈操作:PUSH / POP
* 用于保存临时值或实现子程序调用约定
* -------------------------------------------------- */
case OP_PUSH:
CHECK_REG(dst);
if (vm->sp >= STACK_SIZE) {
fprintf(stderr, "[VM] 栈溢出\n");
vm->halted = 1; return;
}
vm->stack[vm->sp++] = vm->regs[dst];
break;

case OP_POP:
CHECK_REG(dst);
if (vm->sp <= 0) {
fprintf(stderr, "[VM] 栈下溢\n");
vm->halted = 1; return;
}
vm->regs[dst] = vm->stack[--vm->sp];
break;

/* --------------------------------------------------
* CALL addr / RET
* 实现子程序:CALL 把返回地址压栈,RET 弹出跳回。
* 有些 CTF VM 用 CALL 来调用内置的"检查函数",
* 这时函数体本身也是字节码。
* -------------------------------------------------- */
case OP_CALL:
if (vm->sp >= STACK_SIZE) {
fprintf(stderr, "[VM] CALL:栈溢出\n");
vm->halted = 1; return;
}
vm->stack[vm->sp++] = vm->pc; /* 压入 CALL 之后的下一条地址 */
vm->pc = imm16; /* 跳转到被调函数 */
break;

case OP_RET:
if (vm->sp <= 0) {
fprintf(stderr, "[VM] RET:栈下溢\n");
vm->halted = 1; return;
}
vm->pc = vm->stack[--vm->sp]; /* 恢复返回地址 */
break;

/* --------------------------------------------------
* I/O 指令
* IN r[dst] : 从 stdin 读一个字节(即读 flag 字符)
* OUT r[dst] : 向 stdout 输出一个字节(打印 "Correct!" 等)
* CTF 题通常会有一段循环:每次 IN 读一个 flag 字符 → 验证
* -------------------------------------------------- */
case OP_IN:
CHECK_REG(dst);
vm->regs[dst] = (uint8_t)getchar();
break;

case OP_OUT:
CHECK_REG(dst);
putchar((int)vm->regs[dst]);
break;

/* --------------------------------------------------
* HALT:停机指令
* VM 执行到这里时,主循环退出。
* 验证成功和失败分支最终都会跳到 HALT,
* 区别在于跳之前有没有输出 "Correct" 或 "Wrong"。
* -------------------------------------------------- */
case OP_HALT:
vm->halted = 1;
break;

/* --------------------------------------------------
* 未知 opcode:通常是被混淆的 NOP
* -------------------------------------------------- */
default:
fprintf(stderr, "[VM] 未知 opcode: 0x%02X at PC=0x%04X\n",
opcode, vm->pc - INSN_SIZE);
vm->halted = 1;
break;
}
}

int fprintf(FILE *stream, const char *format, …);(格式字符串输出)

5.主循环vm_run()

不断调用vm_step(),直到halted为真

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void vm_run(VM *vm, const uint8_t *bytecode, size_t code_len) {
while (!vm->halted) {
vm_step(vm, bytecode, code_len);
}
}

// 辅助函数:打印当前 VM 状态(调试用),打印各个寄存器与pc指向下一个
void vm_dump(const VM *vm) {
printf("\n--- VM STATE (PC=0x%04X) ---\n", vm->pc);
for (int i = 0; i < REG_COUNT; i++) {
printf(" r%d = 0x%08X (%u)", i, vm->regs[i], vm->regs[i]);
if ((i & 1) == 1) printf("\n");
else printf(" ");
}
printf(" flag = %s sp = %d\n",
vm->flag == FLAG_EQ ? "EQ" : vm->flag == FLAG_LT ? "LT" : "GT",
vm->sp);
printf("----------------------------\n\n");
}

0x02一个虚拟机程序(flag验证)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#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 */
#include<stdio.h>
#include<stdint.h>
#include<string.h>
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]] 从内存读 */
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==EQ: pc = (src1<<8)|src2 */
OP_JNE = 0x0F, /* if 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;
typedef struct {
uint32_t regs[REG_COUNT]; /* 通用寄存器 r0~r7 */
uint32_t pc; /* 程序计数器:下一条要执行的指令地址(字节偏移) */
uint8_t flag; /* 标志寄存器:保存上次 CMP 的结果 */
uint8_t mem[MEM_SIZE]; /* 内存区域:可存放数据(输入、中间值、密文等) */
uint32_t stack[STACK_SIZE]; /* 调用栈 */
int sp; /* 栈指针:指向栈顶的下一个空位 */
int halted; /* 是否已停机(执行了 HALT 或出错) */
} VM;
void vm_step(VM *vm, const uint8_t *bytecode, size_t code_len) {

/* ---- 取指阶段(Fetch)----
* 从字节码数组中读出当前 PC 处的 4 个字节。
* 在 IDA 里,这行代码通常长这样:
* opcode = bytecode[rax]; rax = vm->pc;
* 找到这行,向上追 bytecode 数组的来源,就能定位字节码。
*/
if (vm->pc + INSN_SIZE > code_len) {
fprintf(stderr, "[VM] PC 越界:0x%04X\n", vm->pc);
vm->halted = 1;
return;
}

uint8_t opcode = bytecode[vm->pc]; /* 操作码 */
uint8_t dst = bytecode[vm->pc + 1]; /* 目标操作数 */
uint8_t src1 = bytecode[vm->pc + 2]; /* 第一源操作数 */
uint8_t src2 = bytecode[vm->pc + 3]; /* 第二源操作数 */

/* 解码阶段(Decode):提前算好常用组合 */
uint32_t imm16 = ((uint32_t)src1 << 8) | src2; /* 16 位立即数 */

/* 边界检查:寄存器编号合法性 */
#define CHECK_REG(r) do { \
if ((r) >= REG_COUNT) { \
fprintf(stderr, "[VM] 非法寄存器编号 %d\n", (r)); \
vm->halted = 1; return; \
} \
} while(0)

/* ---- 执行阶段(Execute)——大 switch ----
* PC 默认向后推进一条指令,跳转指令会覆盖这个值。
*/
vm->pc += INSN_SIZE;

switch (opcode) {

/* --------------------------------------------------
* NOP:空操作,PC 已经前进,什么都不做
* 常见用途:占位、对齐、迷惑逆向者
* -------------------------------------------------- */
case OP_NOP:
break;

/* --------------------------------------------------
* MOV r[dst], r[src1]
* 寄存器间赋值,最基础的指令
* -------------------------------------------------- */
case OP_MOV:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1];
break;

/* --------------------------------------------------
* LDI r[dst], imm16
* 加载一个 16 位立即数到寄存器
* CTF 题用它把密钥/密文常量嵌入字节码
* -------------------------------------------------- */
case OP_LDI:
CHECK_REG(dst);
vm->regs[dst] = imm16;
break;

/* --------------------------------------------------
* LOAD r[dst], [r[src1]]
* 从内存地址 r[src1] 读一个字节到 r[dst]
* 用于读取预先存在 mem[] 里的 flag 字符
* -------------------------------------------------- */
case OP_LOAD:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[src1] >= MEM_SIZE) {
fprintf(stderr, "[VM] 内存越界读:addr=0x%X\n", vm->regs[src1]);
vm->halted = 1; return;
}
vm->regs[dst] = vm->mem[vm->regs[src1]];
break;

/* --------------------------------------------------
* STORE [r[dst]], r[src1]
* 把 r[src1] 的值写入内存地址 r[dst]
* -------------------------------------------------- */
case OP_STORE:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[dst] >= MEM_SIZE) {
fprintf(stderr, "[VM] 内存越界写:addr=0x%X\n", vm->regs[dst]);
vm->halted = 1; return;
}
vm->mem[vm->regs[dst]] = (uint8_t)vm->regs[src1];
break;

/* --------------------------------------------------
* 算术 / 逻辑运算
* CTF 题的 flag 验证几乎都由这几条组成
* -------------------------------------------------- */
case OP_ADD:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] + vm->regs[src2];
break;

case OP_SUB:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] - vm->regs[src2];
break;

case OP_XOR:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] ^ vm->regs[src2];
break;

case OP_AND:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] & vm->regs[src2];
break;

case OP_OR:
CHECK_REG(dst); CHECK_REG(src1); CHECK_REG(src2);
vm->regs[dst] = vm->regs[src1] | vm->regs[src2];
break;

/* --------------------------------------------------
* SHL / SHR:移位,src2 直接作立即数(移位量)
* -------------------------------------------------- */
case OP_SHL:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1] << src2;
break;

case OP_SHR:
CHECK_REG(dst); CHECK_REG(src1);
vm->regs[dst] = vm->regs[src1] >> src2;
break;

/* --------------------------------------------------
* CMP r[dst], r[src1]
* 比较两个寄存器,结果存入 flag。
* 这是逆向时的关键分支——flag 决定后续跳转方向。
* 如果你在 IDA 看到 "if (reg_a != reg_b) goto fail_label",
* 那就是这条指令 + JNE 的组合。
* -------------------------------------------------- */
case OP_CMP:
CHECK_REG(dst); CHECK_REG(src1);
if (vm->regs[dst] == vm->regs[src1]) vm->flag = FLAG_EQ;
else if (vm->regs[dst] < vm->regs[src1]) vm->flag = FLAG_LT;
else vm->flag = FLAG_GT;
break;

/* --------------------------------------------------
* 跳转指令:JMP / JEQ / JNE
* 注意:跳转目标是字节码的绝对字节偏移,
* 所以目标地址 = imm16(不需要乘以 INSN_SIZE)
* -------------------------------------------------- */
case OP_JMP:
vm->pc = imm16;
break;

case OP_JEQ: /* 相等时跳转 */
if (vm->flag == FLAG_EQ) vm->pc = imm16;
break;

case OP_JNE: /* 不相等时跳转(最常用:任何一位不对就 fail) */
if (vm->flag != FLAG_EQ) vm->pc = imm16;
break;

/* --------------------------------------------------
* 栈操作:PUSH / POP
* 用于保存临时值或实现子程序调用约定
* -------------------------------------------------- */
case OP_PUSH:
CHECK_REG(dst);
if (vm->sp >= STACK_SIZE) {
fprintf(stderr, "[VM] 栈溢出\n");
vm->halted = 1; return;
}
vm->stack[vm->sp++] = vm->regs[dst];
break;

case OP_POP:
CHECK_REG(dst);
if (vm->sp <= 0) {
fprintf(stderr, "[VM] 栈下溢\n");
vm->halted = 1; return;
}
vm->regs[dst] = vm->stack[--vm->sp];
break;

/* --------------------------------------------------
* CALL addr / RET
* 实现子程序:CALL 把返回地址压栈,RET 弹出跳回。
* 有些 CTF VM 用 CALL 来调用内置的"检查函数",
* 这时函数体本身也是字节码。
* -------------------------------------------------- */
case OP_CALL:
if (vm->sp >= STACK_SIZE) {
fprintf(stderr, "[VM] CALL:栈溢出\n");
vm->halted = 1; return;
}
vm->stack[vm->sp++] = vm->pc; /* 压入 CALL 之后的下一条地址 */
vm->pc = imm16; /* 跳转到被调函数 */
break;

case OP_RET:
if (vm->sp <= 0) {
fprintf(stderr, "[VM] RET:栈下溢\n");
vm->halted = 1; return;
}
vm->pc = vm->stack[--vm->sp]; /* 恢复返回地址 */
break;

/* --------------------------------------------------
* I/O 指令
* IN r[dst] : 从 stdin 读一个字节(即读 flag 字符)
* OUT r[dst] : 向 stdout 输出一个字节(打印 "Correct!" 等)
* CTF 题通常会有一段循环:每次 IN 读一个 flag 字符 → 验证
* -------------------------------------------------- */
case OP_IN:
CHECK_REG(dst);
vm->regs[dst] = (uint8_t)getchar();
break;

case OP_OUT:
CHECK_REG(dst);
putchar((int)vm->regs[dst]);
break;

/* --------------------------------------------------
* HALT:停机指令
* VM 执行到这里时,主循环退出。
* 验证成功和失败分支最终都会跳到 HALT,
* 区别在于跳之前有没有输出 "Correct" 或 "Wrong"。
* -------------------------------------------------- */
case OP_HALT:
vm->halted = 1;
break;

/* --------------------------------------------------
* 未知 opcode:通常是被混淆的 NOP
* -------------------------------------------------- */
default:
fprintf(stderr, "[VM] 未知 opcode: 0x%02X at PC=0x%04X\n",
opcode, vm->pc - INSN_SIZE);
vm->halted = 1;
break;
}
}
void vm_run(VM *vm, const uint8_t *bytecode, size_t code_len) {
while (!vm->halted) {
vm_step(vm, bytecode, code_len);
}
}

// 辅助函数:打印当前 VM 状态(调试用)
void vm_dump(const VM *vm) {
printf("\n--- VM STATE (PC=0x%04X) ---\n", vm->pc);
for (int i = 0; i < REG_COUNT; i++) {
printf(" r%d = 0x%08X (%u)", i, vm->regs[i], vm->regs[i]);
if ((i & 1) == 1) printf("\n");
else printf(" ");
}
printf(" flag = %s sp = %d\n",
vm->flag == FLAG_EQ ? "EQ" : vm->flag == FLAG_LT ? "LT" : "GT",
vm->sp);
printf("----------------------------\n\n");
}
static const uint8_t VM_PROG[] = {
/* === 字符 1 验证 === */
/* 0x00 */ OP_LDI, 0, 0x00, 0x1F, /* r0 = 0x1F ← 加载 XOR 的固定密钥 */
/* 0x04 */ OP_IN, 1, 0x00, 0x00, /* r1 = getchar() ← 读取第 1 个字符 */
/* 0x08 */ OP_XOR, 1, 1, 0, /* r1 = r1 ^ r0 ← 对输入进行异或转换 */
/* 0x0C */ OP_LDI, 2, 0x00, 0x67, /* r2 = 0x67 ← 加载密文 ('x' ^ 0x1F) */
/* 0x10 */ OP_CMP, 1, 2, 0, /* flag = cmp(r1, r2) ← 比较转换结果与密文 */
/* 0x14 */ OP_JNE, 0, 0x00, 0x48, /* if != jump to 0x48 ← 不等则跳转到 FAIL 分支 */

/* === 字符 2 验证 === */
/* 0x18 */ OP_IN, 1, 0x00, 0x00, /* r1 = getchar() ← 读取第 2 个字符 */
/* 0x1C */ OP_XOR, 1, 1, 0, /* r1 = r1 ^ r0 ← 对输入进行异或转换 */
/* 0x20 */ OP_LDI, 2, 0x00, 0x72, /* r2 = 0x72 ← 加载密文 ('m' ^ 0x1F) */
/* 0x24 */ OP_CMP, 1, 2, 0, /* flag = cmp(r1, r2) ← 比较转换结果与密文 */
/* 0x28 */ OP_JNE, 0, 0x00, 0x48, /* if != jump to 0x48 ← 不等则跳转到 FAIL 分支 */

/* === 成功分支 (SUCC) === */
/* 0x2C */ OP_LDI, 0, 0x00, 'O', /* r0 = 'O' */
/* 0x30 */ OP_OUT, 0, 0, 0, /* putchar(r0) */
/* 0x34 */ OP_LDI, 0, 0x00, 'K', /* r0 = 'K' */
/* 0x38 */ OP_OUT, 0, 0, 0, /* putchar(r0) */
/* 0x3C */ OP_LDI, 0, 0x00, '\n', /* r0 = '\n' */
/* 0x40 */ OP_OUT, 0, 0, 0, /* putchar(r0) */
/* 0x44 */ OP_JMP, 0, 0x00, 0x60, /* JMP 0x60 ← 躲开 FAIL 区域,直接跳向 HALT */

/* === 失败分支 (FAIL) === */
/* 0x48 */ OP_LDI, 0, 0x00, 'N', /* r0 = 'N' ← JNE 直接跳向此处 */
/* 0x4C */ OP_OUT, 0, 0, 0, /* putchar(r0) */
/* 0x50 */ OP_LDI, 0, 0x00, 'O', /* r0 = 'O' */
/* 0x54 */ OP_OUT, 0, 0, 0, /* putchar(r0) */
/* 0x58 */ OP_LDI, 0, 0x00, '\n', /* r0 = '\n' */
/* 0x5C */ OP_OUT, 0, 0, 0, /* putchar(r0) */

/* === 结束停机 === */
/* 0x60 */ OP_HALT, 0, 0x00, 0x00, /* 停机 ← 所有流程汇聚此处停机 */
};

int main(void) {
VM vm;
memset(&vm, 0, sizeof(VM)); /* 清零所有状态 */
vm.sp = 0;
vm.halted = 0;

printf("Mini VM — 输入 flag(2个字符):\n");

/* 运行字节码 */
vm_run(&vm, VM_PROG, sizeof(VM_PROG));

/* 打印最终寄存器状态(辅助调试) */
vm_dump(&vm);

return 0;
}

注意的是输入v\nm会直接读个\n进去导致验证失败

0x03程序ida逆向

vm的核心主循环是vm_run,其中当不停时执行核心执行函数vm_step()

进去直接看graphic_view

7

一看就是switch-case用太多了

vm_step函数最开始检测程序是否结束

之后加载操作码和操作数

之后就是指令识别

0x04

开始逆向

解的时候关键看两个点,一个是vm_step()主要执行函数,另一个就是程序的二进制代码

先dump操作码

1
0x2,0x0,0x0,0x1F,0x14,0x1,0x0,0x0,0x7,0x1,0x1,0x0,0x2,0x2,0x0,0x67,0xC,0x1,0x2,0x0,0xF,0x0,0x0,0x48,0x14,0x1,0x0,0x0,0x7,0x1,0x1,0x0,0x2,0x2,0x0,0x72,0xC,0x1,0x2,0x0,0xF,0x0,0x0,0x48,0x2,0x0,0x0,0x4F,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0x4B,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0xA,0x15,0x0,0x0,0x0,0xD,0x0,0x0,0x60,0x2,0x0,0x0,0x4E,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0x4F,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0xA,0x15,0x0,0x0,0x0,0xFF,0x0,0x0,0x0

原有的格式是[opcode][dest][res1][resc2]

graphic view叠一起怎么办,选择layout还原布局

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
bytecode = bytes([0x2,0x0,0x0,0x1F,0x14,0x1,0x0,0x0,0x7,0x1,0x1,0x0,0x2,0x2,0x0,0x67,0xC,0x1,0x2,0x0,0xF,0x0,0x0,0x48,0x14,0x1,0x0,0x0,0x7,0x1,0x1,0x0,0x2,0x2,0x0,0x72,0xC,0x1,0x2,0x0,0xF,0x0,0x0,0x48,0x2,0x0,0x0,0x4F,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0x4B,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0xA,0x15,0x0,0x0,0x0,0xD,0x0,0x0,0x60,0x2,0x0,0x0,0x4E,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0x4F,0x15,0x0,0x0,0x0,0x2,0x0,0x0,0xA,0x15,0x0,0x0,0x0,0xFF,0x0,0x0,0x0])
OPCODE_table = {
0x00:("NOP",""),
0x01:("MOV","reg_reg"),
0x02:("LDI","reg_imm16"),
0x03: ("LOAD", "reg_mem"),
0x04: ("STORE", "mem_reg"),
0x05: ("ADD", "reg_lo_hi"),
0x06: ("SUB", "reg_hi_lo"),
0x07: ("XOR", "reg_lo_hi"),
0x08: ("AND", "reg_lo_hi"),
0x09: ("OR", "reg_lo_hi"),
0x0A: ("SHL", "reg_hi_imm"),
0x0B: ("SHR", "reg_hi_imm"),
0x0C: ("CMP", "reg_reg"),
0x0D: ("JMP", "imm16"),
0x0E: ("JE", "imm16"), #
0x0F: ("JNE", "imm16"),
0x10: ("PUSH", "reg"),
0x11: ("POP", "reg"),
0x12: ("CALL", "imm16"),
0x13: ("RET", ""),
0x14: ("IN", "reg"),
0x15: ("OUT", "reg"),
0xFF: ("HALT", ""),
}
def char_hint(val: int) -> str:#类型注释和返回的数据类型
if val == 0x0A :
return ";'\\n'"
if 0x20<= val<= 0x7E:
return f";'{chr(val)}'" #格式化字符串穿
return ""
def disasm(bytecodes: bytes) -> None:
pc = 0
total =len(bytecodes)

print(f'{"Offset":<8}{'Raw Bytes':<14}{'Instruction'}')
print('-'*55)
while pc + 4 <= total:
op = bytecode[pc]
dst = bytecode[pc + 1]
hi = bytecode[pc + 2]
lo = bytecode[pc + 3]
imm = (hi << 8) | lo # src2 完整 16 位立即数

raw = f"{op:02X} {dst:02X} {hi:02X} {lo:02X}"
offset = pc
pc += 4

# ── 查表解码 ──────────────────────────────────────────────────────
if op not in OPCODE_table:
print(f"[{offset:04X}] {raw} UNKNOWN opcode=0x{op:02X}")
continue

mnem, fmt = OPCODE_table[op]

if fmt == "":
asm = mnem
elif fmt == "reg":
asm = f"{mnem:<5} r{dst}"
elif fmt == "reg_reg":
asm = f"{mnem:<5} r{dst}, r{hi}"
elif fmt == "reg_imm16":
hint = char_hint(lo) if hi == 0 else ""#在高位为0时才打印hint否则打印""
asm = f"{mnem:<5} r{dst}, 0x{imm:04X}{hint}"
elif fmt == "reg_mem":
asm = f"{mnem:<5} r{dst}, [r{hi}]"
elif fmt == "mem_reg":
asm = f"{mnem:<5} [r{dst}], r{hi}"
elif fmt == "reg_lo_hi":
asm = f"{mnem:<5} r{dst}, r{lo}, r{hi}"
elif fmt == "reg_hi_lo":
asm = f"{mnem:<5} r{dst}, r{hi}, r{lo}"
elif fmt == "reg_hi_imm":
asm = f"{mnem:<5} r{dst}, r{hi}, {lo}"
elif fmt == "imm16":
asm = f"{mnem:<5} 0x{imm:04X}"
else:
asm = f"{mnem}"

print(f"[{offset:04X}] {raw:<13} {asm}")

if pc < total:
print(f"\n[警告] 末尾有 {total - pc} 个未对齐字节")

if __name__ == "__main__":#文件直接运行时才执行
disasm(bytecode)



解密的到汇编

0x05ida断点动态trace

首先去找switch-case函数去vm_step()函数中找到每个case函数的处理函数的地址

在每个case函数的入口点设置条件断点

在ida设置

  • location:填写函数地址

  • Condition:写 False (让断点不暂停执行)

  • edit scription编写脚本

第三步:断点脚本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 每次执行到 LOAD_CONST 这个 case 时自动运行
pc_addr = idc.get_name_ea_simple("pc") # 找到 VM 的 pc 变量地址
pc = ida_bytes.get_word(pc_addr) # 读当前 VM pc 值

sp_addr = idc.get_name_ea_simple("SP_")
sp = ida_bytes.get_word(sp_addr) # 读 VM 栈指针

# 读 x64 fastcall 传参寄存器(就是这个 case 函数的入参)
rcx = idc.get_reg_value("rcx")
rdx = idc.get_reg_value("rdx")
r8 = idc.get_reg_value("r8")
r9 = idc.get_reg_value("r9")

op_name = "LOAD_CONST"
print(pc, op_name, "sp={}".format(sp), rcx, rdx, r8, r9)

每一个case都有类似的脚本,再对应case位置设置脚本

第四步:输入真实的flag输入,让程序跑完

ida的output窗口会自动输出trace

1
2
3
4
5
6
7
0  LOAD_CONST   sp=0  1  0  0  475378216224
1 STORE_NAME sp=1 0 32 2324953371952 9295712209692852480
0 LOAD_CONST sp=0 1 1 4294967295 1
1 STORE_NAME sp=1 1 72 2324953371952 9295712209692852480
...
8 COMPARE sp=3 3 136 2324953372256 2324953372161
9 JNT sp=1 4 152 4294967295 1

如何从trace分析flag

重点关注

1
2
1.COMPARE->rcx,rbx就是两侧的操作数(比较了什么)
2.JNT/jMP->是否有跳转(跳转了什么)

例如

1
2
3
4
5
6
行8:  COMPARE sp=3  136  [val_A]  [val_B]
↑ 对比了 val_A 和 val_B

行9: JNT sp=1 4 152 ...
↑ JNT = Jump if Not True,即不等则跳
↑ 如果跳了 → 输入错误

06Funpyvm

python3.13pyc反编译网站

https://pychaos.io/
这道题一个难点就是怎么将高版本的pyc文件反编译为python文件

这是polarisctf2026-FunPyVM是个vm

opcode.bin正是操作码,四字节为一组,opcode+dst+rsc1+rsc2

主要是找vm_step

1
2
3
4
import dis, marshal
f = open("main.pyc","rb").read()
code = marshal.loads(f[16:])
dis.dis(code)

读取汇编

1
2
3
4
5
6
LOAD_CONST               0 (0)
LOAD_CONST 2 (('CustomVM',))
IMPORT_NAME 3 (kernelVM)
IMPORT_FROM 4 (CustomVM)
STORE_NAME 4 (CustomVM)
POP_TOP

可以看见压栈导入kernelvm可以去找这个文件

在main.exe_extracted\PYZ.pyz_extracted找到kennerlvm.pyc

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import sys
from random import randint
class CustomVM:
def __init__(self):
self.R0 = 0
self.R1 = 0
self.PC = 0
self.heap = {}
self.heapNum = 61444
self.first_create_done = False

def run(self,bytecode):
self.PC = 0
length = len(bytecode)
while self.PC < length:
opcode = bytecode[self.PC]
instruction_length = 1
if opcode in (16,17,18,32,48,49,50,51,52,53,54,55,64,65,66,67,68,69,70,80,82):
instruction_length = 2

opnum = bytecode[self.PC+1] if instruction_length == 2 else 0
match opcode:
case 16:
if self.first_create_done:
self.R0 = 0
self.first_create_done = True
self.heap[0] = [0]*opnum
else:
self.R0 = self.heapNum
self.heapNum += randint(1,10)
self.heap[self.R0] = [0]*opnum

self.PC += 2
case 17:
if self.R0 in self.heap and opnum < len(self.heap[self.R0]):
self.R1 = self.heap[self.R0][opnum]
else:
if 0 in self.heap and opnum < len(self.heap[0]):
self.R1 = self.heap[0][opnum]
else:
self.R1 = 0

self.PC += 2

if __CHAOS_PY_NULL_PTR_VALUE_ERR__ == 18:
if self.R0 in self.heap:
self.heap[self.R0][opnum] = self.R1
if 0 in self.heap and opnum < len(self.heap[0]):
self.heap[0][opnum if opnum < len(self.heap[self.R0]) else __CHAOS_PY_NULL_PTR_VALUE_ERR__] = self.R1
else:
raise IndexError(f'''VM Memory Access Violation: store {opnum} out of bounds for both chunk 0x{self.R0:X} and fallback chunk 0''')

raise ValueError(f'''VM Segmentation Fault: store {opnum} to unallocated memory pointer 0x{self.R0:X}''')
self.PC += 2

continue

if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: python kernelVM.py <program.bin>')
sys.exit(1)

filename = sys.argv[1]
with open(filename,'rb') as f:
bytecode = f.read()

vm = CustomVM()
print('--- VM Start ---')
vm.run(bytecode)
print('\n--- VM End ---')

这就是vm的核心

sys是什么,sys是个内置的模板,提供了与python解释器与内部环境的交互器,sys.argv是个列表,用于存储命令行参数,第一个元素是脚本名,其余元素是参数

f self.R0 in self.heap and opnum < len(self.heap[self.R0]):判断出来R0是堆指针

heap是堆,pc是程序计数器

opnum = bytecode[self.PC+1] if instruction_length == 2 else 0这是三元表达式

0x10:allocate immdite模拟动态分配

0x17明显是从内存中load数据

0x18是store内存中

STORE_ATTR 是**对象.heapnum = 值**这么个操作,出栈存在heapnum上

特此声明该反编译代码不全

1
2
3
4
import dis, marshal
f = open("kernelvm.pyc","rb").read()
code = marshal.loads(f[16:])
dis.dis(code)

还是得看汇编

1
2
3
4
5
78   L18:     COPY                     1
LOAD_CONST 17 (32)
COMPARE_OP 88 (bool(==))
POP_JUMP_IF_FALSE 34 (to L19)
POP_TOP

这就是 COMPARE_OP 弹出栈顶两个数,比较是否相等1,将True与False压栈

POP_TOP丢弃栈顶的值

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
26
27
28
29
30
31
32
33
34
35
36
37
38
 
R0:当前堆块指针

R1:通用寄存器

PC:程序计数器

heap:虚拟机堆

本题主要用到这些指令:

0x10 imm:ALLOC imm

0x11 imm:LOAD imm

0x12 imm:STORE imm

0x13:FREE

0x20 imm:MOVI imm

0x30 imm:ADDI imm

0x31 imm:XORI imm

0x35 imm:对内存单元做异或,即 heap[R0][imm] ^= R1

0x40 imm:CMPEQ imm

0x42 imm:JZ imm

0x43 imm:JNZ imm

0x51:交换 R0 和 R1

0x52 0x01:读取输入

0x52 0x02:输出字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
78   L18:     COPY                     1
LOAD_CONST 17 (32)
COMPARE_OP 88 (bool(==))
POP_JUMP_IF_FALSE 34 (to L19)
POP_TOP

79 LOAD_FAST 5 (opnum)
LOAD_CONST 18 (255)
BINARY_OP 1 (&)
LOAD_FAST 0 (self)
STORE_ATTR 7 (R1)

80 LOAD_FAST 0 (self)
COPY 1
LOAD_ATTR 0 (PC)
LOAD_CONST 4 (2)
BINARY_OP 13 (+=)
SWAP 2
STORE_ATTR 0 (PC)
EXTENDED_ARG 8
JUMP_FORWARD 2066 (to L72)

简单列一下32对应的操作为与255位与,取第四位存入r1寄存器中

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

import argparse
from pathlib import Path


LEN2_OPS = {
0x10, 0x11, 0x12, 0x20,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x50, 0x52,
}

OPINFO = {
0x10: ("ALLOC", True),
0x11: ("LOAD", True),
0x12: ("STORE", True),
0x13: ("FREE", False),
0x20: ("MOVI", True),
0x30: ("ADDI", True),
0x31: ("XORI", True),
0x32: ("OP32", True),
0x33: ("OP33", True),
0x34: ("OP34", True),
0x35: ("XOR_MEM", True),
0x36: ("OP36", True),
0x37: ("OP37", True),
0x40: ("CMPEQ", True),
0x41: ("OP41", True),
0x42: ("JZ", True),
0x43: ("JNZ", True),
0x44: ("OP44", True),
0x45: ("OP45", True),
0x46: ("OP46", True),
0x50: ("OP50", True),
0x51: ("SWAP", False),
0x52: ("SYSCALL", True),
}

SYSCALL_NAMES = {
0x01: "READ_INPUT",
0x02: "PRINT_STRING",
}


def decode_instruction(bytecode: bytes, pc: int):
opcode = bytecode[pc]
instr_len = 2 if opcode in LEN2_OPS else 1
arg = bytecode[pc + 1] if instr_len == 2 and pc + 1 < len(bytecode) else None
name, has_arg = OPINFO.get(opcode, (f"DB_0x{opcode:02X}", instr_len == 2))
return {
"pc": pc,
"opcode": opcode,
"len": instr_len,
"arg": arg,
"name": name,
"has_arg": has_arg,
}


def collect_instructions(bytecode: bytes):
pc = 0
insns = []
while pc < len(bytecode):
insn = decode_instruction(bytecode, pc)
insns.append(insn)
pc += insn["len"]
return insns


def annotate_labels(insns):
labels = {}
for insn in insns:
if insn["name"] in {"JZ", "JNZ"} and insn["arg"] is not None:
target = insn["pc"] + insn["arg"]
insn["target"] = target
labels.setdefault(target, f"loc_{target:04X}")
return labels


def format_hex(insn):
if insn["len"] == 2:
return f"{insn['opcode']:02X} {insn['arg']:02X}"
return f"{insn['opcode']:02X}"


def format_detail(insn, labels):
name = insn["name"]
arg = insn["arg"]

if name == "SYSCALL" and arg is not None:
return f"SYSCALL 0x{arg:02X} ; {SYSCALL_NAMES.get(arg, 'UNKNOWN_SYSCALL')}"

if name in {"JZ", "JNZ"} and arg is not None:
target = insn.get("target", insn["pc"] + arg)
label = labels.get(target, f"loc_{target:04X}")
return f"{name} 0x{arg:02X} ; -> {label} (0x{target:04X})"

if insn["has_arg"] and arg is not None:
return f"{name} 0x{arg:02X}"

return name


def disassemble(bytecode: bytes):
insns = collect_instructions(bytecode)
labels = annotate_labels(insns)

print(f"; size = {len(bytecode)} bytes")
print(f"; instructions = {len(insns)}")
print()

for insn in insns:
if insn["pc"] in labels:
print(f"{labels[insn['pc']]}:")
print(
f" 0x{insn['pc']:04X}: "
f"{format_hex(insn):<5} "
f"{format_detail(insn, labels)}"
)#隐式拼接


def main():
parser = argparse.ArgumentParser(description="Disassemble FunPyVM bytecode")
parser.add_argument("path", help="Path to opcode.bin or hidden VM payload")#建立名为path的位置参数
args = parser.parse_args()

data = Path(args.path).read_bytes()
disassemble(data)


if __name__ == "__main__":
main()

得到汇编

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
 python agent.py opcode.bin
; size = 855 bytes
; instructions = 428

0x0000: 10 64 ALLOC 0x64
0x0002: 10 32 ALLOC 0x32
0x0004: 52 01 SYSCALL 0x01 ; READ_INPUT
0x0006: 11 00 LOAD 0x00
0x0008: 40 00 CMPEQ 0x00
0x000A: 43 06 JNZ 0x06 ; -> loc_0010 (0x0010)
0x000C: 11 00 LOAD 0x00
0x000E: 31 55 XORI 0x55
loc_0010:
0x0010: 12 00 STORE 0x00
0x0012: 11 01 LOAD 0x01
0x0014: 40 00 CMPEQ 0x00
0x0016: 43 06 JNZ 0x06 ; -> loc_001C (0x001C)
0x0018: 11 01 LOAD 0x01
0x001A: 31 55 XORI 0x55
loc_001C:
0x001C: 12 01 STORE 0x01
0x001E: 11 02 LOAD 0x02
0x0020: 40 00 CMPEQ 0x00
0x0022: 43 06 JNZ 0x06 ; -> loc_0028 (0x0028)
0x0024: 11 02 LOAD 0x02
0x0026: 31 55 XORI 0x55
loc_0028:
0x0028: 12 02 STORE 0x02
0x002A: 11 03 LOAD 0x03
0x002C: 40 00 CMPEQ 0x00
0x002E: 43 06 JNZ 0x06 ; -> loc_0034 (0x0034)
0x0030: 11 03 LOAD 0x03
0x0032: 31 55 XORI 0x55
loc_0034:
0x0034: 12 03 STORE 0x03
0x0036: 11 04 LOAD 0x04
0x0038: 40 00 CMPEQ 0x00
0x003A: 43 06 JNZ 0x06 ; -> loc_0040 (0x0040)
0x003C: 11 04 LOAD 0x04
0x003E: 31 55 XORI 0x55
loc_0040:
0x0040: 12 04 STORE 0x04
0x0042: 11 05 LOAD 0x05
0x0044: 40 00 CMPEQ 0x00
0x0046: 43 06 JNZ 0x06 ; -> loc_004C (0x004C)
0x0048: 11 05 LOAD 0x05
0x004A: 31 55 XORI 0x55
loc_004C:
0x004C: 12 05 STORE 0x05
0x004E: 11 06 LOAD 0x06
0x0050: 40 00 CMPEQ 0x00
0x0052: 43 06 JNZ 0x06 ; -> loc_0058 (0x0058)
0x0054: 11 06 LOAD 0x06
0x0056: 31 55 XORI 0x55
loc_0058:
0x0058: 12 06 STORE 0x06
0x005A: 11 07 LOAD 0x07
0x005C: 40 00 CMPEQ 0x00
0x005E: 43 06 JNZ 0x06 ; -> loc_0064 (0x0064)
0x0060: 11 07 LOAD 0x07
0x0062: 31 55 XORI 0x55
loc_0064:
0x0064: 12 07 STORE 0x07
0x0066: 11 08 LOAD 0x08
0x0068: 40 00 CMPEQ 0x00
0x006A: 43 06 JNZ 0x06 ; -> loc_0070 (0x0070)
0x006C: 11 08 LOAD 0x08
0x006E: 31 55 XORI 0x55
loc_0070:
0x0070: 12 08 STORE 0x08
0x0072: 11 09 LOAD 0x09
0x0074: 40 00 CMPEQ 0x00
0x0076: 43 06 JNZ 0x06 ; -> loc_007C (0x007C)
0x0078: 11 09 LOAD 0x09
0x007A: 31 55 XORI 0x55
loc_007C:
0x007C: 12 09 STORE 0x09
0x007E: 11 0A LOAD 0x0A
0x0080: 40 00 CMPEQ 0x00
0x0082: 43 06 JNZ 0x06 ; -> loc_0088 (0x0088)
0x0084: 11 0A LOAD 0x0A
0x0086: 31 55 XORI 0x55
loc_0088:
0x0088: 12 0A STORE 0x0A
0x008A: 11 0B LOAD 0x0B
0x008C: 40 00 CMPEQ 0x00
0x008E: 43 06 JNZ 0x06 ; -> loc_0094 (0x0094)
0x0090: 11 0B LOAD 0x0B
0x0092: 31 55 XORI 0x55
loc_0094:
0x0094: 12 0B STORE 0x0B
0x0096: 11 0C LOAD 0x0C
0x0098: 40 00 CMPEQ 0x00
0x009A: 43 06 JNZ 0x06 ; -> loc_00A0 (0x00A0)
0x009C: 11 0C LOAD 0x0C
0x009E: 31 55 XORI 0x55
loc_00A0:
0x00A0: 12 0C STORE 0x0C
0x00A2: 11 0D LOAD 0x0D
0x00A4: 40 00 CMPEQ 0x00
0x00A6: 43 06 JNZ 0x06 ; -> loc_00AC (0x00AC)
0x00A8: 11 0D LOAD 0x0D
0x00AA: 31 55 XORI 0x55
loc_00AC:
0x00AC: 12 0D STORE 0x0D
0x00AE: 11 0E LOAD 0x0E
0x00B0: 40 00 CMPEQ 0x00
0x00B2: 43 06 JNZ 0x06 ; -> loc_00B8 (0x00B8)
0x00B4: 11 0E LOAD 0x0E
0x00B6: 31 55 XORI 0x55
loc_00B8:
0x00B8: 12 0E STORE 0x0E
0x00BA: 11 0F LOAD 0x0F
0x00BC: 40 00 CMPEQ 0x00
0x00BE: 43 06 JNZ 0x06 ; -> loc_00C4 (0x00C4)
0x00C0: 11 0F LOAD 0x0F
0x00C2: 31 55 XORI 0x55
loc_00C4:
0x00C4: 12 0F STORE 0x0F
0x00C6: 11 10 LOAD 0x10
0x00C8: 40 00 CMPEQ 0x00
0x00CA: 43 06 JNZ 0x06 ; -> loc_00D0 (0x00D0)
0x00CC: 11 10 LOAD 0x10
0x00CE: 31 55 XORI 0x55
loc_00D0:
0x00D0: 12 10 STORE 0x10
0x00D2: 11 11 LOAD 0x11
0x00D4: 40 00 CMPEQ 0x00
0x00D6: 43 06 JNZ 0x06 ; -> loc_00DC (0x00DC)
0x00D8: 11 11 LOAD 0x11
0x00DA: 31 55 XORI 0x55
loc_00DC:
0x00DC: 12 11 STORE 0x11
0x00DE: 11 12 LOAD 0x12
0x00E0: 40 00 CMPEQ 0x00
0x00E2: 43 06 JNZ 0x06 ; -> loc_00E8 (0x00E8)
0x00E4: 11 12 LOAD 0x12
0x00E6: 31 55 XORI 0x55
loc_00E8:
0x00E8: 12 12 STORE 0x12
0x00EA: 11 13 LOAD 0x13
0x00EC: 40 00 CMPEQ 0x00
0x00EE: 43 06 JNZ 0x06 ; -> loc_00F4 (0x00F4)
0x00F0: 11 13 LOAD 0x13
0x00F2: 31 55 XORI 0x55
loc_00F4:
0x00F4: 12 13 STORE 0x13
0x00F6: 11 14 LOAD 0x14
0x00F8: 40 00 CMPEQ 0x00
0x00FA: 43 06 JNZ 0x06 ; -> loc_0100 (0x0100)
0x00FC: 11 14 LOAD 0x14
0x00FE: 31 55 XORI 0x55
loc_0100:
0x0100: 12 14 STORE 0x14
0x0102: 11 15 LOAD 0x15
0x0104: 40 00 CMPEQ 0x00
0x0106: 43 06 JNZ 0x06 ; -> loc_010C (0x010C)
0x0108: 11 15 LOAD 0x15
0x010A: 31 55 XORI 0x55
loc_010C:
0x010C: 12 15 STORE 0x15
0x010E: 11 16 LOAD 0x16
0x0110: 40 00 CMPEQ 0x00
0x0112: 43 06 JNZ 0x06 ; -> loc_0118 (0x0118)
0x0114: 11 16 LOAD 0x16
0x0116: 31 55 XORI 0x55
loc_0118:
0x0118: 12 16 STORE 0x16
0x011A: 11 17 LOAD 0x17
0x011C: 40 00 CMPEQ 0x00
0x011E: 43 06 JNZ 0x06 ; -> loc_0124 (0x0124)
0x0120: 11 17 LOAD 0x17
0x0122: 31 55 XORI 0x55
loc_0124:
0x0124: 12 17 STORE 0x17
0x0126: 11 18 LOAD 0x18
0x0128: 40 00 CMPEQ 0x00
0x012A: 43 06 JNZ 0x06 ; -> loc_0130 (0x0130)
0x012C: 11 18 LOAD 0x18
0x012E: 31 55 XORI 0x55
loc_0130:
0x0130: 12 18 STORE 0x18
0x0132: 11 19 LOAD 0x19
0x0134: 40 00 CMPEQ 0x00
0x0136: 43 06 JNZ 0x06 ; -> loc_013C (0x013C)
0x0138: 11 19 LOAD 0x19
0x013A: 31 55 XORI 0x55
loc_013C:
0x013C: 12 19 STORE 0x19
0x013E: 11 1A LOAD 0x1A
0x0140: 40 00 CMPEQ 0x00
0x0142: 43 06 JNZ 0x06 ; -> loc_0148 (0x0148)
0x0144: 11 1A LOAD 0x1A
0x0146: 31 55 XORI 0x55
loc_0148:
0x0148: 12 1A STORE 0x1A
0x014A: 11 00 LOAD 0x00
0x014C: 40 00 CMPEQ 0x00
0x014E: 43 06 JNZ 0x06 ; -> loc_0154 (0x0154)
0x0150: 11 00 LOAD 0x00
0x0152: 30 07 ADDI 0x07
loc_0154:
0x0154: 12 00 STORE 0x00
0x0156: 11 01 LOAD 0x01
0x0158: 40 00 CMPEQ 0x00
0x015A: 43 06 JNZ 0x06 ; -> loc_0160 (0x0160)
0x015C: 11 01 LOAD 0x01
0x015E: 30 0A ADDI 0x0A
loc_0160:
0x0160: 12 01 STORE 0x01
0x0162: 11 02 LOAD 0x02
0x0164: 40 00 CMPEQ 0x00
0x0166: 43 06 JNZ 0x06 ; -> loc_016C (0x016C)
0x0168: 11 02 LOAD 0x02
0x016A: 30 0D ADDI 0x0D
loc_016C:
0x016C: 12 02 STORE 0x02
0x016E: 11 03 LOAD 0x03
0x0170: 40 00 CMPEQ 0x00
0x0172: 43 06 JNZ 0x06 ; -> loc_0178 (0x0178)
0x0174: 11 03 LOAD 0x03
0x0176: 30 10 ADDI 0x10
loc_0178:
0x0178: 12 03 STORE 0x03
0x017A: 11 04 LOAD 0x04
0x017C: 40 00 CMPEQ 0x00
0x017E: 43 06 JNZ 0x06 ; -> loc_0184 (0x0184)
0x0180: 11 04 LOAD 0x04
0x0182: 30 13 ADDI 0x13
loc_0184:
0x0184: 12 04 STORE 0x04
0x0186: 11 05 LOAD 0x05
0x0188: 40 00 CMPEQ 0x00
0x018A: 43 06 JNZ 0x06 ; -> loc_0190 (0x0190)
0x018C: 11 05 LOAD 0x05
0x018E: 30 16 ADDI 0x16
loc_0190:
0x0190: 12 05 STORE 0x05
0x0192: 11 06 LOAD 0x06
0x0194: 40 00 CMPEQ 0x00
0x0196: 43 06 JNZ 0x06 ; -> loc_019C (0x019C)
0x0198: 11 06 LOAD 0x06
0x019A: 30 19 ADDI 0x19
loc_019C:
0x019C: 12 06 STORE 0x06
0x019E: 11 07 LOAD 0x07
0x01A0: 40 00 CMPEQ 0x00
0x01A2: 43 06 JNZ 0x06 ; -> loc_01A8 (0x01A8)
0x01A4: 11 07 LOAD 0x07
0x01A6: 30 1C ADDI 0x1C
loc_01A8:
0x01A8: 12 07 STORE 0x07
0x01AA: 11 08 LOAD 0x08
0x01AC: 40 00 CMPEQ 0x00
0x01AE: 43 06 JNZ 0x06 ; -> loc_01B4 (0x01B4)
0x01B0: 11 08 LOAD 0x08
0x01B2: 30 1F ADDI 0x1F
loc_01B4:
0x01B4: 12 08 STORE 0x08
0x01B6: 11 09 LOAD 0x09
0x01B8: 40 00 CMPEQ 0x00
0x01BA: 43 06 JNZ 0x06 ; -> loc_01C0 (0x01C0)
0x01BC: 11 09 LOAD 0x09
0x01BE: 30 22 ADDI 0x22
loc_01C0:
0x01C0: 12 09 STORE 0x09
0x01C2: 11 0A LOAD 0x0A
0x01C4: 40 00 CMPEQ 0x00
0x01C6: 43 06 JNZ 0x06 ; -> loc_01CC (0x01CC)
0x01C8: 11 0A LOAD 0x0A
0x01CA: 30 25 ADDI 0x25
loc_01CC:
0x01CC: 12 0A STORE 0x0A
0x01CE: 11 0B LOAD 0x0B
0x01D0: 40 00 CMPEQ 0x00
0x01D2: 43 06 JNZ 0x06 ; -> loc_01D8 (0x01D8)
0x01D4: 11 0B LOAD 0x0B
0x01D6: 30 28 ADDI 0x28
loc_01D8:
0x01D8: 12 0B STORE 0x0B
0x01DA: 11 0C LOAD 0x0C
0x01DC: 40 00 CMPEQ 0x00
0x01DE: 43 06 JNZ 0x06 ; -> loc_01E4 (0x01E4)
0x01E0: 11 0C LOAD 0x0C
0x01E2: 30 2B ADDI 0x2B
loc_01E4:
0x01E4: 12 0C STORE 0x0C
0x01E6: 11 0D LOAD 0x0D
0x01E8: 40 00 CMPEQ 0x00
0x01EA: 43 06 JNZ 0x06 ; -> loc_01F0 (0x01F0)
0x01EC: 11 0D LOAD 0x0D
0x01EE: 30 2E ADDI 0x2E
loc_01F0:
0x01F0: 12 0D STORE 0x0D
0x01F2: 11 0E LOAD 0x0E
0x01F4: 40 00 CMPEQ 0x00
0x01F6: 43 06 JNZ 0x06 ; -> loc_01FC (0x01FC)
0x01F8: 11 0E LOAD 0x0E
0x01FA: 30 31 ADDI 0x31
loc_01FC:
0x01FC: 12 0E STORE 0x0E
0x01FE: 11 0F LOAD 0x0F
0x0200: 40 00 CMPEQ 0x00
0x0202: 43 06 JNZ 0x06 ; -> loc_0208 (0x0208)
0x0204: 11 0F LOAD 0x0F
0x0206: 30 34 ADDI 0x34
loc_0208:
0x0208: 12 0F STORE 0x0F
0x020A: 11 10 LOAD 0x10
0x020C: 40 00 CMPEQ 0x00
0x020E: 43 06 JNZ 0x06 ; -> loc_0214 (0x0214)
0x0210: 11 10 LOAD 0x10
0x0212: 30 37 ADDI 0x37
loc_0214:
0x0214: 12 10 STORE 0x10
0x0216: 11 11 LOAD 0x11
0x0218: 40 00 CMPEQ 0x00
0x021A: 43 06 JNZ 0x06 ; -> loc_0220 (0x0220)
0x021C: 11 11 LOAD 0x11
0x021E: 30 3A ADDI 0x3A
loc_0220:
0x0220: 12 11 STORE 0x11
0x0222: 11 12 LOAD 0x12
0x0224: 40 00 CMPEQ 0x00
0x0226: 43 06 JNZ 0x06 ; -> loc_022C (0x022C)
0x0228: 11 12 LOAD 0x12
0x022A: 30 3D ADDI 0x3D
loc_022C:
0x022C: 12 12 STORE 0x12
0x022E: 11 13 LOAD 0x13
0x0230: 40 00 CMPEQ 0x00
0x0232: 43 06 JNZ 0x06 ; -> loc_0238 (0x0238)
0x0234: 11 13 LOAD 0x13
0x0236: 30 40 ADDI 0x40
loc_0238:
0x0238: 12 13 STORE 0x13
0x023A: 11 14 LOAD 0x14
0x023C: 40 00 CMPEQ 0x00
0x023E: 43 06 JNZ 0x06 ; -> loc_0244 (0x0244)
0x0240: 11 14 LOAD 0x14
0x0242: 30 43 ADDI 0x43
loc_0244:
0x0244: 12 14 STORE 0x14
0x0246: 11 15 LOAD 0x15
0x0248: 40 00 CMPEQ 0x00
0x024A: 43 06 JNZ 0x06 ; -> loc_0250 (0x0250)
0x024C: 11 15 LOAD 0x15
0x024E: 30 46 ADDI 0x46
loc_0250:
0x0250: 12 15 STORE 0x15
0x0252: 11 16 LOAD 0x16
0x0254: 40 00 CMPEQ 0x00
0x0256: 43 06 JNZ 0x06 ; -> loc_025C (0x025C)
0x0258: 11 16 LOAD 0x16
0x025A: 30 49 ADDI 0x49
loc_025C:
0x025C: 12 16 STORE 0x16
0x025E: 11 17 LOAD 0x17
0x0260: 40 00 CMPEQ 0x00
0x0262: 43 06 JNZ 0x06 ; -> loc_0268 (0x0268)
0x0264: 11 17 LOAD 0x17
0x0266: 30 4C ADDI 0x4C
loc_0268:
0x0268: 12 17 STORE 0x17
0x026A: 11 18 LOAD 0x18
0x026C: 40 00 CMPEQ 0x00
0x026E: 43 06 JNZ 0x06 ; -> loc_0274 (0x0274)
0x0270: 11 18 LOAD 0x18
0x0272: 30 4F ADDI 0x4F
loc_0274:
0x0274: 12 18 STORE 0x18
0x0276: 11 19 LOAD 0x19
0x0278: 40 00 CMPEQ 0x00
0x027A: 43 06 JNZ 0x06 ; -> loc_0280 (0x0280)
0x027C: 11 19 LOAD 0x19
0x027E: 30 52 ADDI 0x52
loc_0280:
0x0280: 12 19 STORE 0x19
0x0282: 11 1A LOAD 0x1A
0x0284: 40 00 CMPEQ 0x00
0x0286: 43 06 JNZ 0x06 ; -> loc_028C (0x028C)
0x0288: 11 1A LOAD 0x1A
0x028A: 30 55 ADDI 0x55
loc_028C:
0x028C: 12 1A STORE 0x1A
0x028E: 11 00 LOAD 0x00
0x0290: 40 29 CMPEQ 0x29
0x0292: 42 B2 JZ 0xB2 ; -> loc_0344 (0x0344)
0x0294: 11 01 LOAD 0x01
0x0296: 40 47 CMPEQ 0x47
0x0298: 42 AC JZ 0xAC ; -> loc_0344 (0x0344)
0x029A: 11 02 LOAD 0x02
0x029C: 40 39 CMPEQ 0x39
0x029E: 42 A6 JZ 0xA6 ; -> loc_0344 (0x0344)
0x02A0: 11 03 LOAD 0x03
0x02A2: 40 1A CMPEQ 0x1A
0x02A4: 42 A0 JZ 0xA0 ; -> loc_0344 (0x0344)
0x02A6: 11 04 LOAD 0x04
0x02A8: 40 3F CMPEQ 0x3F
0x02AA: 42 9A JZ 0x9A ; -> loc_0344 (0x0344)
0x02AC: 11 05 LOAD 0x05
0x02AE: 40 50 CMPEQ 0x50
0x02B0: 42 94 JZ 0x94 ; -> loc_0344 (0x0344)
0x02B2: 11 06 LOAD 0x06
0x02B4: 40 39 CMPEQ 0x39
0x02B6: 42 8E JZ 0x8E ; -> loc_0344 (0x0344)
0x02B8: 11 07 LOAD 0x07
0x02BA: 40 26 CMPEQ 0x26
0x02BC: 42 88 JZ 0x88 ; -> loc_0344 (0x0344)
0x02BE: 11 08 LOAD 0x08
0x02C0: 40 40 CMPEQ 0x40
0x02C2: 42 82 JZ 0x82 ; -> loc_0344 (0x0344)
0x02C4: 11 09 LOAD 0x09
0x02C6: 40 5F CMPEQ 0x5F
0x02C8: 42 7C JZ 0x7C ; -> loc_0344 (0x0344)
0x02CA: 11 0A LOAD 0x0A
0x02CC: 40 61 CMPEQ 0x61
0x02CE: 42 76 JZ 0x76 ; -> loc_0344 (0x0344)
0x02D0: 11 0B LOAD 0x0B
0x02D2: 40 63 CMPEQ 0x63
0x02D4: 42 70 JZ 0x70 ; -> loc_0344 (0x0344)
0x02D6: 11 0C LOAD 0x0C
0x02D8: 40 69 CMPEQ 0x69
0x02DA: 42 6A JZ 0x6A ; -> loc_0344 (0x0344)
0x02DC: 11 0D LOAD 0x0D
0x02DE: 40 38 CMPEQ 0x38
0x02E0: 42 64 JZ 0x64 ; -> loc_0344 (0x0344)
0x02E2: 11 0E LOAD 0x0E
0x02E4: 40 52 CMPEQ 0x52
0x02E6: 42 5E JZ 0x5E ; -> loc_0344 (0x0344)
0x02E8: 11 0F LOAD 0x0F
0x02EA: 40 71 CMPEQ 0x71
0x02EC: 42 58 JZ 0x58 ; -> loc_0344 (0x0344)
0x02EE: 11 10 LOAD 0x10
0x02F0: 40 73 CMPEQ 0x73
0x02F2: 42 52 JZ 0x52 ; -> loc_0344 (0x0344)
0x02F4: 11 11 LOAD 0x11
0x02F6: 40 60 CMPEQ 0x60
0x02F8: 42 4C JZ 0x4C ; -> loc_0344 (0x0344)
0x02FA: 11 12 LOAD 0x12
0x02FC: 40 47 CMPEQ 0x47
0x02FE: 42 46 JZ 0x46 ; -> loc_0344 (0x0344)
0x0300: 11 13 LOAD 0x13
0x0302: 40 7C CMPEQ 0x7C
0x0304: 42 40 JZ 0x40 ; -> loc_0344 (0x0344)
0x0306: 11 14 LOAD 0x14
0x0308: 40 69 CMPEQ 0x69
0x030A: 42 3A JZ 0x3A ; -> loc_0344 (0x0344)
0x030C: 11 15 LOAD 0x15
0x030E: 40 50 CMPEQ 0x50
0x0310: 42 34 JZ 0x34 ; -> loc_0344 (0x0344)
0x0312: 11 16 LOAD 0x16
0x0314: 40 6A CMPEQ 0x6A
0x0316: 42 2E JZ 0x2E ; -> loc_0344 (0x0344)
0x0318: 11 17 LOAD 0x17
0x031A: 40 73 CMPEQ 0x73
0x031C: 42 28 JZ 0x28 ; -> loc_0344 (0x0344)
0x031E: 11 18 LOAD 0x18
0x0320: 40 6F CMPEQ 0x6F
0x0322: 42 22 JZ 0x22 ; -> loc_0344 (0x0344)
0x0324: 11 19 LOAD 0x19
0x0326: 40 82 CMPEQ 0x82
0x0328: 42 1C JZ 0x1C ; -> loc_0344 (0x0344)
0x032A: 11 1A LOAD 0x1A
0x032C: 40 BF CMPEQ 0xBF
0x032E: 42 16 JZ 0x16 ; -> loc_0344 (0x0344)
0x0330: 10 04 ALLOC 0x04
0x0332: 20 79 MOVI 0x79
0x0334: 12 00 STORE 0x00
0x0336: 20 65 MOVI 0x65
0x0338: 12 01 STORE 0x01
0x033A: 20 73 MOVI 0x73
0x033C: 12 02 STORE 0x02
0x033E: 20 00 MOVI 0x00
0x0340: 12 03 STORE 0x03
0x0342: 52 02 SYSCALL 0x02 ; PRINT_STRING
loc_0344:
0x0344: 41 10 OP41 0x10
0x0346: 10 03 ALLOC 0x03
0x0348: 20 4E MOVI 0x4E
0x034A: 12 00 STORE 0x00
0x034C: 20 6F MOVI 0x6F
0x034E: 12 01 STORE 0x01
0x0350: 20 00 MOVI 0x00
0x0352: 12 02 STORE 0x02
0x0354: 52 02 SYSCALL 0x02 ; PRINT_STRING
0x0356: 13 FREE

主要逻辑是两轮

第一轮

1
2
if input[i]!= 0:
input[i]^=55

第二轮:

1
2
if input[i]!=0:
input[i]+=+3*i+7

最后与

1
[41, 71, 57, 26, 63, 80, 57, 38, 64, 95, 97, 99, 105, 56, 82, 113, 115, 96, 71, 124, 105, 80, 106, 115, 111, 130, 191]

逆向就是((result-7-3*i)&0xFF)^55

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
int main()
{
int arry[] = {41, 71, 57, 26, 63, 80, 57, 38, 64, 95, 97, 99, 105, 56, 82, 113, 115, 96, 71, 124, 105, 80, 106, 115, 111, 130, 191};
for(int i = 0; i<27; i++)
{
arry[i] = ((arry[i]-7-3*i)&0xFF)^0x55;
}
for(int i = 0;i<27;i++)
{
cout<<(char)arry[i];
}
}

得到why_you_think_this_is_true?,很明显是faker flag

一般来说pyd是动态链接文件类似于dll,也是PE文件,开头的魔法数字为MZ,而ntbase.pyd开头

10 64 10 32 20 00 51 12 63 显然是vm流

大概就是先不为0时加10,之后前缀异或最后为技术加0x31,为偶数家伙是那个0x10

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
target = [
0x60, 0x9B, 0x22, 0xAC, 0x40, 0x79, 0x36, 0x80, 0x82,
0x4A, 0x74, 0x18, 0x97, 0x25, 0xB3, 0x23, 0xA9, 0xD3,
0x32, 0x4A, 0x86, 0x57, 0x75, 0x4A, 0x8A, 0x61, 0x5F
]

z = []
for i, c in enumerate(target):
if i % 2 == 0:
z.append((c - 0x10) & 0xFF)
else:
z.append((c - 0x31) & 0xFF)

y = [z[0]]
for i in range(1, len(z)):
y.append(z[i] ^ z[i - 1])

x = []
for v in y:
x.append((v - 10) & 0xFF)

plain = ''.join(chr(c) for c in x)
flag = f"xmctf{{{plain}}}"
print(plain)
print(flag)

得出flag为F0n_And_3asyViMGa1v1eF9rY@u

python的set()集合

创建集合的两种方法,其支持集合的多种操作

1
2
set1 = {1, 2, 3}
set2 = set([4,5,6])

添加元素

1
2
set1.add(4)
print(s)

添加多个元素

1
set1.update([5,6],{7,8})

删除元素的方法

remove(x)删除指定元素,不存在则报错

discard(x)移除指定元素,不存在时不报错

pop()随即清理一个元素

clear()清除随机一个元素

集合运算

交集a&b或者a.intersection(b)

并集a | b或者a.union(b)

差集 a-b或a.difference(b)

对称差集a^b,a.symmertic_difference(b) ——–>{1,2,3}^{3,4,5}—>{1,2,4,5}

判断关系

issubset()是否为子集

issuperset():判断是否为超集

isdisjoint():判断两个集合是否无交集

注意

  • Set 中的元素必须是不可变类型,如数字、字符串、元组。
  • Set 本身是可变的,因此不能作为字典的键或其他集合的元素。

reference

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