# TRX CTF 2026 reverse 方向 write up

# wandering

将 vm 拖入 ida 进行分析,main 函数展现了一个标准的 VM 生命周期:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int v3; // r13d
void *v4; // rax
void *v5; // rbp
_QWORD *v6; // rax
void *v7; // r12
__int64 v9[5]; // [rsp+0h] [rbp-28h] BYREF

v3 = 1;
v9[1] = __readfsqword(0x28u); // 栈金丝雀(Stack Canary)
setvbuf(stdout, 0, 2, 0); // 关闭标准输出缓冲
v4 = sub_1C30(v9); // 从程序的只读数据段里,把那一长串bytecode字节码 读出来放进v4里。v9[0]记录了有多长
if ( v4 )
{
v5 = v4;
v6 = sub_13A0((__int64)v4, v9[0]); // 拿到字节码后,开始初始化虚拟机的内存空间。v6就是 虚拟机的控制台
v7 = v6;
if ( v6 )
{
v3 = 0;
sub_1630(v6); // 虚拟机开始执行指令
free(v7);
free(v5);
}
else
{
free(v5);
puts("Failed initialization");
}
}
return v3;
}

  • sub_1C30: 从.data 段提取加密的字节码 (Bytecode),并在堆上分配内存
  • sub_13A0: 使用 calloc 分配了一块约 4MB 的内存空间,内存布局如下:
    • Offset 0 (QWORD): 字节码基址指针
    • Offset 8 (QWORD): 字节码长度
    • Offset 4120 (DWORD, 索引 1030): 栈顶指针 (SP), 初始化为 - 1
    • Offset 4124 (DWORD, 索引 1031): 随机数种子 (Seed)
  • sub_1630:VM 的核心调度器(Dispatcher),包含一个 while 循环和解析指令的 switch-case。

在初始化函数中,程序使用 time(0) ^ getpid() ^ 2781082089 生成了一个随机数种子存入 1031 处。由于远程时间精度和 PID 均不可预测,静态推断 Seed 的路径无效。

sub_1630 的核心循环中,存在如下逻辑:

1
2
v4 = sub_1410(*(unsigned int *)(v3 + 8 * v1));
v7 = (unsigned int)sub_1470(*(unsigned int *)(v5 + 4), v3, v6, v5, v4);

通过指针步长分析,可以知道

  • 前 4 字节:送入 sub_1410 解密,得到真实的操作码 (Opcode)。
  • 后 4 字节:送入 sub_1470 解密,得到真实的操作数 (Operand) 。

这两个解密函数内部充斥着大量的混合布尔算术(MBA)混淆(如大量异或、位移和魔法数字组合),但是我们并不需要去进行逆向,直接用对 switch-case 分支的分析(如 case 5 为 ADD, case 40 为 XOR),编写 C 语言反汇编器。

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
#include <stdio.h>
#include <stdint.h>


#define HIWORD(a) ((uint16_t)(((uint32_t)(a) >> 16) & 0xFFFF))
#define BYTE1(a) ((uint8_t)(((uint32_t)(a) >> 8) & 0xFF))

// Opcode 解密算法
uint64_t sub_1410(uint32_t a1) {
return (16 * HIWORD(a1) + 4 * (uint8_t)a1 + a1 * (2 * HIWORD(a1) + 1))
^ ((16 * HIWORD(a1)
+ 4 * (uint8_t)a1
+ a1 * (uint64_t)(2 * HIWORD(a1) + 1)
+ (((uint8_t)(a1 ^ BYTE1(a1)) + a1 * (uint64_t)((4 * (uint16_t)(a1 >> 8)) & 0x3FC | 2u)) << 32)) >> 32);
}

// Operand 解密算法
int64_t sub_1470(int32_t a1) {
uint32_t v1 = a1 ^ 0xA5C3F1E9;
int32_t v2 = 4, v3 = 2;
uint32_t v4;
do {
v4 = v1 >> v3;
v3 *= 2;
v1 ^= v4;
--v2;
} while ( v2 );

int32_t v5 = 3;
uint32_t v6 = ((v1 ^ (32 * v1)) << 10) ^ v1 ^ (32 * v1) ^ ((((v1 ^ (32 * v1)) << 10) ^ v1 ^ (32 * v1)) << 20);
int32_t v7 = 4;
uint32_t v8;
do {
v8 = v6 >> v5;
v5 *= 2;
v6 ^= v8;
--v7;
} while ( v7 );

return (v6 << 7) ^ v6 ^ (((v6 << 7) ^ v6) << 14) ^ (((v6 << 7) ^ v6 ^ (((v6 << 7) ^ v6) << 14)) << 28);
}

// 指令映射字典
const char* get_opcode_name(uint32_t op) {
switch(op) {
case 2: return "PUSH";
case 5: return "ADD";
case 6: return "SUB";
case 7: return "MUL";
case 8: return "DIV";
case 11: return "STORE_MEM";
case 12: return "JMP";
case 13: return "JZ";
case 15: return "PUSH_PC";
case 17: return "CMP_EQ";
case 19: return "CMP_LT";
case 25: return "CALL_SYS";
case 26: return "LOAD_FROM_ADDR";
case 27: return "STORE_TO_ADDR";
case 29: return "LOAD_MEM";
case 30: return "JNZ";
case 33: return "FAIL";
case 34: return "RET";
case 38: return "CMP_GT";
case 39: return "ABS";
case 40: return "XOR";
case 41: return "SUCCESS";
case 42: return "RAND_MOD";
default: return "UNKNOWN";
}
}

int main() {
// 提取的 1024 字节 Bytecode
uint8_t bytecode[] = {
0x86, 0x10, 0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5, 0x22, 0x49,
0x40, 0x22, 0xE9, 0xF1, 0xC3, 0xA5, 0x14, 0x2F, 0x13, 0x86,
0x5F, 0x19, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0x1D, 0xAE,
0xC1, 0xA5, 0x37, 0x0B, 0xC3, 0xDA, 0x10, 0x6D, 0xC3, 0xA5,
0x14, 0x2F, 0x13, 0x86, 0x07, 0x28, 0xC4, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0x1D, 0xAE, 0xC1, 0xA5, 0x37, 0x0B, 0xC3, 0xDA,
0x10, 0x6D, 0xC3, 0xA5, 0xE4, 0xC9, 0x33, 0xB1, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB1, 0x66, 0x01, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x12, 0x21, 0x0D,
0x2A, 0x41, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0x9C, 0xA9,
0xC3, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0xA6, 0x85, 0xC3, 0xA5, 0xB8, 0x12,
0x21, 0x0D, 0x2A, 0x41, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0x9C, 0xA9, 0xC3, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0xBB, 0x93, 0xC3, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xA6, 0x85, 0xC3, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0xE9, 0x07, 0xC5, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0x81, 0xE9,
0xD5, 0xA5, 0xE7, 0xCD, 0x44, 0x7D, 0x00, 0x00, 0x00, 0x00,
0xB5, 0xAB, 0x9F, 0x6C, 0xBB, 0x93, 0xC3, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0xC0, 0xEB, 0xC8, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00,
0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0x69, 0x77, 0xA2, 0xA4,
0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49,
0x40, 0x22, 0xCE, 0xCB, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xCE, 0xCB,
0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5,
0xCE, 0x4C, 0xB0, 0x79, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x39,
0x7F, 0xC4, 0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C,
0xA6, 0x85, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0x3D, 0xF2,
0xC2, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x86, 0x10, 0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5, 0xE7, 0xCD,
0x44, 0x7D, 0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C,
0xBB, 0x93, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0x4F, 0xCC,
0xC1, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0x86, 0x10,
0x02, 0x1B, 0x69, 0x77, 0xA2, 0xA4, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0xCE, 0xCB,
0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xCE, 0xCB, 0xC3, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0xCE, 0x4C, 0xB0, 0x79,
0x00, 0x00, 0x00, 0x00, 0x2A, 0x39, 0x7F, 0xC4, 0x00, 0x00,
0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0xD3, 0xDD, 0xC3, 0xA5,
0x22, 0x49, 0x40, 0x22, 0x9B, 0x39, 0xC6, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0xAC, 0xD6, 0xC4, 0xA5,
0xB3, 0xDE, 0xEA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49,
0x40, 0x22, 0xF4, 0xE7, 0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22,
0xD3, 0xDD, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xF4, 0xE7,
0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5,
0xA2, 0x31, 0x67, 0x13, 0x00, 0x00, 0x00, 0x00, 0xF2, 0x8A,
0x4D, 0x0A, 0x8B, 0xEC, 0xC4, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xE9, 0xF1,
0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00, 0x00, 0x00,
0x37, 0x0B, 0xC3, 0xDA, 0x8B, 0xEC, 0xC4, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xD3, 0xDD, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00,
0x00, 0x00, 0xF2, 0x8A, 0x4D, 0x0A, 0x8B, 0xEC, 0xC4, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xD3, 0xDD, 0xC3, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13,
0x00, 0x00, 0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0x8B, 0xEC,
0xC4, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xD3, 0xDD, 0xC3, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5, 0xE7, 0xCD,
0x44, 0x7D, 0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C,
0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00,
0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0x69, 0x77, 0xA2, 0xA4,
0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49,
0x40, 0x22, 0xCE, 0xCB, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xCE, 0xCB, 0xC3, 0xA5, 0xE6, 0x18, 0xA6, 0x5A, 0x00, 0x00,
0x00, 0x00, 0xF2, 0x8A, 0x4D, 0x0A, 0x8B, 0xEC, 0xC4, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xCE, 0xCB, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0xCE, 0x4C, 0xB0, 0x79, 0x00, 0x00,
0x00, 0x00, 0x2A, 0x39, 0x7F, 0xC4, 0x00, 0x00, 0x00, 0x00,
0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0x9B, 0x39,
0xC6, 0xA5, 0x02, 0x00, 0x00, 0x00, 0xA1, 0xE3, 0xC0, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49,
0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5, 0xAD, 0xED, 0x8C, 0x05,
0x00, 0x00, 0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7,
0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5,
0xAD, 0xED, 0x8C, 0x05, 0x00, 0x00, 0x00, 0x00, 0x86, 0x10,
0x02, 0x1B, 0xCE, 0xCB, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xD3, 0xDD, 0xC3, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5,
0x14, 0x2F, 0x13, 0x86, 0x42, 0xF9, 0xC5, 0xA5, 0x14, 0x2F,
0x13, 0x86, 0xED, 0x74, 0xC9, 0xA5, 0x14, 0x2F, 0x13, 0x86,
0x88, 0x0F, 0xCD, 0xA5, 0xAD, 0xED, 0x8C, 0x05, 0x00, 0x00,
0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5,
0x22, 0x49, 0x40, 0x22, 0x9B, 0x39, 0xC6, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0xBF, 0x16, 0xC9, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49,
0x40, 0x22, 0xF4, 0xE7, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22, 0xBC, 0x03,
0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31,
0x67, 0x13, 0x00, 0x00, 0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA,
0x5B, 0x9C, 0xC9, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39,
0xC6, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5,
0xE7, 0xCD, 0x44, 0x7D, 0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB,
0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x86, 0x10, 0x02, 0x1B, 0x69, 0x77,
0xA2, 0xA4, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0xE6, 0x18, 0xA6, 0x5A, 0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB,
0x9F, 0x6C, 0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0xF4, 0xE7,
0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1,
0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49, 0x40, 0x22,
0xBC, 0x03, 0xC6, 0xA5, 0x02, 0x00, 0x00, 0x00, 0xCE, 0x3D,
0xC5, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xF4, 0xE7, 0xC3, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5, 0x2F, 0x32,
0x44, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA,
0x14, 0xE8, 0xC9, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1,
0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29,
0x00, 0x00, 0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0x9B, 0x39,
0xC6, 0xA5, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x8D, 0xC5, 0xA5,
0xAD, 0xED, 0x8C, 0x05, 0x00, 0x00, 0x00, 0x00, 0x86, 0x10,
0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22,
0x9B, 0x39, 0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39,
0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xE9, 0xF1, 0xC3, 0xA5,
0xA2, 0x31, 0x67, 0x13, 0x00, 0x00, 0x00, 0x00, 0x37, 0x0B,
0xC3, 0xDA, 0xAF, 0x35, 0xCD, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22, 0xF4, 0xE7,
0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5,
0x22, 0x49, 0x40, 0x22, 0xBC, 0x03, 0xC6, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0xAF, 0xC3, 0xCB, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5, 0xE7, 0xCD, 0x44, 0x7D,
0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39,
0xC6, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x86, 0x10, 0x02, 0x1B, 0x69, 0x77, 0xA2, 0xA4, 0x9A, 0xF1,
0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x18, 0xA6, 0x5A,
0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C, 0xF4, 0xE7,
0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xBC, 0x03, 0xC6, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00,
0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0xBC, 0x03, 0xC6, 0xA5,
0x02, 0x00, 0x00, 0x00, 0x9F, 0x4A, 0xCA, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xF4, 0xE7, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xF4, 0xE7, 0xC3, 0xA5, 0x2F, 0x32, 0x44, 0x0B, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0xC7, 0x7B, 0xCD, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49,
0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0x9B, 0x39, 0xC6, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7,
0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0x9B, 0x39, 0xC6, 0xA5, 0x02, 0x00,
0x00, 0x00, 0xD7, 0x58, 0xC9, 0xA5, 0xAD, 0xED, 0x8C, 0x05,
0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C, 0xE9, 0xF1,
0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7, 0xC3, 0xA5,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49,
0x40, 0x22, 0xCE, 0xCB, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xCE, 0xCB,
0xC3, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xF4, 0xE7, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xCE, 0xCB, 0xC3, 0xA5, 0x2F, 0x32, 0x44, 0x0B, 0x00, 0x00,
0x00, 0x00, 0xF2, 0x8A, 0x4D, 0x0A, 0x9C, 0xFF, 0xD5, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49,
0x40, 0x22, 0xD3, 0xDD, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49, 0x40, 0x22, 0x9B, 0x39,
0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31,
0x67, 0x13, 0x00, 0x00, 0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA,
0x03, 0xAD, 0xCE, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39,
0xC6, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C, 0xF4, 0xE7, 0xC3, 0xA5,
0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0x22, 0x49,
0x40, 0x22, 0x9C, 0xA9, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0x9C, 0xA9, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1,
0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00, 0x00, 0x00,
0xF2, 0x8A, 0x4D, 0x0A, 0x51, 0xCF, 0xCE, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0x9C, 0xA9, 0xC3, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xE9, 0xF1, 0xC3, 0xA5, 0xA2, 0x31, 0x67, 0x13, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0x51, 0xCF, 0xCE, 0xA5,
0xB5, 0xAB, 0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0x86, 0x10,
0x02, 0x1B, 0x81, 0xE9, 0xD5, 0xA5, 0xE7, 0xCD, 0x44, 0x7D,
0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C, 0x9C, 0xA9,
0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x86, 0x10, 0x02, 0x1B, 0x69, 0x77, 0xA2, 0xA4, 0x9A, 0xF1,
0x0A, 0x29, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x18, 0xA6, 0x5A,
0x00, 0x00, 0x00, 0x00, 0xB5, 0xAB, 0x9F, 0x6C, 0xD3, 0xDD,
0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0xD3, 0xDD, 0xC3, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0x9B, 0x39, 0xC6, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xF4, 0xE7, 0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00,
0x00, 0x00, 0x22, 0x49, 0x40, 0x22, 0x9B, 0x39, 0xC6, 0xA5,
0x02, 0x00, 0x00, 0x00, 0x2A, 0x17, 0xD5, 0xA5, 0xB5, 0xAB,
0x9F, 0x6C, 0xD3, 0xDD, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B,
0xF4, 0xE7, 0xC3, 0xA5, 0x2F, 0x32, 0x44, 0x0B, 0x00, 0x00,
0x00, 0x00, 0x37, 0x0B, 0xC3, 0xDA, 0x2D, 0x71, 0xD6, 0xA5,
0x86, 0x10, 0x02, 0x1B, 0xE9, 0xF1, 0xC3, 0xA5, 0x22, 0x49,
0x40, 0x22, 0x1D, 0xAE, 0xC1, 0xA5, 0xB5, 0xAB, 0x9F, 0x6C,
0xF4, 0xE7, 0xC3, 0xA5, 0x86, 0x10, 0x02, 0x1B, 0xF4, 0xE7,
0xC3, 0xA5, 0x9A, 0xF1, 0x0A, 0x29, 0x00, 0x00, 0x00, 0x00,
0x22, 0x49, 0x40, 0x22, 0xF4, 0xE7, 0xC3, 0xA5, 0x02, 0x00,
0x00, 0x00, 0xA6, 0xD3, 0xD5, 0xA5, 0xAD, 0xED, 0x8C, 0x05,
0x00, 0x00, 0x00, 0x00
};

int num_instructions = sizeof(bytecode) / 8;
printf("PC\t| Opcode\t| Mnemonic\t| Operand\n");
printf("-----------------------------------------------------------------\n");

for (int i = 0; i < num_instructions; i++) {
uint32_t raw_op = *(uint32_t*)(&bytecode[i * 8]);
uint32_t raw_arg = *(uint32_t*)(&bytecode[i * 8 + 4]);

uint32_t real_op = (uint32_t)sub_1410(raw_op);
uint32_t real_arg = (uint32_t)sub_1470(raw_arg);

printf("%04d\t| %-8d\t| %-12s\t| 0x%08X (%d)\n",
i, real_op, get_opcode_name(real_op), real_arg, real_arg);
}

return 0;
}

运行得到

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
PC      | Opcode        | Mnemonic      | Operand                                                               
-----------------------------------------------------------------
0000 | 2 | PUSH | 0x00000100 (256)
0001 | 11 | STORE_MEM | 0x00000000 (0)
0002 | 15 | PUSH_PC | 0x0000000C (12)
0003 | 29 | LOAD_MEM | 0x0000002A (42)
0004 | 13 | JZ | 0x0000000A (10)
0005 | 15 | PUSH_PC | 0x0000006A (106)
0006 | 29 | LOAD_MEM | 0x0000002A (42)
0007 | 13 | JZ | 0x0000000A (10)
0008 | 41 | SUCCESS | 0x7C671A0A (2087131658)
0009 | 24 | UNKNOWN | 0x7C671A0A (2087131658)
0010 | 33 | FAIL | 0x7C671A0A (2087131658)
0011 | 24 | UNKNOWN | 0x7C671A0A (2087131658)
0012 | 42 | RAND_MOD | 0x00000008 (8)
0013 | 2 | PUSH | 0x00000004 (4)
0014 | 6 | SUB | 0x7C671A0A (2087131658)
0015 | 11 | STORE_MEM | 0x00000006 (6)
0016 | 42 | RAND_MOD | 0x00000008 (8)
0017 | 2 | PUSH | 0x00000004 (4)
0018 | 6 | SUB | 0x7C671A0A (2087131658)
0019 | 11 | STORE_MEM | 0x00000007 (7)
0020 | 29 | LOAD_MEM | 0x00000006 (6)
0021 | 2 | PUSH | 0x0000007F (127)
0022 | 5 | ADD | 0x7C671A0A (2087131658)
0023 | 2 | PUSH | 0x00000100 (256)
0024 | 7 | MUL | 0x7C671A0A (2087131658)
0025 | 29 | LOAD_MEM | 0x00000007 (7)
0026 | 2 | PUSH | 0x00000081 (129)
0027 | 5 | ADD | 0x7C671A0A (2087131658)
0028 | 5 | ADD | 0x7C671A0A (2087131658)
0029 | 2 | PUSH | 0x00001000 (4096)
0030 | 5 | ADD | 0x7C671A0A (2087131658)
0031 | 11 | STORE_MEM | 0x00000003 (3)
0032 | 2 | PUSH | 0x00000001 (1)
0033 | 29 | LOAD_MEM | 0x00000003 (3)
0034 | 2 | PUSH | 0x00000000 (0)
0035 | 40 | XOR | 0x7C671A0A (2087131658)
0036 | 27 | STORE_TO_ADDR | 0x7C671A0A (2087131658)
0037 | 29 | LOAD_MEM | 0x00000006 (6)
0038 | 2 | PUSH | 0x00000017 (23)
0039 | 5 | ADD | 0x7C671A0A (2087131658)
0040 | 2 | PUSH | 0x00000100 (256)
0041 | 7 | MUL | 0x7C671A0A (2087131658)
0042 | 29 | LOAD_MEM | 0x00000007 (7)
0043 | 2 | PUSH | 0x0000002D (45)
0044 | 5 | ADD | 0x7C671A0A (2087131658)
0045 | 5 | ADD | 0x7C671A0A (2087131658)
0046 | 2 | PUSH | 0x00001000 (4096)
0047 | 5 | ADD | 0x7C671A0A (2087131658)
0048 | 11 | STORE_MEM | 0x00000003 (3)
0049 | 2 | PUSH | 0x00000001 (1)
0050 | 29 | LOAD_MEM | 0x00000003 (3)
0051 | 2 | PUSH | 0x00000000 (0)
0052 | 40 | XOR | 0x7C671A0A (2087131658)
0053 | 27 | STORE_TO_ADDR | 0x7C671A0A (2087131658)
0054 | 2 | PUSH | 0x00000002 (2)
0055 | 11 | STORE_MEM | 0x00000045 (69)
0056 | 29 | LOAD_MEM | 0x00000045 (69)
0057 | 29 | LOAD_MEM | 0x00000000 (0)
0058 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0059 | 13 | JZ | 0x00000067 (103)
0060 | 25 | CALL_SYS | 0x7C671A0A (2087131658)
0061 | 11 | STORE_MEM | 0x00000001 (1)
0062 | 11 | STORE_MEM | 0x00000002 (2)
0063 | 29 | LOAD_MEM | 0x00000001 (1)
0064 | 2 | PUSH | 0x00000000 (0)
0065 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0066 | 30 | JNZ | 0x00000064 (100)
0067 | 29 | LOAD_MEM | 0x00000001 (1)
0068 | 29 | LOAD_MEM | 0x00000000 (0)
0069 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0070 | 13 | JZ | 0x00000064 (100)
0071 | 29 | LOAD_MEM | 0x00000002 (2)
0072 | 2 | PUSH | 0x00000000 (0)
0073 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0074 | 30 | JNZ | 0x00000064 (100)
0075 | 29 | LOAD_MEM | 0x00000002 (2)
0076 | 29 | LOAD_MEM | 0x00000000 (0)
0077 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0078 | 13 | JZ | 0x00000064 (100)
0079 | 29 | LOAD_MEM | 0x00000002 (2)
0080 | 2 | PUSH | 0x00000100 (256)
0081 | 7 | MUL | 0x7C671A0A (2087131658)
0082 | 29 | LOAD_MEM | 0x00000001 (1)
0083 | 5 | ADD | 0x7C671A0A (2087131658)
0084 | 2 | PUSH | 0x00001000 (4096)
0085 | 5 | ADD | 0x7C671A0A (2087131658)
0086 | 11 | STORE_MEM | 0x00000003 (3)
0087 | 29 | LOAD_MEM | 0x00000003 (3)
0088 | 26 | LOAD_FROM_ADDR | 0x7C671A0A (2087131658)
0089 | 30 | JNZ | 0x00000064 (100)
0090 | 2 | PUSH | 0x00000001 (1)
0091 | 29 | LOAD_MEM | 0x00000003 (3)
0092 | 2 | PUSH | 0x00000000 (0)
0093 | 40 | XOR | 0x7C671A0A (2087131658)
0094 | 27 | STORE_TO_ADDR | 0x7C671A0A (2087131658)
0095 | 29 | LOAD_MEM | 0x00000045 (69)
0096 | 2 | PUSH | 0x00000001 (1)
0097 | 5 | ADD | 0x7C671A0A (2087131658)
0098 | 11 | STORE_MEM | 0x00000045 (69)
0099 | 12 | JMP | 0x00000038 (56)
0100 | 2 | PUSH | 0x00000000 (0)
0101 | 11 | STORE_MEM | 0x0000002A (42)
0102 | 34 | RET | 0x7C671A0A (2087131658)
0103 | 2 | PUSH | 0x00000001 (1)
0104 | 11 | STORE_MEM | 0x0000002A (42)
0105 | 34 | RET | 0x7C671A0A (2087131658)
0106 | 2 | PUSH | 0x00000003 (3)
0107 | 2 | PUSH | 0x00000002 (2)
0108 | 6 | SUB | 0x7C671A0A (2087131658)
0109 | 11 | STORE_MEM | 0x0000002A (42)
0110 | 15 | PUSH_PC | 0x00000072 (114)
0111 | 15 | PUSH_PC | 0x0000009C (156)
0112 | 15 | PUSH_PC | 0x000000C6 (198)
0113 | 34 | RET | 0x7C671A0A (2087131658)
0114 | 2 | PUSH | 0x00000000 (0)
0115 | 11 | STORE_MEM | 0x00000045 (69)
0116 | 29 | LOAD_MEM | 0x00000045 (69)
0117 | 29 | LOAD_MEM | 0x00000000 (0)
0118 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0119 | 13 | JZ | 0x0000009B (155)
0120 | 2 | PUSH | 0x00000000 (0)
0121 | 11 | STORE_MEM | 0x00000001 (1)
0122 | 2 | PUSH | 0x00000000 (0)
0123 | 11 | STORE_MEM | 0x00000046 (70)
0124 | 29 | LOAD_MEM | 0x00000046 (70)
0125 | 29 | LOAD_MEM | 0x00000000 (0)
0126 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0127 | 13 | JZ | 0x00000090 (144)
0128 | 29 | LOAD_MEM | 0x00000045 (69)
0129 | 2 | PUSH | 0x00000100 (256)
0130 | 7 | MUL | 0x7C671A0A (2087131658)
0131 | 29 | LOAD_MEM | 0x00000046 (70)
0132 | 5 | ADD | 0x7C671A0A (2087131658)
0133 | 2 | PUSH | 0x00001000 (4096)
0134 | 5 | ADD | 0x7C671A0A (2087131658)
0135 | 26 | LOAD_FROM_ADDR | 0x7C671A0A (2087131658)
0136 | 29 | LOAD_MEM | 0x00000001 (1)
0137 | 5 | ADD | 0x7C671A0A (2087131658)
0138 | 11 | STORE_MEM | 0x00000001 (1)
0139 | 29 | LOAD_MEM | 0x00000046 (70)
0140 | 2 | PUSH | 0x00000001 (1)
0141 | 5 | ADD | 0x7C671A0A (2087131658)
0142 | 11 | STORE_MEM | 0x00000046 (70)
0143 | 12 | JMP | 0x0000007C (124)
0144 | 29 | LOAD_MEM | 0x00000001 (1)
0145 | 2 | PUSH | 0x00000001 (1)
0146 | 19 | CMP_LT | 0x7C671A0A (2087131658)
0147 | 13 | JZ | 0x00000096 (150)
0148 | 2 | PUSH | 0x00000000 (0)
0149 | 11 | STORE_MEM | 0x0000002A (42)
0150 | 29 | LOAD_MEM | 0x00000045 (69)
0151 | 2 | PUSH | 0x00000001 (1)
0152 | 5 | ADD | 0x7C671A0A (2087131658)
0153 | 11 | STORE_MEM | 0x00000045 (69)
0154 | 12 | JMP | 0x00000074 (116)
0155 | 34 | RET | 0x7C671A0A (2087131658)
0156 | 2 | PUSH | 0x00000000 (0)
0157 | 11 | STORE_MEM | 0x00000045 (69)
0158 | 29 | LOAD_MEM | 0x00000045 (69)
0159 | 29 | LOAD_MEM | 0x00000000 (0)
0160 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0161 | 13 | JZ | 0x000000C5 (197)
0162 | 2 | PUSH | 0x00000000 (0)
0163 | 11 | STORE_MEM | 0x00000001 (1)
0164 | 2 | PUSH | 0x00000000 (0)
0165 | 11 | STORE_MEM | 0x00000046 (70)
0166 | 29 | LOAD_MEM | 0x00000046 (70)
0167 | 29 | LOAD_MEM | 0x00000000 (0)
0168 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0169 | 13 | JZ | 0x000000BA (186)
0170 | 29 | LOAD_MEM | 0x00000046 (70)
0171 | 2 | PUSH | 0x00000100 (256)
0172 | 7 | MUL | 0x7C671A0A (2087131658)
0173 | 29 | LOAD_MEM | 0x00000045 (69)
0174 | 5 | ADD | 0x7C671A0A (2087131658)
0175 | 2 | PUSH | 0x00001000 (4096)
0176 | 5 | ADD | 0x7C671A0A (2087131658)
0177 | 26 | LOAD_FROM_ADDR | 0x7C671A0A (2087131658)
0178 | 29 | LOAD_MEM | 0x00000001 (1)
0179 | 5 | ADD | 0x7C671A0A (2087131658)
0180 | 11 | STORE_MEM | 0x00000001 (1)
0181 | 29 | LOAD_MEM | 0x00000046 (70)
0182 | 2 | PUSH | 0x00000001 (1)
0183 | 5 | ADD | 0x7C671A0A (2087131658)
0184 | 11 | STORE_MEM | 0x00000046 (70)
0185 | 12 | JMP | 0x000000A6 (166)
0186 | 29 | LOAD_MEM | 0x00000001 (1)
0187 | 2 | PUSH | 0x00000001 (1)
0188 | 19 | CMP_LT | 0x7C671A0A (2087131658)
0189 | 13 | JZ | 0x000000C0 (192)
0190 | 2 | PUSH | 0x00000000 (0)
0191 | 11 | STORE_MEM | 0x0000002A (42)
0192 | 29 | LOAD_MEM | 0x00000045 (69)
0193 | 2 | PUSH | 0x00000001 (1)
0194 | 5 | ADD | 0x7C671A0A (2087131658)
0195 | 11 | STORE_MEM | 0x00000045 (69)
0196 | 12 | JMP | 0x0000009E (158)
0197 | 34 | RET | 0x7C671A0A (2087131658)
0198 | 29 | LOAD_MEM | 0x00000000 (0)
0199 | 2 | PUSH | 0x00000001 (1)
0200 | 6 | SUB | 0x7C671A0A (2087131658)
0201 | 11 | STORE_MEM | 0x00000003 (3)
0202 | 2 | PUSH | 0x00000000 (0)
0203 | 29 | LOAD_MEM | 0x00000003 (3)
0204 | 6 | SUB | 0x7C671A0A (2087131658)
0205 | 11 | STORE_MEM | 0x00000001 (1)
0206 | 29 | LOAD_MEM | 0x00000001 (1)
0207 | 29 | LOAD_MEM | 0x00000003 (3)
0208 | 19 | CMP_LT | 0x7C671A0A (2087131658)
0209 | 30 | JNZ | 0x00000101 (257)
0210 | 2 | PUSH | 0x00000000 (0)
0211 | 11 | STORE_MEM | 0x00000002 (2)
0212 | 2 | PUSH | 0x00000000 (0)
0213 | 11 | STORE_MEM | 0x00000045 (69)
0214 | 29 | LOAD_MEM | 0x00000045 (69)
0215 | 29 | LOAD_MEM | 0x00000000 (0)
0216 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0217 | 13 | JZ | 0x000000F6 (246)
0218 | 29 | LOAD_MEM | 0x00000045 (69)
0219 | 29 | LOAD_MEM | 0x00000001 (1)
0220 | 5 | ADD | 0x7C671A0A (2087131658)
0221 | 11 | STORE_MEM | 0x00000004 (4)
0222 | 29 | LOAD_MEM | 0x00000004 (4)
0223 | 2 | PUSH | 0x00000000 (0)
0224 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0225 | 30 | JNZ | 0x000000F1 (241)
0226 | 29 | LOAD_MEM | 0x00000004 (4)
0227 | 29 | LOAD_MEM | 0x00000000 (0)
0228 | 38 | CMP_GT | 0x7C671A0A (2087131658)
0229 | 13 | JZ | 0x000000F1 (241)
0230 | 29 | LOAD_MEM | 0x00000045 (69)
0231 | 2 | PUSH | 0x00000100 (256)
0232 | 7 | MUL | 0x7C671A0A (2087131658)
0233 | 29 | LOAD_MEM | 0x00000004 (4)
0234 | 5 | ADD | 0x7C671A0A (2087131658)
0235 | 2 | PUSH | 0x00001000 (4096)
0236 | 5 | ADD | 0x7C671A0A (2087131658)
0237 | 26 | LOAD_FROM_ADDR | 0x7C671A0A (2087131658)
0238 | 29 | LOAD_MEM | 0x00000002 (2)
0239 | 5 | ADD | 0x7C671A0A (2087131658)
0240 | 11 | STORE_MEM | 0x00000002 (2)
0241 | 29 | LOAD_MEM | 0x00000045 (69)
0242 | 2 | PUSH | 0x00000001 (1)
0243 | 5 | ADD | 0x7C671A0A (2087131658)
0244 | 11 | STORE_MEM | 0x00000045 (69)
0245 | 12 | JMP | 0x0000010D (269)
0246 | 29 | LOAD_MEM | 0x00000002 (2)
0247 | 2 | PUSH | 0x00000001 (1)
0248 | 19 | CMP_LT | 0x7C671A0A (2087131658)
0249 | 13 | JZ | 0x00000133 (307)
0250 | 2 | PUSH | 0x00000000 (0)
0251 | 11 | STORE_MEM | 0x0000002A (42)
0252 | 29 | LOAD_MEM | 0x00000001 (1)
0253 | 2 | PUSH | 0x00000001 (1)
0254 | 5 | ADD | 0x7C671A0A (2087131658)
0255 | 11 | STORE_MEM | 0x00000001 (1)
0256 | 12 | JMP | 0x00000103 (259)
0257 | 34 | RET | 0x7C671A0A (2087131658)

通过分析这段指令流(从 00800088 ):

1
2
3
4
5
6
7
8
9
0080 | PUSH           | 256       <-- 压入数字 256
0081 | MUL | ... <-- 将栈顶的 Y 与 256 相乘 (算出 Y * 256)
0082 | LOAD_MEM | 1 <-- 从内存位置 1 读取你输入的 X 坐标
0083 | ADD | ... <-- 相加 (算出 Y * 256 + X)
0084 | PUSH | 4096 <-- 压入数字 4096 (这是网格在 4MB 内存中的起始偏移量)
0085 | ADD | ... <-- 相加 (算出最终内存地址:Y * 256 + X + 4096)
0086 | STORE_MEM | 3 <-- 把这个算出来的地址临时存起来
...
0088 | LOAD_FROM_ADDR | ... <-- 拿着这个地址去读内存,看这个格子里是不是已经有东西了

可以知道这实际上是一个在 4MB 内存中维护的 256x256 棋盘网格

  • 初始盲盒:利用 Xorshift32 伪随机算法(基于之前不可预测的 Seed),生成两个 [-4, 3] 之间的偏移量 dxdy 。并在棋盘上强行放置两个皇后:
    • Q1: (X = dy + 129, Y = dx + 127)
    • Q2: (X = dy + 45, Y = dx + 23)
  • 交互输入:通过 CALL_SYS 循环 254 次读取标准输入,要求玩家输入剩下的 254 个坐标。
  • 终极校验:遍历全部的行、列、正对角线、副对角线。如果任意一条线上的元素和大于 1,则输出 incorrect! 并断开连接。全部通过则输出 Flag。

这正是经典的 N 皇后问题 (N=256)

我们使用最小冲突局部搜索算法写出求解脚本 (solve.py)

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
import random
import time

def solve_n_queens():
N = 256

locked = {127: 129, 23: 45}

while True:
# 用一维数组维护每列、主对角线、副对角线上的皇后数量
cols = [0] * N
diag1 = [0] * (2 * N)
diag2 = [0] * (2 * N)
state = [0] * N # 记录每行(Y)皇后的列(X)坐标

def add_queen(r, c, val):
cols[c] += val
diag1[r - c + N] += val
diag2[r + c] += val

def get_conflicts(r, c):
return cols[c] + diag1[r - c + N] + diag2[r + c]


for r in range(N):
c = locked[r] if r in locked else random.randint(0, N - 1)
state[r] = c
add_queen(r, c, 1)


steps = 0
success = False
for _ in range(5000):

bad_rows = [r for r in range(N) if r not in locked and get_conflicts(r, state[r]) > 3]

if not bad_rows:
success = True
break


r = random.choice(bad_rows)
c_old = state[r]
add_queen(r, c_old, -1)


min_conf = float('inf')
candidates = []
for c in range(N):
conf = get_conflicts(r, c)
if conf < min_conf:
min_conf = conf
candidates = [c]
elif conf == min_conf:
candidates.append(c)

c_new = random.choice(candidates)
state[r] = c_new
add_queen(r, c_new, 1)
steps += 1

if success:
return state


start_time = time.time()
solution = solve_n_queens()


payload = ""
for y in range(256):
if y in [127, 23]:
continue

payload += f"{solution[y]} {y}\n"

with open("payload.txt", "w") as f:
f.write(payload)
print("[+] Payload 已保存至 payload.txt")

运行得到 payload.txt 为

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

虽然我们无法预知服务器的 dxdy , 但突破口在于 Docker 环境的 run.sh 中使用了一个 while true 死循环。这意味着如果输入错误导致程序断开,服务会瞬间重启并刷新一组全新的随机数种子。

因为 dxdy 的取值范围仅为 [-4, 3] ,所以总棋盘形态只有 8 * 8 = 64 种! 我们只需将上一步假定 dx=0, dy=0 算出的 254 对坐标作为唯一载荷,利用 Pwntools 对服务器进行暴力求解从而击穿防御。

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
from pwn import *
import sys


try:
with open("payload.txt", "r") as f:
payload = f.read().encode()
except FileNotFoundError:
log.error("未找到 payload.txt,请确保坐标文件在同一目录下!")
sys.exit(1)

log.info(f"已成功加载 Payload,大小: {len(payload)} 字节")


host = 'wandering.ctf.theromanxpl0.it'
port = 9091
p = remote(host, port)


attempts = 0
max_attempts = 150

while attempts < max_attempts:
attempts += 1

sys.stdout.write(f"\r[*] 正在进行第 {attempts} 次盲打投递 (1/64 命中率)...")
sys.stdout.flush()


p.send(payload)

try:

res = p.recvline(timeout=2)

if b"incorrect" in res:

continue
elif b"correct" in res:

print("\n")
log.success("🎉 命中正确 Seed,防御已击穿!")
log.success(f"服务器返回: {res.decode('utf-8', errors='ignore').strip()}")


log.info("正在拉取 Flag...")
flag = p.recvall(timeout=3)
print("-" * 40)
print(flag.decode('utf-8', errors='ignore').strip())
print("-" * 40)
break
else:

print("\n")
log.warning(f"收到意外响应: {res}")

except EOFError:
print("\n")
log.error("连接因为 60 秒超时被 socat 掐断,正在重新连接...")
p.close()
p = remote(host, port)

if attempts >= max_attempts:
print("\n")
log.failure("达到最大尝试次数,仍未命中,请检查网络或 Payload。")

# 1+1

题目给了 3 个附件:1,+1,2

通过简单的判断可以知道:

  • 1: 一个很小的 ELF 文件
  • +1: 纯数据
  • 2: 一串十六进制文本

因此首先逆 ELF 文件,将其拖进 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
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
start:                                  ; DATA XREF: LOAD:0000000000400018↑o
LOAD:0000000000400078 mov rdi, [rsp]
LOAD:000000000040007C cmp rdi, 2
LOAD:0000000000400080 jl loc_40019C
LOAD:0000000000400086 mov rdi, [rsp+10h]
LOAD:000000000040008B mov eax, 2
LOAD:0000000000400090 xor rsi, rsi
LOAD:0000000000400093 xor rdx, rdx
LOAD:0000000000400096 syscall ; LINUX - sys_open
LOAD:0000000000400098 test rax, rax
LOAD:000000000040009B js loc_40019C
LOAD:00000000004000A1 mov r12, rax
LOAD:00000000004000A4 sub rsp, 90h
LOAD:00000000004000AB mov rdi, r12
LOAD:00000000004000AE mov rsi, rsp
LOAD:00000000004000B1 mov eax, 5
LOAD:00000000004000B6 syscall ; LINUX - sys_fstat
LOAD:00000000004000B8 mov r13, [rsp+30h]
LOAD:00000000004000BD add rsp, 90h
LOAD:00000000004000C4 sub rsp, r13
LOAD:00000000004000C7 lea r14, [rsp]
LOAD:00000000004000CB xor r15, r15
LOAD:00000000004000CE
LOAD:00000000004000CE loc_4000CE: ; CODE XREF: LOAD:00000000004000ED↓j
LOAD:00000000004000CE cmp r15, r13
LOAD:00000000004000D1 jge short loc_4000EF
LOAD:00000000004000D3 xor rax, rax
LOAD:00000000004000D6 mov rdi, r12
LOAD:00000000004000D9 lea rsi, [r14+r15]
LOAD:00000000004000DD mov rdx, r13
LOAD:00000000004000E0 sub rdx, r15
LOAD:00000000004000E3 syscall ; LINUX - sys_read
LOAD:00000000004000E5 test rax, rax
LOAD:00000000004000E8 jle short loc_4000EF
LOAD:00000000004000EA add r15, rax
LOAD:00000000004000ED jmp short loc_4000CE
LOAD:00000000004000EF ; ---------------------------------------------------------------------------
LOAD:00000000004000EF
LOAD:00000000004000EF loc_4000EF: ; CODE XREF: LOAD:00000000004000D1↑j
LOAD:00000000004000EF ; LOAD:00000000004000E8↑j
LOAD:00000000004000EF mov eax, 3
LOAD:00000000004000F4 mov rdi, r12
LOAD:00000000004000F7 syscall ; LINUX - sys_close
LOAD:00000000004000F9 shr r13, 3
LOAD:00000000004000FD xor r15, r15
LOAD:0000000000400100
LOAD:0000000000400100 loc_400100: ; CODE XREF: LOAD:0000000000400187↓j
LOAD:0000000000400100 ; LOAD:0000000000400190↓j
LOAD:0000000000400100 test r15, r15 ; r14是虚拟机的内存基址,也就是+1文 件被读入栈后的起始地址;r15是虚拟机的指令指针
LOAD:0000000000400103 js loc_400195
LOAD:0000000000400109 cmp r15, r13 ; r13是内存上限,用来做越界保护
LOAD:000000000040010C jge loc_400195
LOAD:0000000000400112 mov rax, [r14+r15*8] ; 取出A
LOAD:0000000000400116 mov rbx, [r14+r15*8+8] ; 取出B
LOAD:000000000040011B mov rcx, [r14+r15*8+10h] ; 取出C
LOAD:0000000000400120 cmp rax, 0FFFFFFFFFFFFFFFFh ; A == -1 ?
LOAD:0000000000400124 jz short loc_40013D
LOAD:0000000000400126 cmp rax, 0FFFFFFFFFFFFFFFEh ; A == -2 ?
LOAD:000000000040012A jz short loc_400162
LOAD:000000000040012C mov rdx, [r14+rax*8] ; rdx = Memory[A]
LOAD:0000000000400130 add [r14+rbx*8], rdx ; Memory[B] = Memory[B] + Memory[A]
LOAD:0000000000400134 cmp qword ptr [r14+rbx*8], 0 ; 判断相加后的结果
LOAD:0000000000400139 jg short loc_40018C ; jg全称Jump if Greater,如果 > 0,跳到40018C
LOAD:000000000040013B jmp short loc_400184 ; 如果 <= 0,跳到400184
LOAD:000000000040013D ; ---------------------------------------------------------------------------
LOAD:000000000040013D
LOAD:000000000040013D loc_40013D: ; CODE XREF: LOAD:0000000000400124↑j
LOAD:000000000040013D sub rsp, 8
LOAD:0000000000400141 xor rax, rax
LOAD:0000000000400144 xor rdi, rdi
LOAD:0000000000400147 mov rsi, rsp
LOAD:000000000040014A mov edx, 1
LOAD:000000000040014F push rcx
LOAD:0000000000400150 syscall ; LINUX - sys_read
LOAD:0000000000400152 pop rcx
LOAD:0000000000400153 movzx rax, byte ptr [rsp]
LOAD:0000000000400158 mov [r14+rbx*8], rax ; Memory[B] = gerchar()
LOAD:000000000040015C add rsp, 8
LOAD:0000000000400160 jmp short loc_400184 ; IP = C
LOAD:0000000000400162 ; ---------------------------------------------------------------------------
LOAD:0000000000400162
LOAD:0000000000400162 loc_400162: ; CODE XREF: LOAD:000000000040012A↑j
LOAD:0000000000400162 push rcx
LOAD:0000000000400163 mov rdi, [r14+rbx*8]
LOAD:0000000000400167 call sub_4001B8 ; 打印Memory[B]的十六进制值
LOAD:000000000040016C push 20h ; ' ' ; 调用sys_write打印一个空格(20h)
LOAD:000000000040016E mov eax, 1
LOAD:0000000000400173 mov edi, 1
LOAD:0000000000400178 mov rsi, rsp
LOAD:000000000040017B mov edx, 1
LOAD:0000000000400180 syscall ; LINUX - sys_write
LOAD:0000000000400182 pop rax
LOAD:0000000000400183 pop rcx
LOAD:0000000000400184
LOAD:0000000000400184 loc_400184: ; CODE XREF: LOAD:000000000040013B↑j
LOAD:0000000000400184 ; LOAD:0000000000400160↑j
LOAD:0000000000400184 mov r15, rcx ; IP = C
LOAD:0000000000400187 jmp loc_400100
LOAD:000000000040018C ; ---------------------------------------------------------------------------
LOAD:000000000040018C
LOAD:000000000040018C loc_40018C: ; CODE XREF: LOAD:0000000000400139↑j
LOAD:000000000040018C add r15, 3 ; IP = IP + 3
LOAD:0000000000400190 jmp loc_400100

其核心逻辑可以表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while (1) {
a = mem[pc];
b = mem[pc + 1];
c = mem[pc + 2];

if (a == 0xffffffffffffffff) { // -1
mem[b] = getchar(); // 读 1 字节输入
pc = c;
}
else if (a == 0xfffffffffffffffe) { // -2
print_hex(mem[b]); // 输出一个十六进制数
pc = c;
}
else {
mem[b] = mem[b] + mem[a];
if ((int64_t)mem[b] > 0)
pc += 3;
else
pc = c;
}
}

这说明 1 其实只是一个极简 VM 解释器。

它的指令是三元组 (a, b, c)

  1. a == -1
    从标准输入读 1 个字节,写到 mem[b] ,然后跳到 c

  2. a == -2
    mem[b] 以十六进制输出,然后跳到 c

  3. 其他情况
    做:

    1
    mem[b] += mem[a];

    然后根据 mem[b]有符号值是否大于 0 决定下一条指令地址.

现在开始做差分实验;

首先写一个黑盒调用脚本 (oracle.py):

1
2
3
4
5
6
7
8
9
10
11
12
import subprocess

def run_prog(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip()
return out.split()

base = b"A" * 128
res = run_prog(base)

print("输出个数 =", len(res))
print("输出内容 =")
print(res)

运行得到:

1
2
3
输出个数 = 32
输出内容 =
['e45f4e2d', '608ed19d', 'e55bb98d', '7bf1dfd', '5b5a6bed', '195a3d5d', 'd06fe34d', 'a8507fbd', '5d95abad', 'f4b9d51d', '2fb0c50d', 'fe0d8b7d', 'b6c34f6d', '8b8104dd', 'b5114ecd', 'fb88a33d', '8402952d', '6b26e89d', '2ef7a08d', '42bdd4fd', 'af6af2ed', '8a7b945d', '75f10a4d', 'b3d076bd', 'f20372ad', 'cd546c1d', '17072c0d', '14fec27d', '8b7e566d', '84dbdd', 'b82cf5cd', 'cdb1a3d']

然后只改 1 个字节,看哪些输出会变 (diff1.py):

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
import subprocess

def run_prog(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip()
return out.split()

base = run_prog(b"A" * 128)

buf = bytearray(b"A" * 128)
buf[0] = ord("B") # 只把第 0 个字节从 A 改成 B
test = run_prog(bytes(buf))

print("base:")
print(base)
print()
print("test:")
print(test)
print()

changed = []
for i, (x, y) in enumerate(zip(base, test)):
if x != y:
changed.append(i)

print("changed word indexes:", changed)

运行得到

1
2
3
4
5
6
7
ase:
['e45f4e2d', '608ed19d', 'e55bb98d', '7bf1dfd', '5b5a6bed', '195a3d5d', 'd06fe34d', 'a8507fbd', '5d95abad', 'f4b9d51d', '2fb0c50d', 'fe0d8b7d', 'b6c34f6d', '8b8104dd', 'b5114ecd', 'fb88a33d', '8402952d', '6b26e89d', '2ef7a08d', '42bdd4fd', 'af6af2ed', '8a7b945d', '75f10a4d', 'b3d076bd', 'f20372ad', 'cd546c1d', '17072c0d', '14fec27d', '8b7e566d', '84dbdd', 'b82cf5cd', 'cdb1a3d']

test:
['e45f4e2d', '608ed19d', 'e55bb98d', '7bf1dfd', '5b5a6bed', '195a3d5d', 'd06fe34d', 'a8507fbd', '5d95abad', 'f4b9d51d', '2fb0c50d', 'fe0d8b7d', 'b6c34f6d', '8b8104dd', 'b5114ecd', 'fb88a33d', '8402952d', '6b26e89d', '2ef7a08d', '42bdd4fd', '802d5fb0', '453edc60', '522ac890', '5ed172c0', 'f912cff0', '341d9ea0', '38a86cd0', 'bc015700', '6a80f430', 'a56bb8e0', '1b80d10', '7f462d40']

changed word indexes: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

运行结果说明输入第 0 个字节,不是从一开始就参与校验的。它是在程序内部处理到某个较晚的位置时,才进入最终的滚动状态。

然后批量扫 128 个输入位置 (scan_positions.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import subprocess

def run_prog(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip()
return out.split()

base_inp = b"A" * 128
base_out = run_prog(base_inp)

for pos in range(128):
buf = bytearray(base_inp)
buf[pos] = ord("B")
out = run_prog(bytes(buf))

changed = [i for i, (x, y) in enumerate(zip(base_out, out)) if x != y]
first = changed[0] if changed else None
last = changed[-1] if changed else None

print(f"pos={pos:3d} changed_count={len(changed):2d} first={first} last={last}")

运行得到:

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
pos=  0 changed_count=12 first=20 last=31
pos= 1 changed_count=31 first=1 last=31
pos= 2 changed_count=22 first=10 last=31
pos= 3 changed_count= 7 first=25 last=31
pos= 4 changed_count= 8 first=24 last=31
pos= 5 changed_count= 3 first=29 last=31
pos= 6 changed_count=31 first=1 last=31
pos= 7 changed_count=32 first=0 last=31
pos= 8 changed_count=11 first=21 last=31
pos= 9 changed_count= 1 first=31 last=31
pos= 10 changed_count=15 first=17 last=31
pos= 11 changed_count=26 first=6 last=31
pos= 12 changed_count=21 first=11 last=31
pos= 13 changed_count=22 first=10 last=31
pos= 14 changed_count=13 first=19 last=31
pos= 15 changed_count= 7 first=25 last=31
pos= 16 changed_count= 2 first=30 last=31
pos= 17 changed_count=30 first=2 last=31
pos= 18 changed_count=30 first=2 last=31
pos= 19 changed_count= 4 first=28 last=31
pos= 20 changed_count=21 first=11 last=31
pos= 21 changed_count=20 first=12 last=31
pos= 22 changed_count=17 first=15 last=31
pos= 23 changed_count=29 first=3 last=31
pos= 24 changed_count=24 first=8 last=31
pos= 25 changed_count=27 first=5 last=31
pos= 26 changed_count=13 first=19 last=31
pos= 27 changed_count=24 first=8 last=31
pos= 28 changed_count=12 first=20 last=31
pos= 29 changed_count= 2 first=30 last=31
pos= 30 changed_count= 1 first=31 last=31
pos= 31 changed_count=16 first=16 last=31
pos= 32 changed_count=31 first=1 last=31
pos= 33 changed_count=23 first=9 last=31
pos= 34 changed_count=10 first=22 last=31
pos= 35 changed_count=19 first=13 last=31
pos= 36 changed_count=25 first=7 last=31
pos= 37 changed_count=19 first=13 last=31
pos= 38 changed_count= 7 first=25 last=31
pos= 39 changed_count=29 first=3 last=31
pos= 40 changed_count= 9 first=23 last=31
pos= 41 changed_count=12 first=20 last=31
pos= 42 changed_count=23 first=9 last=31
pos= 43 changed_count= 3 first=29 last=31
pos= 44 changed_count= 9 first=23 last=31
pos= 45 changed_count= 2 first=30 last=31
pos= 46 changed_count= 9 first=23 last=31
pos= 47 changed_count=27 first=5 last=31
pos= 48 changed_count=17 first=15 last=31
pos= 49 changed_count=16 first=16 last=31
pos= 50 changed_count= 4 first=28 last=31
pos= 51 changed_count=27 first=5 last=31
pos= 52 changed_count=28 first=4 last=31
pos= 53 changed_count=28 first=4 last=31
pos= 54 changed_count=23 first=9 last=31
pos= 55 changed_count= 5 first=27 last=31
pos= 56 changed_count=30 first=2 last=31
pos= 57 changed_count= 2 first=30 last=31
pos= 58 changed_count= 5 first=27 last=31
pos= 59 changed_count= 3 first=29 last=31
pos= 60 changed_count=18 first=14 last=31
pos= 61 changed_count=26 first=6 last=31
pos= 62 changed_count=27 first=5 last=31
pos= 63 changed_count=16 first=16 last=31
pos= 64 changed_count=11 first=21 last=31
pos= 65 changed_count=12 first=20 last=31
pos= 66 changed_count=14 first=18 last=31
pos= 67 changed_count= 4 first=28 last=31
pos= 68 changed_count=26 first=6 last=31
pos= 69 changed_count=17 first=15 last=31
pos= 70 changed_count= 8 first=24 last=31
pos= 71 changed_count=26 first=6 last=31
pos= 72 changed_count= 4 first=28 last=31
pos= 73 changed_count= 8 first=24 last=31
pos= 74 changed_count=32 first=0 last=31
pos= 75 changed_count=24 first=8 last=31
pos= 76 changed_count=20 first=12 last=31
pos= 77 changed_count= 8 first=24 last=31
pos= 78 changed_count=22 first=10 last=31
pos= 79 changed_count=18 first=14 last=31
pos= 80 changed_count=25 first=7 last=31
pos= 81 changed_count=19 first=13 last=31
pos= 82 changed_count= 6 first=26 last=31
pos= 83 changed_count= 1 first=31 last=31
pos= 84 changed_count=17 first=15 last=31
pos= 85 changed_count=13 first=19 last=31
pos= 86 changed_count=10 first=22 last=31
pos= 87 changed_count=18 first=14 last=31
pos= 88 changed_count=32 first=0 last=31
pos= 89 changed_count=13 first=19 last=31
pos= 90 changed_count=30 first=2 last=31
pos= 91 changed_count=14 first=18 last=31
pos= 92 changed_count= 6 first=26 last=31
pos= 93 changed_count=18 first=14 last=31
pos= 94 changed_count=29 first=3 last=31
pos= 95 changed_count=19 first=13 last=31
pos= 96 changed_count= 5 first=27 last=31
pos= 97 changed_count= 1 first=31 last=31
pos= 98 changed_count=10 first=22 last=31
pos= 99 changed_count= 9 first=23 last=31
pos=100 changed_count=24 first=8 last=31
pos=101 changed_count= 5 first=27 last=31
pos=102 changed_count=20 first=12 last=31
pos=103 changed_count=28 first=4 last=31
pos=104 changed_count=15 first=17 last=31
pos=105 changed_count=23 first=9 last=31
pos=106 changed_count=29 first=3 last=31
pos=107 changed_count=14 first=18 last=31
pos=108 changed_count=20 first=12 last=31
pos=109 changed_count=15 first=17 last=31
pos=110 changed_count=14 first=18 last=31
pos=111 changed_count=28 first=4 last=31
pos=112 changed_count=25 first=7 last=31
pos=113 changed_count=25 first=7 last=31
pos=114 changed_count=31 first=1 last=31
pos=115 changed_count=21 first=11 last=31
pos=116 changed_count=15 first=17 last=31
pos=117 changed_count=10 first=22 last=31
pos=118 changed_count=11 first=21 last=31
pos=119 changed_count=21 first=11 last=31
pos=120 changed_count= 7 first=25 last=31
pos=121 changed_count=16 first=16 last=31
pos=122 changed_count= 6 first=26 last=31
pos=123 changed_count= 3 first=29 last=31
pos=124 changed_count=32 first=0 last=31
pos=125 changed_count=22 first=10 last=31
pos=126 changed_count=11 first=21 last=31
pos=127 changed_count= 6 first=26 last=31

通过运行结果可以看出两个规律:

  • 某个输入字节一旦从第 first 个输出 word 开始进入最终状态更新,它就会一直影响后面所有 word,直到第 31 个。所以最后一定是个 “滚动状态”,不是独立逐块比较。
  • 每个 first 值都正好出现 4 次

将运行结果整理成一张表 (groups.py):

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
groups = {
0: [7, 74, 88, 124],
1: [1, 6, 32, 114],
2: [17, 18, 56, 90],
3: [23, 39, 94, 106],
4: [52, 53, 103, 111],
5: [25, 47, 51, 62],
6: [11, 61, 68, 71],
7: [36, 80, 112, 113],
8: [24, 27, 75, 100],
9: [33, 42, 54, 105],
10: [2, 13, 78, 125],
11: [12, 20, 115, 119],
12: [21, 76, 102, 108],
13: [35, 37, 81, 95],
14: [60, 79, 87, 93],
15: [22, 48, 69, 84],
16: [31, 49, 63, 121],
17: [10, 104, 109, 116],
18: [66, 91, 107, 110],
19: [14, 26, 85, 89],
20: [0, 28, 41, 65],
21: [8, 64, 118, 126],
22: [34, 86, 98, 117],
23: [40, 44, 46, 99],
24: [4, 70, 73, 77],
25: [3, 15, 38, 120],
26: [82, 92, 122, 127],
27: [55, 58, 96, 101],
28: [19, 50, 67, 72],
29: [5, 43, 59, 123],
30: [16, 29, 45, 57],
31: [9, 30, 83, 97],
}

然后只盯住 1 组,搞清楚组内顺序 (word 31 最适合),新建 test_word31.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess

def run_prog(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip()
return out.split()

base = b"A" * 128

for pos in [9, 30, 83, 97]:
buf = bytearray(base)
buf[pos] = ord("B")
out = run_prog(bytes(buf))
print(f"pos={pos}, last_word={out[31]}")

运行得到:

1
2
3
4
pos=9, last_word=cdb1abc
pos=30, last_word=a49a1b5c
pos=83, last_word=155c4180
pos=97, last_word=8c7313e0

+1 末尾常量里已经能看到:

  • 0x0a1b2c3d
  • 0x13255
  • 0xffffffff

这非常像一个 32 位 rolling state。把它写成函数就是:

1
2
3
4
def upd(s, b):
s = (s * 0x13255 + ((s & 0xff) << 24)) & 0xffffffff
s ^= b
return s

现在根据上面的公式 跑一个 “只解最后一组” 的小脚本 (solve_word31.py):

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
import itertools
import math


base_last = int("cdb1a3d", 16)
observed = {
9: int("cdb1abc", 16),
30: int("a49a1b5c", 16),
83: int("155c4180", 16),
97: int("8c7313e0", 16),
}

def upd(s, b):
s = (s * 0x13255 + ((s & 0xff) << 24)) & 0xffffffff
s ^= b
return s

def inv_upd_unique(out, b):
"""
已知 out = upd(prev, b),倒推出唯一的 prev
"""
t = out ^ b
Acoef = (0x13255 << 8) & 0xffffffff

for low in range(256):
rhs = (t - ((low << 24) + low * 0x13255)) & 0xffffffff

g = math.gcd(Acoef, 2**32)
if rhs % g != 0:
continue

mod = 2**32 // g
a = Acoef // g
bb = rhs // g
inva = pow(a, -1, mod)
q = (bb * inva) % mod

if q < (1 << 24):
prev = ((q << 8) | low) & 0xffffffff
if upd(prev, b) == out:
return prev

return None


for a_byte in range(256):

st = base_last
ok = True
for _ in range(4):
st = inv_upd_unique(st, a_byte)
if st is None:
ok = False
break
if not ok:
continue

pre_state = st

for b_byte in range(256):
if b_byte == a_byte:
continue


slot_outs = []
for slot in range(4):
s = pre_state
for i in range(4):
s = upd(s, b_byte if i == slot else a_byte)
slot_outs.append(s)


if set(slot_outs) == set(observed.values()) and len(set(slot_outs)) == 4:

slot_by_out = {v: i for i, v in enumerate(slot_outs)}


order = [None] * 4
for pos, val in observed.items():
order[slot_by_out[val]] = pos

print("[+] 找到唯一匹配")
print("进入最后状态机时: A ->", hex(a_byte), "B ->", hex(b_byte))
print("word31 组内顺序(从早到晚):", order)
break
else:
continue
break

运行得到

1
2
3
[+] 找到唯一匹配
进入最后状态机时: A -> 0xc6 B -> 0x47
word31 组内顺序(从早到晚): [83, 30, 97, 9]

通过脚本得到 128 字节内部顺序表 (proc_order.py):

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
import subprocess
import sys

SEED = 0x0A1B2C3D
A_map = 0xC6
B_map = 0x47


groups = {
0: [7, 74, 88, 124],
1: [1, 6, 32, 114],
2: [17, 18, 56, 90],
3: [23, 39, 94, 106],
4: [52, 53, 103, 111],
5: [25, 47, 51, 62],
6: [11, 61, 68, 71],
7: [36, 80, 112, 113],
8: [24, 27, 75, 100],
9: [33, 42, 54, 105],
10: [2, 13, 78, 125],
11: [12, 20, 115, 119],
12: [21, 76, 102, 108],
13: [35, 37, 81, 95],
14: [60, 79, 87, 93],
15: [22, 48, 69, 84],
16: [31, 49, 63, 121],
17: [10, 104, 109, 116],
18: [66, 91, 107, 110],
19: [14, 26, 85, 89],
20: [0, 28, 41, 65],
21: [8, 64, 118, 126],
22: [34, 86, 98, 117],
23: [40, 44, 46, 99],
24: [4, 70, 73, 77],
25: [3, 15, 38, 120],
26: [82, 92, 122, 127],
27: [55, 58, 96, 101],
28: [19, 50, 67, 72],
29: [5, 43, 59, 123],
30: [16, 29, 45, 57],
31: [9, 30, 83, 97],
}

def oracle(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip().split()
return [int(x, 16) for x in out]

def upd(s, b):
s = (s * 0x13255 + ((s & 0xff) << 24)) & 0xffffffff
s ^= b
return s

base = b"A" * 128
print("[*] running base...", flush=True)
base_out = oracle(base)
print("[+] base done", flush=True)


group_of_pos = {}
for g, arr in groups.items():
for pos in arr:
group_of_pos[pos] = g

needed_val = {}

print("[*] collecting 128 per-position outputs...", flush=True)
for pos in range(128):
buf = bytearray(base)
buf[pos] = ord("B")
out = oracle(bytes(buf))
g = group_of_pos[pos]
needed_val[pos] = out[g]

if pos % 8 == 0:
print(f"[+] done pos {pos}/127", flush=True)

print("[+] all position outputs collected", flush=True)

proc_order = [None] * 128

print("[*] reconstructing group orders...", flush=True)
for g in range(32):
pre = SEED if g == 0 else base_out[g - 1]

slot_value = {}
for slot in range(4):
s = pre
for i in range(4):
s = upd(s, B_map if i == slot else A_map)
slot_value[s] = slot

order = [None] * 4
for pos in groups[g]:
val = needed_val[pos]
if val not in slot_value:
print(f"[!] group {g}, pos {pos}, value={val:08x} not matched", flush=True)
sys.exit(1)
slot = slot_value[val]
order[slot] = pos

print(f"[+] group {g:2d} order = {order}", flush=True)

for slot in range(4):
proc_order[4 * g + slot] = order[slot]

print()
print("[+] proc_order =")
print(proc_order)

运行得到:

1
2
[+] proc_order =
[88, 7, 124, 74, 114, 6, 32, 1, 17, 18, 56, 90, 23, 106, 39, 94, 52, 111, 103, 53, 25, 62, 51, 47, 61, 68, 11, 71, 113, 80, 36, 112, 24, 75, 27, 100, 42, 54, 105, 33, 13, 2, 125, 78, 12, 20, 119, 115, 108, 76, 102, 21, 95, 37, 35, 81, 79, 87, 60, 93, 84, 69, 48, 22, 49, 121, 31, 63, 10, 116, 109, 104, 91, 107, 110, 66, 85, 26, 89, 14, 0, 28, 65, 41, 126, 64, 8, 118, 98, 117, 86, 34, 40, 44, 46, 99, 70, 77, 73, 4, 38, 3, 120, 15, 122, 127, 92, 82, 58, 96, 101, 55, 50, 72, 19, 67, 59, 43, 123, 5, 45, 16, 29, 57, 83, 30, 97, 9]

然后再把 map_byte 倒出来 (map_byte.py):

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
import subprocess

SEED = 0x0A1B2C3D
A_map = 0xC6
proc_order = [88, 7, 124, 74, 114, 6, 32, 1, 17, 18, 56, 90, 23, 106, 39, 94, 52, 111, 103, 53, 25, 62, 51, 47, 61, 68, 11, 71, 113, 80, 36, 112, 24, 75, 27, 100, 42, 54, 105, 33, 13, 2, 125, 78, 12, 20, 119, 115, 108, 76, 102, 21, 95, 37, 35, 81, 79, 87, 60, 93, 84, 69, 48, 22, 49, 121, 31, 63, 10, 116, 109, 104, 91, 107, 110, 66, 85, 26, 89, 14, 0, 28, 65, 41, 126, 64, 8, 118, 98, 117, 86, 34, 40, 44, 46, 99, 70, 77, 73, 4, 38, 3, 120, 15, 122, 127, 92, 82, 58, 96, 101, 55, 50, 72, 19, 67, 59, 43, 123, 5, 45, 16, 29, 57, 83, 30, 97, 9]

def oracle(inp: bytes):
out = subprocess.check_output(["./1", "./+1"], input=inp).decode().strip().split()
return [int(x, 16) for x in out]

def upd(s, b):
s = (s * 0x13255 + ((s & 0xff) << 24)) & 0xffffffff
s ^= b
return s

base = b"A" * 128
base_out = oracle(base)


pre31 = base_out[30]
s3 = upd(upd(upd(pre31, A_map), A_map), A_map)
const = (s3 * 0x13255 + ((s3 & 0xff) << 24)) & 0xffffffff

map_byte = [0] * 256
for c in range(256):
buf = bytearray(base)
buf[9] = c
out = oracle(bytes(buf))
map_byte[c] = out[31] ^ const
if c % 32 == 0:
print(f"[+] done {c}/255", flush=True)

print()
print("[+] map_byte =")
print(map_byte)
print("[+] len(set(map_byte)) =", len(set(map_byte)))
print("[+] A ->", hex(map_byte[ord('A')]))
print("[+] B ->", hex(map_byte[ord('B')]))

运行得到:

1
2
[+] map_byte =
[240, 104, 220, 135, 118, 219, 152, 187, 244, 210, 214, 70, 1, 107, 86, 55, 134, 48, 170, 12, 16, 162, 231, 253, 150, 106, 58, 33, 243, 238, 125, 21, 184, 127, 4, 37, 81, 5, 84, 126, 79, 62, 229, 251, 239, 74, 68, 99, 114, 78, 121, 199, 3, 50, 6, 91, 248, 105, 241, 149, 109, 157, 69, 176, 111, 198, 71, 190, 53, 97, 209, 161, 254, 35, 193, 205, 128, 28, 160, 65, 177, 15, 222, 32, 188, 181, 203, 171, 197, 174, 155, 20, 26, 165, 46, 227, 103, 29, 19, 54, 80, 200, 130, 59, 217, 169, 252, 66, 77, 158, 13, 93, 52, 226, 36, 223, 56, 147, 67, 255, 10, 51, 202, 225, 115, 192, 110, 131, 34, 17, 124, 22, 83, 247, 23, 133, 212, 233, 27, 47, 94, 173, 41, 117, 159, 113, 57, 101, 9, 230, 76, 95, 73, 137, 42, 136, 143, 39, 123, 7, 175, 235, 64, 142, 246, 148, 120, 245, 14, 180, 183, 90, 201, 92, 88, 218, 242, 213, 194, 98, 108, 141, 153, 185, 146, 204, 122, 211, 63, 82, 2, 228, 167, 145, 236, 182, 100, 45, 75, 166, 89, 250, 25, 215, 232, 216, 138, 30, 139, 151, 195, 208, 24, 189, 186, 164, 49, 61, 191, 234, 129, 221, 156, 172, 96, 249, 140, 178, 87, 31, 18, 85, 40, 196, 8, 0, 38, 112, 207, 132, 144, 224, 72, 237, 206, 154, 102, 116, 43, 11, 168, 119, 179, 60, 163, 44]

有了上述条件后,直接写出恢复 flag 的脚本 (recover_flag.py):

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
import math
import subprocess
import string

SEED = 0x0A1B2C3D

proc_order = [
88, 7, 124, 74, 114, 6, 32, 1, 17, 18, 56, 90, 23, 106, 39, 94,
52, 111, 103, 53, 25, 62, 51, 47, 61, 68, 11, 71, 113, 80, 36, 112,
24, 75, 27, 100, 42, 54, 105, 33, 13, 2, 125, 78, 12, 20, 119, 115,
108, 76, 102, 21, 95, 37, 35, 81, 79, 87, 60, 93, 84, 69, 48, 22,
49, 121, 31, 63, 10, 116, 109, 104, 91, 107, 110, 66, 85, 26, 89, 14,
0, 28, 65, 41, 126, 64, 8, 118, 98, 117, 86, 34, 40, 44, 46, 99,
70, 77, 73, 4, 38, 3, 120, 15, 122, 127, 92, 82, 58, 96, 101, 55,
50, 72, 19, 67, 59, 43, 123, 5, 45, 16, 29, 57, 83, 30, 97, 9
]

map_byte = [
240, 104, 220, 135, 118, 219, 152, 187, 244, 210, 214, 70, 1, 107, 86, 55,
134, 48, 170, 12, 16, 162, 231, 253, 150, 106, 58, 33, 243, 238, 125, 21,
184, 127, 4, 37, 81, 5, 84, 126, 79, 62, 229, 251, 239, 74, 68, 99,
114, 78, 121, 199, 3, 50, 6, 91, 248, 105, 241, 149, 109, 157, 69, 176,
111, 198, 71, 190, 53, 97, 209, 161, 254, 35, 193, 205, 128, 28, 160, 65,
177, 15, 222, 32, 188, 181, 203, 171, 197, 174, 155, 20, 26, 165, 46, 227,
103, 29, 19, 54, 80, 200, 130, 59, 217, 169, 252, 66, 77, 158, 13, 93,
52, 226, 36, 223, 56, 147, 67, 255, 10, 51, 202, 225, 115, 192, 110, 131,
34, 17, 124, 22, 83, 247, 23, 133, 212, 233, 27, 47, 94, 173, 41, 117,
159, 113, 57, 101, 9, 230, 76, 95, 73, 137, 42, 136, 143, 39, 123, 7,
175, 235, 64, 142, 246, 148, 120, 245, 14, 180, 183, 90, 201, 92, 88, 218,
242, 213, 194, 98, 108, 141, 153, 185, 146, 204, 122, 211, 63, 82, 2, 228,
167, 145, 236, 182, 100, 45, 75, 166, 89, 250, 25, 215, 232, 216, 138, 30,
139, 151, 195, 208, 24, 189, 186, 164, 49, 61, 191, 234, 129, 221, 156, 172,
96, 249, 140, 178, 87, 31, 18, 85, 40, 196, 8, 0, 38, 112, 207, 132,
144, 224, 72, 237, 206, 154, 102, 116, 43, 11, 168, 119, 179, 60, 163, 44
]

def upd(s, b):
s = (s * 0x13255 + ((s & 0xff) << 24)) & 0xffffffff
s ^= b
return s

def inv_upd_unique(out, b):
t = out ^ b
Acoef = (0x13255 << 8) & 0xffffffff

for low in range(256):
rhs = (t - ((low << 24) + low * 0x13255)) & 0xffffffff

g = math.gcd(Acoef, 2**32)
if rhs % g != 0:
continue

mod = (2**32) // g
a = Acoef // g
bb = rhs // g
inva = pow(a, -1, mod)
q = (bb * inva) % mod

if q < (1 << 24):
prev = ((q << 8) | low) & 0xffffffff
if upd(prev, b) == out:
return prev

raise ValueError(f"no inverse for out={out:08x}, b={b:02x}")

target = [int(x, 16) for x in open("2", "r").read().strip().split()]
assert len(target) == 32

# internal -> original
inv_map = {}
for x in range(256):
inv_map[map_byte[x]] = x
assert len(inv_map) == 256

GOOD = set((string.ascii_letters + string.digits + "_{}:/.-<>").encode())
FIXED = {
0: ord('T'),
1: ord('R'),
2: ord('X'),
3: ord('{'),
127: ord('}'),
}

def cand_score(g, cand):

score = 0
pos4 = [proc_order[4*g + i] for i in range(4)]
orig4 = [inv_map[b] for b in cand]

for pos, ch in zip(pos4, orig4):

if not (32 <= ch <= 126):
return -10**9

score += 10

if ch in GOOD:
score += 5

if pos in FIXED:
if ch != FIXED[pos]:
return -10**9
score += 100

return score

all_group_choices = []

for g in range(32):
pre = SEED if g == 0 else target[g - 1]
post = target[g]

forward = {}
for b0 in range(256):
s1 = upd(pre, b0)
for b1 in range(256):
s2 = upd(s1, b1)
forward.setdefault(s2, []).append((b0, b1))

cands = set()

for b3 in range(256):
s3 = inv_upd_unique(post, b3)
for b2 in range(256):
s2 = inv_upd_unique(s3, b2)
if s2 in forward:
for b0, b1 in forward[s2]:
cand = (b0, b1, b2, b3)
s = pre
for bb in cand:
s = upd(s, bb)
if s == post:
cands.add(cand)

cands = list(cands)
cands.sort(key=lambda c: cand_score(g, c), reverse=True)

print(f"[+] group {g:2d}: {len(cands)} candidates", flush=True)


for i, cand in enumerate(cands[:5]):
pos4 = [proc_order[4*g + j] for j in range(4)]
orig4 = [inv_map[b] for b in cand]
printable = ''.join(chr(x) if 32 <= x <= 126 else '.' for x in orig4)
print(f" top{i}: score={cand_score(g,cand):4d} pos={pos4} orig={orig4} str={printable}")

best = cands[0]
all_group_choices.append(best)

internal_stream = [0] * 128
for g, cand in enumerate(all_group_choices):
for i in range(4):
internal_stream[4*g + i] = cand[i]

orig = [0] * 128
for k, ib in enumerate(internal_stream):
pos = proc_order[k]
orig[pos] = inv_map[ib]

flag = bytes(orig)

print()
print("[+] recovered bytes:")
print(flag)

print("[+] recovered latin1:")
print(flag.decode('latin1'))


if all(32 <= b <= 126 for b in flag):
print("[+] recovered ascii:")
print(flag.decode('ascii'))


out = subprocess.check_output(["./1", "./+1"], input=flag).decode().strip().split()
assert [int(x, 16) for x in out] == target
print("[+] verification passed")

运行得到;

1
2
[+] recovered bytes:
b'TRX{you_really_did_it_you_can_do_additions_you_won_this_cookie_-->_http://youtube.com/post/UgkxZyfYqbQJRn86UCsEh8AZBnE8GVO-4fXI}'

从而得到 flag 为 TRX {you_really_did_it_you_can_do_additions_you_won_this_cookie_-->_http://youtube.com/post/UgkxZyfYqbQJRn86UCsEh8AZBnE8GVO-4fXI}