RCTF 2025 Reverse-Onion & Determinism WriteUp

13k 词

Onion

读取50个64位整数作为密钥,存储到0x5555555C3D00

vm字节码加载到0x5555555B5D00

参数结构:

1
2
3
4
5
6
7
8
9
10
unsigned __int8 *opcodes;
_QWORD reg[8];
_WORD MEM[256];
unsigned __int16 PC;
unsigned __int16 HIPC;
unsigned __int16 LOTAG;
unsigned __int16 HITAG;
unsigned __int16 STAKPC;
bool ZFLAG;
char ENDFLAG;

字节码种类:

字节码 参数 作用 等价于 长度
0x01 1个int16 无条件跳转 jmp 3
0x02 1个int16 !ZFLAG 时跳转 jz 3
0x03 1个int16 ZFLAG 时跳转 jnz 3
0xFF - 结束 exit 1
0x11 1个int16 给LOTAG赋值 mov 3
0x12 1个int16 给HITAG赋值 mov 3
0x15 1个int8 从LOTAG指向的文件地址处加载int64到寄存器A load_data 2
0x16 1个int8, 1个int64 将64位立即数加载到寄存器A load_imm 10
0x17 2个int8 mov regA, regB mov 3
0x18 1个int8, 1个int16 从LOTAG + imm16 指向的文件地址处加载int64到寄存器A load_data 4
0x19 1个int8 把寄存器A的值写入LOTAG指向的文件地址 write 2
0x1A 2个int8 从寄存器B + LOTAG指向的文件地址读取1字节到寄存器A load_data 3
0x1B 2个int8 将寄存器A的低字节写入寄存器B + LOTAG指向的文件地址 write 3
0x1C 1个int8 寄存器自增,如果结果为0则设置ZFLAG为true inc 2
0x1D 1个int8 寄存器自减,如果结果为0则设置ZFLAG为true dec 2
0x1E 2个int8 shr regA, imm8,如果结果为0则设置ZFLAG为true shr 3
0x1F 1个int8 将寄存器低 16 位加到LOTAG add_tag 2
0x25 2个int8 and regA, regB,如果结果为0则设置ZFLAG为true and 3
0x26 2个int8 xor regA, regB,如果结果为0则设置ZFLAG为true xor 3
0x27 2个int8 shl regA, imm8,如果结果为0则设置ZFLAG为true shl 3
0x29 1个int8, 1个int64 xor regA, imm64,如果结果为0则设置ZFLAG为true xor_imm 10
0x2A 1个int8, 1个int64 and regA, imm64,如果结果为0则设置ZFLAG为true and_imm 10
0x2B 2个int8 从寄存器B + HITAG指向的文件地址读取1字节到寄存器A load_data 3
0x2C 2个int8 将寄存器A的低字节写入寄存器B + HITAG指向的文件地址 write 3
0x32 1个int8, 1个int64 cmp regA, imm64,如果相等则设置ZFLAG为true cmp 10
0x80 - 将下一条指令地址保存到STAKPC save_ret_addr 1
0x81 1个int8 将STAKPC的值+3后存入参数索引指向的缓存 push_ret_addr 2
0x82 1个int8 将下一指令地址写入HIPC-2指向的文件地址并跳转 call 2
0x83 - 跳回返回地址,恢复HIPC ret 1
0x84 1个int8 将寄存器A的值HIPC-8指向的文件地址 pushq 2
0x85 1个int8 从HIPC指向的文件地址取出int64赋值给寄存器A,恢复HIPC popq 2
0x90 1个int8 输出1字节 printf 2

基本结构:

外层结构体:

1
2
3
4
5
6
7
8
9
10
VMOPCODE ptr
REGS 8 * qword
MEM 256 * word
LOPC word
HIPC word
LOTAG word
HITAG word
STAKPC word
ZFLAG bool
ENDFLAG bool

opcode内部前26497是文件,0xE000开始是50个8字节输入,0xFFF7处有一个倒着生长的栈(向上生长)

在代码中存在3个函数,0x104、0x116和0x2BE,0x104是校验未通过的退出函数,0x116和0x2BE是两个不同的加密函数

第一个check仅校验输入的第三个int64(0xE010):

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
0x0	jmp	0x100
0x3 jmp 0x5a9
0x100 svstk 0x101
0x101 jmp 0x110
0x104 stdout
0x106 stdout
0x108 stdout
0x10a stdout
0x10c stdout
0x10e exit
0x110 push mem[0x1] 0x104
0x112 svstk 0x113
0x113 jmp 0x2b8
0x2b8 push mem[0x10] 0x116
0x2ba svstk 0x2bb
0x2bb jmp 0x5a4
0x2be pushq code[0xfff5] reg[0]
0x2c0 pushq code[0xffed] reg[1]
0x2c2 pushq code[0xffe5] reg[2]
0x2c4 pushq code[0xffdd] reg[3]
0x2c6 pushq code[0xffd5] reg[4]
0x2c8 pushq code[0xffcd] reg[5]
0x2ca pushq code[0xffc5] reg[6]
0x2cc pushq code[0xffbd] reg[7]
0x2ce mov reg[1] reg[0]
0x2d1 shr reg[1] 0x20
0x2d4 and_imm reg[0] 0xffffffff
0x2de load code[0x7200] reg[2]
0x2e0 pushq code[0xffb5] reg[0]
0x2e2 mov_imm reg[0] 0x8
0x2ec add LOTAG regs[0]
0x2ee popq reg[0] code[0xffb5]
0x2f0 load code[0x7208] reg[3]
0x2f2 pushq code[0xffb5] reg[0]
0x2f4 mov_imm reg[0] 0x8
0x2fe pushq code[0xffad] reg[5]
0x300 mov_imm reg[5] 0x10
0x30a xor_imm reg[5] 0xffffffffffffffff
0x314 pushq code[0xffa5] reg[6]
0x316 pushq code[0xff9d] reg[7]
0x318 mov_imm reg[6] 0x1
0x322 mov reg[7] reg[5]
0x325 and reg[7] reg[6]
0x328 xor reg[5] reg[6]
0x32b cmp_imm reg[7] 0x0
0x335 jnz 0x341
0x338 shl reg[7] 0x1
0x33b mov reg[6] reg[7]
0x33e jmp 0x322
0x341 popq reg[7] code[0xff9d]
0x343 popq reg[6] code[0xffa5]
0x345 pushq code[0xffa5] reg[6]
0x347 pushq code[0xff9d] reg[7]
0x349 mov reg[6] reg[5]
0x34c mov reg[7] reg[0]
0x34f and reg[7] reg[6]
0x352 xor reg[0] reg[6]
0x355 cmp_imm reg[7] 0x0
0x35f jnz 0x36b
0x36b and_imm reg[0] 0xffffffffffffffff
0x375 popq reg[7] code[0xff9d]
0x377 popq reg[6] code[0xffa5]
0x379 and_imm reg[0] 0xffffffffffffffff
0x383 popq reg[5] code[0xffad]
0x385 add LOTAG regs[0]
0x387 popq reg[0] code[0xffb5]
0x389 mov reg[4] reg[2]
0x38c mov reg[5] reg[3]
0x38f and_imm reg[2] 0xffffffff
0x399 shr reg[4] 0x20
0x39c and_imm reg[3] 0xffffffff
0x3a6 shr reg[5] 0x20
0x3a9 pushq code[0xffb5] reg[7]
0x3ab mov reg[7] reg[3]
0x3ae mov reg[3] reg[4]
0x3b1 mov reg[4] reg[7]
0x3b4 popq reg[7] code[0xffb5]
0x3b6 mov_imm reg[6] 0x0
0x3c0 pushq code[0xffb5] reg[7]
0x3c2 mov reg[7] reg[0]
0x3c5 pushq code[0xffad] reg[6]
0x3c7 pushq code[0xffa5] reg[7]
0x3c9 mov reg[6] reg[7]
0x3cc mov reg[7] reg[7]
0x3cf shr reg[6] 0x8
0x3d2 shl reg[7] 0x18
0x3d5 xor reg[6] reg[7]
0x3d8 and_imm reg[6] 0xffffffff
0x3e2 popq reg[7] code[0xffa5]
0x3e4 mov reg[7] reg[6]
0x3e7 popq reg[6] code[0xffad]
0x3e9 mov reg[0] reg[7]
0x3ec pushq code[0xffad] reg[6]
0x3ee pushq code[0xffa5] reg[7]
0x3f0 mov reg[6] reg[1]
0x3f3 mov reg[7] reg[0]
0x3f6 and reg[7] reg[6]
0x3f9 xor reg[0] reg[6]
0x3fc cmp_imm reg[7] 0x0
0x406 jnz 0x412
0x409 shl reg[7] 0x1
0x40c mov reg[6] reg[7]
0x40f jmp 0x3f3
0x412 and_imm reg[0] 0xffffffffffffffff
0x41c popq reg[7] code[0xffa5]
0x41e popq reg[6] code[0xffad]
0x420 and_imm reg[0] 0xffffffff
0x42a xor reg[0] reg[2]
0x42d and_imm reg[0] 0xffffffff
0x437 mov reg[7] reg[1]
0x43a pushq code[0xffad] reg[6]
0x43c pushq code[0xffa5] reg[7]
0x43e mov reg[6] reg[7]
0x441 mov reg[7] reg[7]
0x444 shl reg[6] 0x3
0x447 shr reg[7] 0x1d
0x44a xor reg[6] reg[7]
0x44d and_imm reg[6] 0xffffffff
0x457 popq reg[7] code[0xffa5]
0x459 mov reg[7] reg[6]
0x45c popq reg[6] code[0xffad]
0x45e mov reg[1] reg[7]
0x461 xor reg[1] reg[0]
0x464 and_imm reg[1] 0xffffffff
0x46e popq reg[7] code[0xffb5]
0x470 cmp_imm reg[6] 0x1a
0x47a jnz 0x54a
0x47d pushq code[0xffb5] reg[0]
0x47f pushq code[0xffad] reg[1]
0x481 mov reg[0] reg[3]
0x484 mov reg[1] reg[2]
0x487 mov reg[2] reg[6]
0x48a pushq code[0xffa5] reg[7]
0x48c mov reg[7] reg[0]
0x48f pushq code[0xff9d] reg[6]
0x491 pushq code[0xff95] reg[7]
0x493 mov reg[6] reg[7]
0x496 mov reg[7] reg[7]
0x499 shr reg[6] 0x8
0x49c shl reg[7] 0x18
0x49f xor reg[6] reg[7]
0x4a2 and_imm reg[6] 0xffffffff
0x4ac popq reg[7] code[0xff95]
0x4ae mov reg[7] reg[6]
0x4b1 popq reg[6] code[0xff9d]
0x4b3 mov reg[0] reg[7]
0x4b6 pushq code[0xff9d] reg[6]
0x4b8 pushq code[0xff95] reg[7]
0x4ba mov reg[6] reg[1]
0x4bd mov reg[7] reg[0]
0x4c0 and reg[7] reg[6]
0x4c3 xor reg[0] reg[6]
0x4c6 cmp_imm reg[7] 0x0
0x4d0 jnz 0x4dc
0x4d3 shl reg[7] 0x1
0x4d6 mov reg[6] reg[7]
0x4d9 jmp 0x4bd
0x4dc and_imm reg[0] 0xffffffffffffffff
0x4e6 popq reg[7] code[0xff95]
0x4e8 popq reg[6] code[0xff9d]
0x4ea and_imm reg[0] 0xffffffff
0x4f4 xor reg[0] reg[2]
0x4f7 and_imm reg[0] 0xffffffff
0x501 mov reg[7] reg[1]
0x504 pushq code[0xff9d] reg[6]
0x506 pushq code[0xff95] reg[7]
0x508 mov reg[6] reg[7]
0x50b mov reg[7] reg[7]
0x50e shl reg[6] 0x3
0x511 shr reg[7] 0x1d
0x514 xor reg[6] reg[7]
0x517 and_imm reg[6] 0xffffffff
0x521 popq reg[7] code[0xff95]
0x523 mov reg[7] reg[6]
0x526 popq reg[6] code[0xff9d]
0x528 mov reg[1] reg[7]
0x52b xor reg[1] reg[0]
0x52e and_imm reg[1] 0xffffffff
0x538 popq reg[7] code[0xffa5]
0x53a mov reg[2] reg[1]
0x53d mov reg[3] reg[4]
0x540 mov reg[4] reg[5]
0x543 mov reg[5] reg[0]
0x546 popq reg[1] code[0xffad]
0x548 popq reg[0] code[0xffb5]
0x54a inc reg[6]
0x54c cmp_imm reg[6] 0x1b
0x556 jnz 0x55c
0x559 jmp 0x3c0
0x55c shl reg[1] 0x20
0x55f pushq code[0xffb5] reg[6]
0x561 pushq code[0xffad] reg[7]
0x563 mov reg[6] reg[1]
0x566 mov reg[7] reg[0]
0x569 and reg[7] reg[6]
0x56c xor reg[0] reg[6]
0x56f cmp_imm reg[7] 0x0
0x579 jnz 0x585
0x585 and_imm reg[0] 0xffffffffffffffff
0x58f popq reg[7] code[0xffad]
0x591 popq reg[6] code[0xffb5]
0x593 popq reg[7] code[0xffbd]
0x595 popq reg[6] code[0xffc5]
0x597 popq reg[5] code[0xffcd]
0x599 popq reg[4] code[0xffd5]
0x59b popq reg[3] code[0xffdd]
0x59d popq reg[2] code[0xffe5]
0x59f popq reg[1] code[0xffed]
0x5a1 popq reg[6] code[0xfff5]
0x5a3 retn 0x705
0x5a4 push mem[0x20] 0x2be
0x5a6 jmp 0x3
0x5a9 mov_imm LOTAG 0xe000
0x5ac load reg[0] code[0xe010]
0x5b0 mov reg[5] reg[0]
0x5b3 pushq code[0xfff7] reg[5]
0x5b5 mov_imm reg[5] 0x48f0e6421ac66dea
0x5bf xor_imm reg[5] 0xffffffffffffffff
0x5c9 pushq code[0xffef] reg[6]
0x5cb pushq code[0xffe7] reg[7]
0x5cd mov_imm reg[6] 0x1
0x5d7 mov reg[7] reg[5]
0x5da and reg[7] reg[6]
0x5dd xor reg[5] reg[6]
0x5e0 cmp_imm reg[7] 0x0
0x5ea jnz 0x5f6
0x5ed shl reg[7] 0x1
0x5f0 mov reg[6] reg[7]
0x5f3 jmp 0x5d7
0x5f6 popq reg[7] code[0xffe7]
0x5f8 popq reg[6] code[0xffef]
0x5fa pushq code[0xffef] reg[6]
0x5fc pushq code[0xffe7] reg[7]
0x5fe mov reg[6] reg[5]
0x601 mov reg[7] reg[0]
0x604 and reg[7] reg[6]
0x607 xor reg[0] reg[6]
0x60a cmp_imm reg[7] 0x0
0x614 jnz 0x620
0x617 shl reg[7] 0x1
0x61a mov reg[6] reg[7]
0x61d jmp 0x601
0x620 and_imm reg[0] 0xffffffffffffffff
0x62a popq reg[7] code[0xffe7]
0x62c popq reg[6] code[0xffef]
0x62e and_imm reg[0] 0xffffffffffffffff
0x638 popq reg[5] code[0xfff7]
0x63a xor_imm reg[0] 0x5074d85b9194e696
0x644 pushq code[0xfff7] reg[5]
0x646 mov_imm reg[5] 0x5566488c9c5cf234
0x650 xor_imm reg[5] 0xffffffffffffffff
0x65a pushq code[0xffef] reg[6]
0x65c pushq code[0xffe7] reg[7]
0x65e mov_imm reg[6] 0x1
0x668 mov reg[7] reg[5]
0x66b and reg[7] reg[6]
0x66e xor reg[5] reg[6]
0x671 cmp_imm reg[7] 0x0
0x67b jnz 0x687
0x67e shl reg[7] 0x1
0x681 mov reg[6] reg[7]
0x684 jmp 0x668
0x687 popq reg[7] code[0xffe7]
0x689 popq reg[6] code[0xffef]
0x68b pushq code[0xffef] reg[6]
0x68d pushq code[0xffe7] reg[7]
0x68f mov reg[6] reg[5]
0x692 mov reg[7] reg[0]
0x695 and reg[7] reg[6]
0x698 xor reg[0] reg[6]
0x69b cmp_imm reg[7] 0x0
0x6a5 jnz 0x6b1
0x6a8 shl reg[7] 0x1
0x6ab mov reg[6] reg[7]
0x6ae jmp 0x692
0x6b1 and_imm reg[0] 0xffffffffffffffff
0x6bb popq reg[7] code[0xffe7]
0x6bd popq reg[6] code[0xffef]
0x6bf and_imm reg[0] 0xffffffffffffffff
0x6c9 popq reg[5] code[0xfff7]
0x6cb xor_imm reg[0] 0x8cb331163a92fc19
0x6d5 mov_imm LOTAG 0x7200
0x6d8 mov_imm reg[1] 0x36b1cc9fe433713d
0x6e2 write code[0x7200] reg[1]
0x6e4 pushq code[0xfff7] reg[0]
0x6e6 mov_imm reg[0] 0x8
0x6f0 add LOTAG regs[0]
0x6f2 popq reg[0] code[0xfff7]
0x6f4 mov_imm reg[1] 0xf97646d69c84ebd8
0x6fe write code[0x7208] reg[1]
0x700 mov_imm LOTAG 0x7200
0x703 call 0x2be
0x705 cmp_imm reg[0] 0xda19ba6b81c83f61
0x70f jnz 0x715
0x712 call 0x104

call函数之前的代码等价于:

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
R0 = inp[2]
R5 = 0x48f0e6421ac66dea
R5 = ~R5 & 0xFFFFFFFFFFFFFFFF
R6 = 1
while True:
R7 = R5 & R6
R5 ^= R6
if R7 == 0: break
R6 = R7 << 1 & 0xFFFFFFFFFFFFFFFF
R6, R7 = R5, 0
while True:
R7 = R0 & R6
R0 ^= R6
if R7 == 0: break
R6 = R7 << 1 & 0xFFFFFFFFFFFFFFFF
R5, R6, R7 = inp, 0, 0
R0 ^= 0x5074d85b9194e696
R5 = 0x5566488c9c5cf234
R5 = ~R5 & 0xFFFFFFFFFFFFFFFF
R6 = 1
while True:
R7 = R5 & R6
R5 ^= R6
if R7 == 0: break
R6 = R7 << 1 & 0xFFFFFFFFFFFFFFFF
R6, R7 = R5, 0
while True:
R7 = R0 & R6
R0 ^= R6
if R7 == 0: break
R6 = R7 << 1 & 0xFFFFFFFFFFFFFFFF
R5, R6, R7 = inp, 0, 0
R0 ^= 0x8cb331163a92fc19

进一步等价于(用循环和位运算实现了加减法):

1
2
3
4
5
R0 = inp[2]
R0 -= 0x48f0e6421ac66dea
R0 ^= 0x5074d85b9194e696
R0 -= 0x5566488c9c5cf234
R0 ^= 0x8cb331163a92fc19

函数0x2BE是一个SPECK加密,R0为明文,code[0x7200]和code[0x7208]为顺序打乱的密钥,同构:

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
def enc(inp, key):
k = [key[0] & 0xFFFFFFFF, (key[1] >> 32) & 0xFFFFFFFF, key[1] & 0xFFFFFFFF, (key[0] >> 32) & 0xFFFFFFFF]
rk = [k[0]]
kl = [k[3], k[2], k[1]]
for i in range(26):
l1 = (((((kl[0] >> 0x08) | (kl[0] << 0x18)) & 0xFFFFFFFF) + rk[i]) ^ i) & 0xFFFFFFFF
k1 = ((((rk[i] << 0x03) | (rk[i] >> 0x1D)) & 0xFFFFFFFF) ^ l1) & 0xFFFFFFFF
kl.pop(0)
kl.append(l1)
rk.append(k1)
x = inp & 0xFFFFFFFF
y = inp >> 32
for i in range(27):
x = (((((x >> 0x08) | (x << 0x18)) & 0xFFFFFFFF) + y) & 0xFFFFFFFF) ^ rk[i]
y = (((y << 0x03) | (y >> 0x1D)) & 0xFFFFFFFF) ^ x
return y << 32 | x

def dec(inp, key):
k = [key[0] & 0xFFFFFFFF, (key[1] >> 32) & 0xFFFFFFFF, key[1] & 0xFFFFFFFF, (key[0] >> 32) & 0xFFFFFFFF]
rk = [k[0]]
kl = [k[3], k[2], k[1]]
for i in range(26):
l1 = (((((kl[0] >> 0x08) | (kl[0] << 0x18)) & 0xFFFFFFFF) + rk[i]) ^ i) & 0xFFFFFFFF
k1 = ((((rk[i] << 0x03) | (rk[i] >> 0x1D)) & 0xFFFFFFFF) ^ l1) & 0xFFFFFFFF
kl.pop(0)
kl.append(l1)
rk.append(k1)
x = inp & 0xFFFFFFFF
y = inp >> 32
for i in range(26, -1, -1):
y = (((y^x) >> 0x03) | ((y^x) << 0x1D)) & 0xFFFFFFFF
x = (((((x^rk[i]) - y) & 0xFFFFFFFF) << 0x08) | ((((x^rk[i]) - y) & 0xFFFFFFFF) >> 0x18)) & 0xFFFFFFFF
return y << 32 | x

key = [0x36b1cc9fe433713d, 0xf97646d69c84ebd8]
inp = 0xcf626ef7f00ee7fd
enc = enc(inp, key)
print(f"{hex(enc)}")
enc = 0xda19ba6b81c83f61
dec = dec(enc, key)
print(f"{hex(dec)}")

得到中间密文0x38377082e139ecb9,解密得到inp[2] = 0xa28f38bd0463522c,继续执行:

1
2
3
4
0x715	mov	reg[0]	reg[5]
0x718 mov_imm HITAG 0x72a
0x71b mov_imm reg[1] 0x6057
0x725 call 0x116

0x116是一个标准RC4,以inp[2]为密钥,存储在code[0x7100],S盒存储在code[0x7000],自解密0x72A开始长度0x6057的代码块:

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
0x116	mov_imm	LOTAG	0x7100
0x119 write code[0x7100] reg[0]
0x11b mov_imm LOTAG 0x7000
0x11e mov_imm reg[2] 0x0
0x128 write code[LOTAG + reg[2]] reg[2]
0x12b inc reg[2]
0x12d cmp_imm reg[2] 0x100
0x137 jz 0x128
0x13a mov_imm reg[2] 0x0
0x144 mov_imm reg[3] 0x0
0x14e mov_imm reg[7] 0xff
0x158 mov_imm LOTAG 0x7000
0x15b load reg[5] code[LOTAG + reg[2]]
0x15e mov reg[4] reg[2]
0x161 and_imm reg[4] 0x7
0x16b mov_imm LOTAG 0x7100
0x16e load reg[4] code[LOTAG + reg[4]]
0x171 pushq code[0xfff5] reg[6]
0x173 pushq code[0xffed] reg[7]
0x175 mov reg[6] reg[5]
0x178 mov reg[7] reg[3]
0x17b and reg[7] reg[6]
0x17e xor reg[3] reg[6]
0x181 cmp_imm reg[7] 0x0
0x18b jnz 0x197
0x18e shl reg[7] 0x1
0x191 mov reg[6] reg[7]
0x194 jmp 0x178
0x197 and_imm reg[3] 0xffffffffffffffff
0x1a1 popq reg[7] code[0xffed]
0x1a3 popq reg[6] code[0xfff5]
0x1a5 pushq code[0xfff5] reg[6]
0x1a7 pushq code[0xffed] reg[7]
0x1a9 mov reg[6] reg[4]
0x1ac mov reg[7] reg[3]
0x1af and reg[7] reg[6]
0x1b2 xor reg[3] reg[6]
0x1b5 cmp_imm reg[7] 0x0
0x1bf jnz 0x1cb
0x1c2 shl reg[7] 0x1
0x1c5 mov reg[6] reg[7]
0x1c8 jmp 0x1ac
0x1cb and_imm reg[3] 0xffffffffffffffff
0x1d5 popq reg[7] code[0xffed]
0x1d7 popq reg[6] code[0xfff5]
0x1d9 and reg[3] reg[7]
0x1dc mov_imm LOTAG 0x7000
0x1df load reg[6] code[LOTAG + reg[3]]
0x1e2 write code[LOTAG + reg[2]] reg[6]
0x1e5 write code[LOTAG + reg[3]] reg[5]
0x1e8 inc reg[2]
0x1ea cmp_imm reg[2] 0x100
0x1f4 jz 0x158
0x1f7 mov_imm reg[2] 0x0
0x201 mov_imm reg[3] 0x0
0x20b mov_imm reg[6] 0x0
0x215 cmp_imm reg[1] 0x0
0x21f jnz 0x2b7
0x222 inc reg[2]
0x224 and reg[2] reg[7]
0x227 mov_imm LOTAG 0x7000
0x22a load reg[5] code[LOTAG + reg[2]]
0x22d pushq code[0xfff5] reg[6]
0x22f pushq code[0xffed] reg[7]
0x231 mov reg[6] reg[5]
0x234 mov reg[7] reg[3]
0x237 and reg[7] reg[6]
0x23a xor reg[3] reg[6]
0x23d cmp_imm reg[7] 0x0
0x247 jnz 0x253
0x24a shl reg[7] 0x1
0x24d mov reg[6] reg[7]
0x250 jmp 0x234
0x253 and_imm reg[3] 0xffffffffffffffff
0x25d popq reg[7] code[0xffed]
0x25f popq reg[6] code[0xfff5]
0x261 and reg[3] reg[7]
0x264 load reg[0] code[LOTAG + reg[3]]
0x267 write code[LOTAG + reg[2]] reg[0]
0x26a write code[LOTAG + reg[3]] reg[5]
0x26d pushq code[0xfff5] reg[6]
0x26f pushq code[0xffed] reg[7]
0x271 mov reg[6] reg[0]
0x274 mov reg[7] reg[5]
0x277 and reg[7] reg[6]
0x27a xor reg[5] reg[6]
0x27d cmp_imm reg[7] 0x0
0x287 jnz 0x293
0x28a shl reg[7] 0x1
0x28d mov reg[6] reg[7]
0x290 jmp 0x274
0x293 and_imm reg[5] 0xffffffffffffffff
0x29d popq reg[7] code[0xffed]
0x29f popq reg[6] code[0xfff5]
0x2a1 and reg[5] reg[7]
0x2a4 load reg[4] code[LOTAG + reg[5]]
0x2a7 load reg[5] code[HITAG + reg[6]]
0x2aa xor reg[5] reg[4]
0x2ad write code[HITAG + reg[6]] reg[5]
0x2b0 inc reg[6]
0x2b2 dec reg[1]
0x2b4 jmp 0x215
0x2b7 retn 0x727

后续逻辑:

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
0x727	jmp	0x72a
0x72a mov_imm LOTAG 0xe000
0x72d load reg[0] code[0xe008]
0x731 mov reg[5] reg[0]
0x734 xor_imm reg[0] 0x95714c91bc8b306f
0x73e xor_imm reg[0] 0x4303f92241dd9a9f
0x748 xor_imm reg[0] 0x311e18c91413b58c
0x752 pushq code[0xfff7] reg[5]
0x754 mov_imm reg[5] 0x8df6073d0dbbff09
0x75e xor_imm reg[5] 0xffffffffffffffff
0x768 pushq code[0xffef] reg[6]
0x76a pushq code[0xffe7] reg[7]
0x76c mov_imm reg[6] 0x1
0x776 mov reg[7] reg[5]
0x779 and reg[7] reg[6]
0x77c xor reg[5] reg[6]
0x77f cmp_imm reg[7] 0x0
0x789 jnz 0x795
0x795 popq reg[7] code[0xffe7]
0x797 popq reg[6] code[0xffef]
0x799 pushq code[0xffef] reg[6]
0x79b pushq code[0xffe7] reg[7]
0x79d mov reg[6] reg[5]
0x7a0 mov reg[7] reg[0]
0x7a3 and reg[7] reg[6]
0x7a6 xor reg[0] reg[6]
0x7a9 cmp_imm reg[7] 0x0
0x7b3 jnz 0x7bf
0x7b6 shl reg[7] 0x1
0x7b9 mov reg[6] reg[7]
0x7bc jmp 0x7a0
0x7bf and_imm reg[0] 0xffffffffffffffff
0x7c9 popq reg[7] code[0xffe7]
0x7cb popq reg[6] code[0xffef]
0x7cd and_imm reg[0] 0xffffffffffffffff
0x7d7 popq reg[5] code[0xfff7]
0x7d9 pushq code[0xfff7] reg[5]
0x7db mov_imm reg[5] 0xee5744efe81e97b7
0x7e5 xor_imm reg[5] 0xffffffffffffffff
0x7ef pushq code[0xffef] reg[6]
0x7f1 pushq code[0xffe7] reg[7]
0x7f3 mov_imm reg[6] 0x1
0x7fd mov reg[7] reg[5]
0x800 and reg[7] reg[6]
0x803 xor reg[5] reg[6]
0x806 cmp_imm reg[7] 0x0
0x810 jnz 0x81c
0x81c popq reg[7] code[0xffe7]
0x81e popq reg[6] code[0xffef]
0x820 pushq code[0xffef] reg[6]
0x822 pushq code[0xffe7] reg[7]
0x824 mov reg[6] reg[5]
0x827 mov reg[7] reg[0]
0x82a and reg[7] reg[6]
0x82d xor reg[0] reg[6]
0x830 cmp_imm reg[7] 0x0
0x83a jnz 0x846
0x83d shl reg[7] 0x1
0x840 mov reg[6] reg[7]
0x843 jmp 0x827
0x846 and_imm reg[0] 0xffffffffffffffff
0x850 popq reg[7] code[0xffe7]
0x852 popq reg[6] code[0xffef]
0x854 and_imm reg[0] 0xffffffffffffffff
0x85e popq reg[5] code[0xfff7]
0x860 pushq code[0xfff7] reg[6]
0x862 pushq code[0xffef] reg[7]
0x864 mov_imm reg[6] 0xf8a82a8dbdb78c3f
0x86e mov reg[7] reg[0]
0x871 and reg[7] reg[6]
0x874 xor reg[0] reg[6]
0x877 cmp_imm reg[7] 0x0
0x881 jnz 0x88d
0x884 shl reg[7] 0x1
0x887 mov reg[6] reg[7]
0x88a jmp 0x86e
0x88d popq reg[7] code[0xffef]
0x88f popq reg[6] code[0xfff7]
0x891 pushq code[0xfff7] reg[6]
0x893 pushq code[0xffef] reg[7]
0x895 mov_imm reg[6] 0x58e8abfc7618f5fd
0x89f mov reg[7] reg[0]
0x8a2 and reg[7] reg[6]
0x8a5 xor reg[0] reg[6]
0x8a8 cmp_imm reg[7] 0x0
0x8b2 jnz 0x8be
0x8b5 shl reg[7] 0x1
0x8b8 mov reg[6] reg[7]
0x8bb jmp 0x89f
0x8be popq reg[7] code[0xffef]
0x8c0 popq reg[6] code[0xfff7]
0x8c2 xor_imm reg[0] 0x99d88c4fa4cc68aa
0x8cc mov_imm LOTAG 0x7200
0x8cf mov_imm reg[1] 0x8d85b3156df9f721
0x8d9 write code[0x7200] reg[1]
0x8db pushq code[0xfff7] reg[0]
0x8dd mov_imm reg[0] 0x8
0x8e7 add LOTAG regs[0]
0x8e9 popq reg[0] code[0xfff7]
0x8eb mov_imm reg[1] 0x28e3d33340bc0884
0x8f5 write code[0x7208] reg[1]
0x8f7 mov_imm LOTAG 0x7200
0x8fa call 0x2be
0x8fc cmp_imm reg[0] 0x659391a5dc3522b3
0x906 jnz 0x90c
0x909 call 0x104

开始对inp[1]进行检查,同构:

1
2
3
4
5
6
7
8
9
10
11
12
R0 = inp[1]
R0 ^= 0x95714c91bc8b306f
R0 ^= 0x4303f92241dd9a9f
R0 ^= 0x311e18c91413b58c
R0 -= 0x8df6073d0dbbff09
R0 -= 0xee5744efe81e97b7
R0 += 0xf8a82a8dbdb78c3f
R0 += 0x58e8abfc7618f5fd
R0 ^= 0x99d88c4fa4cc68aa
R0 &= 0xFFFFFFFFFFFFFFFF
key = [0x8d85b3156df9f721, 0x28e3d33340bc0884]
enc = speck_dec(R0, key)

解密得到inp[1] = 0xbf11b34d0ce941cc,作为下一组加密代码的RC4解密密钥,之后所有的组都符合此模式:

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
0x90c	mov	reg[0]	reg[5]
0x90f mov_imm HITAG 0x921
0x912 mov_imm reg[1] 0x5e60
0x91c call 0x116
0x91e jmp 0x921
0x921 mov_imm LOTAG 0xe000
0x924 load reg[0] code[0xe0a0]
0x928 mov reg[5] reg[0]
0x92b pushq code[0xfff7] reg[6]
0x92d pushq code[0xffef] reg[7]
0x92f mov_imm reg[6] 0x52591d5fa111b92e
0x939 mov reg[7] reg[0]
0x93c and reg[7] reg[6]
0x93f xor reg[0] reg[6]
0x942 cmp_imm reg[7] 0x0
0x94c jnz 0x958
0x94f shl reg[7] 0x1
0x952 mov reg[6] reg[7]
0x955 jmp 0x939
0x958 popq reg[7] code[0xffef]
0x95a popq reg[6] code[0xfff7]
0x95c xor_imm reg[0] 0xc7e29a0a50ac78e3
0x966 xor_imm reg[0] 0xead13c41399fcfd6
0x970 pushq code[0xfff7] reg[5]
0x972 mov_imm reg[5] 0x61553c85a2f4e8b9
0x97c xor_imm reg[5] 0xffffffffffffffff
0x986 pushq code[0xffef] reg[6]
0x988 pushq code[0xffe7] reg[7]
0x98a mov_imm reg[6] 0x1
0x994 mov reg[7] reg[5]
0x997 and reg[7] reg[6]
0x99a xor reg[5] reg[6]
0x99d cmp_imm reg[7] 0x0
0x9a7 jnz 0x9b3
0x9b3 popq reg[7] code[0xffe7]
0x9b5 popq reg[6] code[0xffef]
0x9b7 pushq code[0xffef] reg[6]
0x9b9 pushq code[0xffe7] reg[7]
0x9bb mov reg[6] reg[5]
0x9be mov reg[7] reg[0]
0x9c1 and reg[7] reg[6]
0x9c4 xor reg[0] reg[6]
0x9c7 cmp_imm reg[7] 0x0
0x9d1 jnz 0x9dd
0x9d4 shl reg[7] 0x1
0x9d7 mov reg[6] reg[7]
0x9da jmp 0x9be
0x9dd and_imm reg[0] 0xffffffffffffffff
0x9e7 popq reg[7] code[0xffe7]
0x9e9 popq reg[6] code[0xffef]
0x9eb and_imm reg[0] 0xffffffffffffffff
0x9f5 popq reg[5] code[0xfff7]
0x9f7 xor_imm reg[0] 0x15f909ccb556ec05
0xa01 pushq code[0xfff7] reg[5]
0xa03 mov_imm reg[5] 0x4674252dee87e8a2
0xa0d xor_imm reg[5] 0xffffffffffffffff
0xa17 pushq code[0xffef] reg[6]
0xa19 pushq code[0xffe7] reg[7]
0xa1b mov_imm reg[6] 0x1
0xa25 mov reg[7] reg[5]
0xa28 and reg[7] reg[6]
0xa2b xor reg[5] reg[6]
0xa2e cmp_imm reg[7] 0x0
0xa38 jnz 0xa44
0xa3b shl reg[7] 0x1
0xa3e mov reg[6] reg[7]
0xa41 jmp 0xa25
0xa44 popq reg[7] code[0xffe7]
0xa46 popq reg[6] code[0xffef]
0xa48 pushq code[0xffef] reg[6]
0xa4a pushq code[0xffe7] reg[7]
0xa4c mov reg[6] reg[5]
0xa4f mov reg[7] reg[0]
0xa52 and reg[7] reg[6]
0xa55 xor reg[0] reg[6]
0xa58 cmp_imm reg[7] 0x0
0xa62 jnz 0xa6e
0xa65 shl reg[7] 0x1
0xa68 mov reg[6] reg[7]
0xa6b jmp 0xa4f
0xa6e and_imm reg[0] 0xffffffffffffffff
0xa78 popq reg[7] code[0xffe7]
0xa7a popq reg[6] code[0xffef]
0xa7c and_imm reg[0] 0xffffffffffffffff
0xa86 popq reg[5] code[0xfff7]
0xa88 xor_imm reg[0] 0xe885b64f981d1baa
0xa92 pushq code[0xfff7] reg[6]
0xa94 pushq code[0xffef] reg[7]
0xa96 mov_imm reg[6] 0x94c6e3f48118560b
0xaa0 mov reg[7] reg[0]
0xaa3 and reg[7] reg[6]
0xaa6 xor reg[0] reg[6]
0xaa9 cmp_imm reg[7] 0x0
0xab3 jnz 0xabf
0xab6 shl reg[7] 0x1
0xab9 mov reg[6] reg[7]
0xabc jmp 0xaa0
0xabf popq reg[7] code[0xffef]
0xac1 popq reg[6] code[0xfff7]
0xac3 mov_imm LOTAG 0x7200
0xac6 mov_imm reg[1] 0x1d1a63b571be74bc
0xad0 write code[0x7200] reg[1]
0xad2 pushq code[0xfff7] reg[0]
0xad4 mov_imm reg[0] 0x8
0xade add LOTAG regs[0]
0xae0 popq reg[0] code[0xfff7]
0xae2 mov_imm reg[1] 0x3e36eee3aac04cfd
0xaec write code[0x7208] reg[1]
0xaee mov_imm LOTAG 0x7200
0xaf1 call 0x2be
0xaf3 cmp_imm reg[0] 0x5538224d4c7a252a
0xafd jnz 0xb03
0xb00 call 0x104

同构下一组inp[20]:

1
2
3
4
5
6
7
8
9
10
11
12
13
R0 = inp[20]
R0 = 0x3a2e507999f2831f
R0 += 0x52591d5fa111b92e
R0 ^= 0xc7e29a0a50ac78e3
R0 ^= 0xead13c41399fcfd6
R0 -= 0x61553c85a2f4e8b9
R0 ^= 0x15f909ccb556ec05
R0 -= 0x4674252dee87e8a2
R0 ^= 0xe885b64f981d1baa
R0 += 0x94c6e3f48118560b
R0 &= 0xFFFFFFFFFFFFFFFF
key = [0x1d1a63b571be74bc, 0x3e36eee3aac04cfd]
enc = speck_enc(R0, key)

解得inp[20] = 0xef320f9e6ae31520,以此类推,注意到其加密方式除了speck之外只有3种:+、-和^,所有关键汇编分别具有特征:

运算 表达式举例 特征
明文 load reg[0] code[0xe0e8] code[0xe
+ mov_imm reg[6] 0x52591d5fa111b92e mov_imm reg[6] uint64
- mov_imm reg[5] 0x308442bd2f0ab265 mov_imm reg[5] uint64
^ xor_imm reg[0] 0xd701b4a09a5bde6f xor_imm reg[0] uint64
speck mov_imm reg[1] 0x54a0680a97645358 mov_imm reg[1] uint64
密文 cmp_imm reg[0] 0x66e064f4dac280d0 cmp_imm reg[0] uint64

因此可以提取汇编中的特征,编写脚本同构整个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
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
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
import re
import time

def printhex(array):
for i in range(len(array)):
print(f"0x{array[i]:016x}", end=" ")
if i % 5 == 4: print()

def b2nle(b, n): return [int.from_bytes(b[i:i+n], byteorder='little', signed=False) for i in range(0, len(b), n)]

def n2ble(na, n):
b = bytearray()
for a in na: b.extend(a.to_bytes(n, byteorder='little'))
return b

def speck_enc(inp, key):
k = [key[0] & 0xFFFFFFFF, (key[1] >> 32) & 0xFFFFFFFF, key[1] & 0xFFFFFFFF, (key[0] >> 32) & 0xFFFFFFFF]
rk = [k[0]]
kl = [k[3], k[2], k[1]]
for i in range(26):
l1 = (((((kl[0] >> 0x08) | (kl[0] << 0x18)) & 0xFFFFFFFF) + rk[i]) ^ i) & 0xFFFFFFFF
k1 = ((((rk[i] << 0x03) | (rk[i] >> 0x1D)) & 0xFFFFFFFF) ^ l1) & 0xFFFFFFFF
kl.pop(0)
kl.append(l1)
rk.append(k1)
x = inp & 0xFFFFFFFF
y = inp >> 32
for i in range(27):
x = (((((x >> 0x08) | (x << 0x18)) & 0xFFFFFFFF) + y) & 0xFFFFFFFF) ^ rk[i]
y = (((y << 0x03) | (y >> 0x1D)) & 0xFFFFFFFF) ^ x
return y << 32 | x

def speck_dec(inp, key):
k = [key[0] & 0xFFFFFFFF, (key[1] >> 32) & 0xFFFFFFFF, key[1] & 0xFFFFFFFF, (key[0] >> 32) & 0xFFFFFFFF]
rk = [k[0]]
kl = [k[3], k[2], k[1]]
for i in range(26):
l1 = (((((kl[0] >> 0x08) | (kl[0] << 0x18)) & 0xFFFFFFFF) + rk[i]) ^ i) & 0xFFFFFFFF
k1 = ((((rk[i] << 0x03) | (rk[i] >> 0x1D)) & 0xFFFFFFFF) ^ l1) & 0xFFFFFFFF
kl.pop(0)
kl.append(l1)
rk.append(k1)
x = inp & 0xFFFFFFFF
y = inp >> 32
for i in range(26, -1, -1):
y = (((y^x) >> 0x03) | ((y^x) << 0x1D)) & 0xFFFFFFFF
x = (((((x^rk[i]) - y) & 0xFFFFFFFF) << 0x08) | ((((x^rk[i]) - y) & 0xFFFFFFFF) >> 0x18)) & 0xFFFFFFFF
return y << 32 | x

def solve(inst):
enc = inst[-1][1] # 密文
key = [inst[-3][1], inst[-2][1]] # C7200和C7208
dec = speck_dec(enc, key) # 解密speck
for i in range(len(inst)-4,0,-1): # 解密线性运算
if inst[i][0] == "+": dec = (dec - inst[i][1]) & 0xFFFFFFFFFFFFFFFF
if inst[i][0] == "-": dec = (dec + inst[i][1]) & 0xFFFFFFFFFFFFFFFF
if inst[i][0] == "^": dec = dec ^ inst[i][1]
return dec

def parse(insts):
for i in range(len(insts)): # 50个单句
inst = insts[i]
enc = inst[-1][1] # 密文
key = [inst[-3][1], inst[-2][1]] # C7200和C7208
idx = inst[0] # 索引
for j in range(1,len(inst)-3):
if inst[j][0] == "+": print(f"inps[{idx}] += {hex(inst[j][1])}")
if inst[j][0] == "-": print(f"inps[{idx}] -= {hex(inst[j][1])}")
if inst[j][0] == "^": print(f"inps[{idx}] ^= {hex(inst[j][1])}")
print(f"speck_enc(inps[{idx}], key = [{hex(inst[-3][1])}, {hex(inst[-2][1])}])")
print(f"assert inps[{idx}] == {hex(enc)}")

def extr(l):
fl = [it for it in l if it is not None]
idx = -1
c = None
for i, ln in enumerate(fl):
if "code[0xe" in ln:
mt = re.search(r'code\\[(0x[0-9a-fA-F]+)\\]', ln)
if mt:
hs = mt.group(1)
c = (int(hs, 16) - 0xE000) // 8
idx = i
break
if idx == -1: raise ValueError("Base addr not found.")
res = [c]
for i in range(idx + 1, len(fl)):
ln = fl[i]
arg = re.search(r'0x[0-9a-fA-F]{9,}', ln)
if not arg: continue
hv = int(arg.group(), 16)
if "mov_imm\\treg[6]" in ln: res.append(["+", hv])
elif "mov_imm\\treg[5]" in ln: res.append(["-", hv])
elif "xor_imm\\treg[0]" in ln: res.append(["^", hv])
elif "mov_imm\\treg[1]" in ln: res.append(["k", hv])
elif "cmp_imm\\treg[0]" in ln: res.append(["c", hv])
return res

def extr_all(l):
fl = [it for it in l if it is not None]
idx = []
for i, ln in enumerate(fl):
if "code[0xe" in ln: idx.append(i)
if not idx: raise ValueError("No base addr found.")
res = []
for ci in idx:
ln = fl[ci]
mt = re.search(r'code\\[(0x[0-9a-fA-F]+)\\]', ln)
if mt:
hs = mt.group(1)
c = (int(hs, 16) - 0xE000) // 8
sg = [c]
ei = len(fl)
if idx.index(ci) + 1 < len(idx):
ei = idx[idx.index(ci) + 1]
for i in range(ci + 1, ei):
ln = fl[i]
arg = re.search(r'0x[0-9a-fA-F]{9,}', ln)
if not arg: continue
hv = int(arg.group(), 16)
if "mov_imm\\treg[6]" in ln: sg.append(["+", hv])
elif "mov_imm\\treg[5]" in ln: sg.append(["-", hv])
elif "xor_imm\\treg[0]" in ln: sg.append(["^", hv])
elif "mov_imm\\treg[1]" in ln: sg.append(["k", hv])
elif "cmp_imm\\treg[0]" in ln: sg.append(["c", hv])
res.append(sg)
return res

def trace_vm(inp, limit):
with open("full_vmcode", 'rb') as file: code = file.read(26497)
code = bytearray(code) + bytearray(0xE000 - len(code))
binp = n2ble(inp, 8)
code += binp
code = bytearray(code) + bytearray(0x10000 - len(code))
PC = 0
HIPC = 0xFFFF
LOTAG = 0
HITAG = 0
STAKPC = 0
ZFLAG = False
ENDFLAG = 1
regs = [0] * 8
mem = [0] * 256
stdout = ""
tracelen = 0
DEBUG, NOISY, ASM = True, False, False # 输出trace流
FORCEJMP = False
PASSED = [0] * len(code)
ASMS = [None] * len(code)
print("traceid\\taddr\\topcode\\tname\\targs and ress")
while True:
if PC > limit: DEBUG, ASM = True, False # 限制trace输出范围
else: DEBUG, ASM = False, False
PC_1 = PC
op = code[PC]
if DEBUG and not ASM: print(f"{tracelen}\\t{hex(PC)}\\t{hex(op)}",end="\\t")
if op == 0x01:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if DEBUG: print(f"jmp\\t{hex(PC)}")
elif op == 0x02:
if ZFLAG == False or FORCEJMP == True:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if DEBUG: print(f"jz\\t{hex(PC)} => JUMPED")
else:
if DEBUG: print(f"jz\\t{hex(b2nle(code[PC+1:PC+3], 2)[0])} => NOT JUMP")
PC += 3
elif op == 0x03:
if ZFLAG == True or FORCEJMP == True:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if DEBUG: print(f"jnz\\t{hex(PC)} => JUMPED")
else:
if DEBUG: print(f"jnz\\t{hex(b2nle(code[PC+1:PC+3], 2)[0])} => NOT JUMP")
PC += 3
elif op == 0x11:
LOTAG = b2nle(code[PC+1:PC+3], 2)[0]
if DEBUG: print(f"mov_imm\\tLOTAG\\t{hex(LOTAG)}")
PC += 3
elif op == 0x12:
HITAG = b2nle(code[PC+1:PC+3], 2)[0]
if DEBUG: print(f"mov_imm\\tHITAG\\t{hex(HITAG)}")
PC += 3
elif op == 0x15:
regid = code[PC+1]
regs[regid] = b2nle(code[LOTAG:LOTAG+8], 8)[0]
if DEBUG: print(f"load\\tcode[{hex(LOTAG)}]\\treg[{regid}] = {hex(regs[regid])}")
PC += 2
elif op == 0x16:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] = IMM
ZFLAG = regs[REG_B] == 0
if DEBUG and NOISY: print(f"mov_imm\\treg[{regid}]\\t{hex(IMM)}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"mov_imm\\treg[{regid}]\\t{hex(IMM)}")
PC += 10
elif op == 0x17:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] = regs[REG_B]
ZFLAG = regs[REG_A] == 0
if DEBUG and NOISY: print(f"mov\\treg[{REG_A}]\\treg[{REG_B}] = {hex(regs[REG_A])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"mov\\treg[{REG_A}]\\treg[{REG_B}] = {hex(regs[REG_A])}")
PC += 3
elif op == 0x18:
regid = code[PC+1]
addr = LOTAG + b2nle(code[PC+2:PC+4], 2)[0]
regs[regid] = b2nle(code[addr:addr+8], 8)[0]
if DEBUG: print(f"load\\treg[{regid}]\\tcode[{hex(addr)}] = {hex(regs[regid])}")
PC += 4
elif op == 0x19:
regid = code[PC+1]
code[LOTAG:LOTAG+8] = n2ble([regs[regid]], 8)
if DEBUG: print(f"write\\tcode[{hex(LOTAG)}]\\treg[{regid}] = {hex(regs[regid])}")
PC += 2
elif op == 0x1A:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = LOTAG + regs[REG_B]
regs[REG_A] = code[addr]
if DEBUG: print(f"load\\treg[{REG_A}]\\tcode[{hex(addr)}] = {hex(regs[REG_A])}")
PC += 3
elif op == 0x1B:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = LOTAG + regs[REG_B]
code[addr] = regs[REG_A] & 0xFF
if DEBUG: print(f"write\\tcode[{hex(addr)}]\\treg[{REG_A}] = {hex(regs[REG_A])}")
PC += 3
elif op == 0x1C:
regid = code[PC+1]
regs[regid] += 1
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"inc\\treg[{regid}]\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"inc\\treg[{regid}]\\tres = {hex(regs[regid])}")
PC += 2
elif op == 0x1D:
regid = code[PC+1]
regs[regid] -= 1
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"dec\\treg[{regid}]\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"dec\\treg[{regid}]\\tres = {hex(regs[regid])}")
PC += 2
elif op == 0x1E:
regid = code[PC+1]
IMM8 = code[PC+2]
regs[regid] = regs[regid] >> IMM8
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"shr\\treg[{regid}]\\t{hex(IMM8)}\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"shr\\treg[{regid}]\\t{hex(IMM8)}\\tres = {hex(regs[regid])}")
PC += 3
elif op == 0x1f:
regid = code[PC+1]
LOTAG += regs[regid] & 0xFFFF
if DEBUG: print(f"add\\tLOTAG\\tregs[{regid}]\\tres = {hex(LOTAG)}")
PC += 2
elif op == 0x25:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] &= regs[REG_B]
ZFLAG = regs[REG_A] == 0
if DEBUG and NOISY: print(f"and\\treg[{REG_A}]\\treg[{REG_B}]\\tres = {hex(regs[REG_A])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"and\\treg[{REG_A}]\\treg[{REG_B}]\\tres = {hex(regs[REG_A])}")
PC += 3
elif op == 0x26:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] ^= regs[REG_B]
ZFLAG = regs[REG_A] == 0
if DEBUG and NOISY: print(f"xor\\treg[{REG_A}]\\treg[{REG_B}]\\tres = {hex(regs[REG_A])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"xor\\treg[{REG_A}]\\treg[{REG_B}]\\tres = {hex(regs[REG_A])}")
PC += 3
elif op == 0x27:
regid = code[PC+1]
IMM8 = code[PC+2]
regs[regid] = (regs[regid] << IMM8) & 0xFFFFFFFFFFFFFFFF
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"shl\\treg[{regid}]\\t{hex(IMM8)}\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"shl\\treg[{regid}]\\t{hex(IMM8)}\\tres = {hex(regs[regid])}")
PC += 3
elif op == 0x29:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] ^= IMM
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"xor_imm\\treg[{regid}]\\t{hex(IMM)}\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"xor_imm\\treg[{regid}]\\t{hex(IMM)}\\tres = {hex(regs[regid])}")
PC += 10
elif op == 0x2A:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] &= IMM
ZFLAG = regs[regid] == 0
if DEBUG and NOISY: print(f"and_imm\\treg[{regid}]\\t{hex(IMM)}\\tres = {hex(regs[regid])}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"and_imm\\treg[{regid}]\\t{hex(IMM)}\\tres = {hex(regs[regid])}")
PC += 10
elif op == 0x2B:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = HITAG + regs[REG_B]
regs[REG_A] = code[addr]
if DEBUG: print(f"load\\treg[{REG_A}]\\tcode[{hex(addr)}] = {hex(regs[REG_A])}")
PC += 3
elif op == 0x2C:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = HITAG + regs[REG_B]
code[addr] = regs[REG_A] & 0xFF
if DEBUG: print(f"write\\tcode[{hex(addr)}]\\treg[{REG_A}] = {hex(regs[REG_A])}")
PC += 3
elif op == 0x32:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
ZFLAG = regs[regid] == IMM
if DEBUG and NOISY: print(f"cmp_imm\\treg[{regid}] = {hex(regs[regid])}\\t{hex(IMM)}\\tZFLAG = {ZFLAG}")
if DEBUG and not NOISY: print(f"cmp_imm\\treg[{regid}] = {hex(regs[regid])}\\t{hex(IMM)}")
PC += 10
elif op == 0x80:
STAKPC = PC + 1
if DEBUG: print(f"svstk\\t{hex(STAKPC)}")
PC += 1
elif op == 0x81:
mem[code[PC+1]] = STAKPC + 3
if DEBUG: print(f"push\\tmem[{hex(code[PC+1])}]\\t{hex(STAKPC+3)}")
PC += 2
elif op == 0x82:
addr = mem[code[PC+1]]
RETADDR = PC + 2
HIPC -= 2
code[HIPC:HIPC+2] = n2ble([RETADDR], 2)
if DEBUG: print(f"call\\tmem[{hex(code[PC+1])}] = {hex(addr)}\\tretaddr = {hex(RETADDR)}")
PC = addr
elif op == 0x83:
addr = b2nle(code[HIPC:HIPC+2], 2)[0]
if DEBUG: print(f"retn\\t{hex(addr)}")
HIPC += 2
PC = addr
elif op == 0x84:
regid = code[PC+1]
HIPC -= 8
code[HIPC:HIPC+8] = n2ble([regs[regid]], 8)
if DEBUG: print(f"pushq\\tcode[{hex(HIPC & 0xFFFF)}]\\treg[{regid}] = {hex(regs[regid])}")
PC += 2
elif op == 0x85:
regid = code[PC+1]
regs[regid] = b2nle(code[HIPC:HIPC+8], 8)[0]
if DEBUG: print(f"popq\\treg[{regid}]\\tcode[{hex(HIPC & 0xFFFF)}] = {hex(regs[regid])}")
HIPC += 8
PC += 2
elif op == 0x90:
addr = code[PC+1]
stdout += chr(addr)
if DEBUG: print("stdout")
PC += 2
elif op == 0xFF:
if DEBUG: print("exit")
break
else:
if DEBUG: print(f"Unknown OP {hex(op)} at {hex(PC)}")
break
tracelen += 1
PASSED.append(PC_1)

if NOISY: print(HITAG, LOTAG, HIPC, PC)
if NOISY: print(regs)
if NOISY: print(b2nle(code[0x7200:0x7200+8*5], 8))
if NOISY: print(b2nle(code[0xFFF7-8*0x20:0xFFFF], 8))
if NOISY: print(code[0x7000:0x7100].hex())
print(tracelen)
print(stdout)

def run_vm(inp, limit, auto = False):
with open("full_vmcode", 'rb') as file: code = file.read(26497)
code = bytearray(code) + bytearray(0xE000 - len(code))
binp = n2ble(inp, 8)
code += binp
code = bytearray(code) + bytearray(0x10000 - len(code))
PC = 0
HIPC = 0xFFFF
LOTAG = 0
HITAG = 0
STAKPC = 0
ZFLAG = False
ENDFLAG = 1
regs = [0] * 8
mem = [0] * 256
stdout = ""
tracelen = 0
DEBUG, NOISY, ASM = False, False, True # 输出反汇编
FORCEJMP = False
PASSED = [0] * len(code)
ASMS = [None] * len(code)
while True:
if PC > limit: DEBUG, ASM = False, True # 限制汇编输出范围
else: DEBUG, ASM = False, False
PC_1 = PC
op = code[PC]
if op == 0x01:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"jmp\\t{hex(PC)}"
elif op == 0x02:
if ZFLAG == False or FORCEJMP == True:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"jz\\t{hex(PC)}"
else:
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"jz\\t{hex(b2nle(code[PC+1:PC+3], 2)[0])}"
PC += 3
elif op == 0x03:
if ZFLAG == True or FORCEJMP == True:
PC = b2nle(code[PC+1:PC+3], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"jnz\\t{hex(PC)}"
else:
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"jnz\\t{hex(b2nle(code[PC+1:PC+3], 2)[0])}"
PC += 3
elif op == 0x11:
LOTAG = b2nle(code[PC+1:PC+3], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"mov_imm\\tLOTAG\\t{hex(LOTAG)}"
PC += 3
elif op == 0x12:
HITAG = b2nle(code[PC+1:PC+3], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"mov_imm\\tHITAG\\t{hex(HITAG)}"
PC += 3
elif op == 0x15:
regid = code[PC+1]
regs[regid] = b2nle(code[LOTAG:LOTAG+8], 8)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"load\\tcode[{hex(LOTAG)}]\\treg[{regid}]"
PC += 2
elif op == 0x16:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] = IMM
ZFLAG = regs[REG_B] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"mov_imm\\treg[{regid}]\\t{hex(IMM)}"
PC += 10
elif op == 0x17:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] = regs[REG_B]
ZFLAG = regs[REG_A] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"mov\\treg[{REG_A}]\\treg[{REG_B}]"
PC += 3
elif op == 0x18:
regid = code[PC+1]
addr = LOTAG + b2nle(code[PC+2:PC+4], 2)[0]
regs[regid] = b2nle(code[addr:addr+8], 8)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"load\\treg[{regid}]\\tcode[{hex(addr)}]"
PC += 4
elif op == 0x19:
regid = code[PC+1]
code[LOTAG:LOTAG+8] = n2ble([regs[regid]], 8)
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"write\\tcode[{hex(LOTAG)}]\\treg[{regid}]"
PC += 2
elif op == 0x1A:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = LOTAG + regs[REG_B]
regs[REG_A] = code[addr]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"load\\treg[{REG_A}]\\tcode[LOTAG + reg[{REG_B}]]"
PC += 3
elif op == 0x1B:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = LOTAG + regs[REG_B]
code[addr] = regs[REG_A] & 0xFF
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"write\\tcode[LOTAG + reg[{REG_B}]]\\treg[{REG_A}]"
PC += 3
elif op == 0x1C:
regid = code[PC+1]
regs[regid] += 1
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"inc\\treg[{regid}]"
PC += 2
elif op == 0x1D:
regid = code[PC+1]
regs[regid] -= 1
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"dec\\treg[{regid}]"
PC += 2
elif op == 0x1E:
regid = code[PC+1]
IMM8 = code[PC+2]
regs[regid] = regs[regid] >> IMM8
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"shr\\treg[{regid}]\\t{hex(IMM8)}"
PC += 3
elif op == 0x1f:
regid = code[PC+1]
LOTAG += regs[regid] & 0xFFFF
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"add\\tLOTAG\\tregs[{regid}]"
PC += 2
elif op == 0x25:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] &= regs[REG_B]
ZFLAG = regs[REG_A] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"and\\treg[{REG_A}]\\treg[{REG_B}]"
PC += 3
elif op == 0x26:
REG_A = code[PC+1]
REG_B = code[PC+2]
regs[REG_A] ^= regs[REG_B]
ZFLAG = regs[REG_A] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"xor\\treg[{REG_A}]\\treg[{REG_B}]"
PC += 3
elif op == 0x27:
regid = code[PC+1]
IMM8 = code[PC+2]
regs[regid] = (regs[regid] << IMM8) & 0xFFFFFFFFFFFFFFFF
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"shl\\treg[{regid}]\\t{hex(IMM8)}"
PC += 3
elif op == 0x29:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] ^= IMM
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"xor_imm\\treg[{regid}]\\t{hex(IMM)}"
PC += 10
elif op == 0x2A:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
regs[regid] &= IMM
ZFLAG = regs[regid] == 0
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"and_imm\\treg[{regid}]\\t{hex(IMM)}"
PC += 10
elif op == 0x2B:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = HITAG + regs[REG_B]
regs[REG_A] = code[addr]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"load\\treg[{REG_A}]\\tcode[HITAG + reg[{REG_B}]]"
PC += 3
elif op == 0x2C:
REG_A = code[PC+1]
REG_B = code[PC+2]
addr = HITAG + regs[REG_B]
code[addr] = regs[REG_A] & 0xFF
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"write\\tcode[HITAG + reg[{REG_B}]]\\treg[{REG_A}]"
PC += 3
elif op == 0x32:
regid = code[PC+1]
IMM = b2nle(code[PC+2:PC+10], 8)[0]
ZFLAG = regs[regid] == IMM
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"cmp_imm\\treg[{regid}]\\t{hex(IMM)}"
PC += 10
elif op == 0x80:
STAKPC = PC + 1
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"svstk\\t{hex(STAKPC)}"
PC += 1
elif op == 0x81:
mem[code[PC+1]] = STAKPC + 3
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"push\\tmem[{hex(code[PC+1])}]\\t{hex(STAKPC+3)}"
PC += 2
elif op == 0x82:
addr = mem[code[PC+1]]
RETADDR = PC + 2
HIPC -= 2
code[HIPC:HIPC+2] = n2ble([RETADDR], 2)
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"call\\t{hex(addr)}"
PC = addr
elif op == 0x83:
addr = b2nle(code[HIPC:HIPC+2], 2)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"retn\\t{hex(addr)}"
HIPC += 2
PC = addr
elif op == 0x84:
regid = code[PC+1]
HIPC -= 8
code[HIPC:HIPC+8] = n2ble([regs[regid]], 8)
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"pushq\\tcode[{hex(HIPC & 0xFFFF)}]\\treg[{regid}]"
PC += 2
elif op == 0x85:
regid = code[PC+1]
regs[regid] = b2nle(code[HIPC:HIPC+8], 8)[0]
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"popq\\treg[{regid}]\\tcode[{hex(HIPC & 0xFFFF)}]"
HIPC += 8
PC += 2
elif op == 0x90:
addr = code[PC+1]
stdout += chr(addr)
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"stdout"
PC += 2
elif op == 0xFF:
if ASM and PASSED[PC_1] == 0: PASSED[PC_1] = 1; ASMS[PC_1] = f"exit"
break
else:
if DEBUG: print(f"Unknown OP {hex(op)} at {hex(PC)}")
break
tracelen += 1
PASSED.append(PC_1)

if not auto:
for i in range(len(ASMS)):
if ASMS[i] is not None: print(f"{hex(i)}\\t{ASMS[i]}")

return ASMS, code, HIPC, stdout

limit = 0x5A4
inps = [0xba610b6c5d80c91a, 0xbf11b34d0ce941cc, 0xa28f38bd0463522c, 0x79ed5d84199dd9cb, 0x4d9c56b2a1d77a0d,
0xfe13c54ceb12fea8, 0x494a63fc85b9953a, 0xad1f6be84bbb4680, 0xcd05f91609d653fa, 0x55493aa141fbe86f,
0x25bc9aff736b80a8, 0xd8817dda43824d2c, 0x5fcca9a9cb65130d, 0x6f3ed35da24dacfa, 0xb5e1534e1dc36c87,
0xac1b4e2750778a01, 0xc8f82d07316dcd3b, 0x36646367b78c2f91, 0x9eed7637cd5eaa26, 0xff546a0085041459,
0xef320f9e6ae31520, 0x1e00a4b9e25488f6, 0x1a9a0626a035fb9d, 0xe2f1eb0e5248cd2c, 0x8a0bf5239eed75c4,
0x749e8082db34037d, 0xf4d25540ed584887, 0xc12422512500c887, 0x7e1a125dcfa56359, 0x497cff13eaa5bf76,
0xd51ceddab7795459, 0xa922933b0b315a10, 0xcabd557ffa1df043, 0xe0459b855188d045, 0x82700d6f6a986873,
0xc01552dff3a12f67, 0x0615548ece7312fb, 0x0e189fa829657913, 0x8d4c8f2124957228, 0x451572c65bcb3425,
0x554fca602792e879, 0x4f749f6bbca2014c, 0xb1e1adc831c8d567, 0x9c73a6d3f711e66e, 0x2ab305ec4e07b0b4,
0x98a16d274bb044d2, 0xc409de0e72c1029e, 0x5e68e47d3a360a80, 0xa1570f48caceb3dd, 0xd6ab1c9a18ebb936]

# trace_vm(inps, limit) # 输出非函数部分的trace
# run_vm(inps, 0) # 输出整个文件的反汇编

# 输出所有校验的反编译
# ASMS, code, HIPC, stdout = run_vm(inps, limit, True)
# all_inst = extr_all(ASMS)
# parse(all_inst)

# 自动化逆向
start = time.time()
inps = [0] * 50
while 0 in inps:
ASMS, code, HIPC, stdout = run_vm(inps, limit, True)
inst = extr(ASMS)
limit = b2nle(code[HIPC:HIPC+2], 2)[0]
dec = solve(inst)
inps[inst[0]] = dec
print(f"[{(time.time()-start):9.4f}s] inps[0x{inst[0]:02X}] = 0x{dec:016X}")
end = time.time()
print(f"Completed. Total time: {(end-start):9.4f}s.")
printhex(inps)
print("Now getting the flag...")
ASMS, code, HIPC, stdout = run_vm(inps, limit, True)
print(stdout)

RCTF{VM_ALU_SMC_RC4_SPECK!_593eb6079d2da6c187ed462b033fee34}

Determinism

程序数据段中存在12M的被加密小函数,flag校验逻辑围绕这数万个函数展开,很像flare-on 12的#5 ntfsm那题.

程序主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall main(int a1, char **a2, char **a3)
{
if ( a1 <= 1 )
{
__printf_chk(1, "usage: %s path.txt\\n", *a2);
return 1;
}
else
{
readfile(a2[1]);
readflag();
parse();
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
int __fastcall readfile(const char *filename)
{
stream_1 = fopen(filename, "r");
if ( !stream_1 )
goto LABEL_13;
stream = stream_1;
v3 = file;
LABEL_8:
v4 = (_DWORD)v3 - (unsigned int)file;
while ( 1 )
{
n10 = fgetc(stream);
if ( n10 == -1 )
break;
if ( v3 == &byte_F64B9F )
{
byte_F64B9F = 0;
result = fclose(stream);
n1023 = 1023;
return result;
}
if ( n10 != 10 && n10 != 13 )
{
if ( ((*__ctype_b_loc())[n10] & 0x800) == 0 ) //只允许0-9
goto LABEL_13;
*v3++ = n10;
goto LABEL_8;
}
}
file[v4] = 0;
result = fclose(stream);
n1023 = (_DWORD)v3 - (unsigned int)file;
if ( !v4 )
LABEL_13:
exit(1);
return result;
}

存储到长度1023的全局数组file中,又读了一次flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 readflag()
{
v4 = __readfsqword(0x28u);
__printf_chk(1, " input flag: ");
if ( !fgets(s_, 256, stdin) || (s_[strcspn(s_, "\\r\\n")] = 0, strlen(s_) != 17) )
LABEL_7:
exit(1);
for ( n17 = 0; n17 != 17; ++n17 )
{
v1 = s_[n17];
if ( (unsigned __int8)(v1 - 32) > 0x5Fu )
goto LABEL_7;
flag[n17] = v1;
}
return v4 - __readfsqword(0x28u);
}

flag长度17,之后开始处理:

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
unsigned __int64 parse()
{
p_file = (unsigned __int8 *)file;
v18 = __readfsqword(0x28u);
p_buf = buf;
memset(buf, 0, sizeof(buf));
LODWORD(buf[2]) = 1;
for ( i = 1; ; i = buf[2] )
{
v3 = i - 1;
src = func_A_off[v3];
if ( !src )
{
puts("Missed the Jump!");
exit(0);
}
n16 = 0;
*(_OWORD *)v17 = 0;
do
{
v17[n16] = ~key1[n16];
++n16;
}
while ( n16 != 16 );
n = 0;
src_1 = TEA_DEC(src, func_A_size[v3], (__int64)v17, &n);
if ( (int)Run_Func_A(src_1, n, (__int64)buf, (__int64)flag) < 0 )
break;
if ( HIDWORD(buf[0]) == 2 )
{
__printf_chk(1, "running code error \\n");
return v18 - __readfsqword(0x28u);
}
v7 = LODWORD(buf[2]) - 1;
src_2 = func_B_off[v7];
if ( !src_2 )
{
puts("Empty Path");
exit(0);
}
for ( n16_1 = 0; n16_1 != 16; ++n16_1 )
v17[n16_1] = ~key2[n16_1];
n = 0;
src_3 = AES_DEC(src_2, func_B_size[v7], v17, &n);
Run_Func_B(src_3, n, (__int64)buf, buf[2], *p_file);
if ( LODWORD(buf[0]) == 2 )
{
if ( buf[1] == TARGET_17E1 )
{
if ( !LODWORD(buf[131]) )
goto LABEL_24;
v12 = 0x14650FB0739D0383LL;
do
{
v13 = *((int *)p_buf + 6);
p_buf = (__int64 *)((char *)p_buf + 4);
v14 = v12 ^ v13;
v12 = 0x100000001B3LL * v14;
}
while ( (__int64 *)((char *)buf + 4 * SLODWORD(buf[131])) != p_buf );
if ( v14 == 0x18C3D466784B5624LL
&& (flag[14] ^ (unsigned __int8)(((unsigned __int8)flag[4] >> 1) + 2 * flag[0])) == -48 )
{
__printf_chk(1, "you get flag %s", flag);
putc(10, stdout);
}
else
{
LABEL_24:
__printf_chk(1, "final check failed!\\n");
}
}
return v18 - __readfsqword(0x28u);
}
if ( ++p_file == (unsigned __int8 *)&dword_F64BA0 )
return v18 - __readfsqword(0x28u);
}
return v18 - __readfsqword(0x28u);
}

程序的data段有两组函数,前半组(A,0x1F2D000x933C88)用TEA加密,后半组(B,0x9D87C00xF308E0)用AES加密,密钥分别为取反的key1(Jump_h1gh_2_sky!)和key2(L1nk_2_th3_IN3T!),直接用hrtng解密,patch回源程序,然后批量创建函数:

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
import ida_ida, ida_idaapi, ida_bytes, ida_funcs
import ida_ua
import ida_name
import idaapi

pattern = b"\\xF3\\x0F\\x1E\\xFA"
BADADDR = idaapi.BADADDR
REGIONS = [(0x1F2D00, 0x933C88),(0x9D87C0, 0xF308E0)]
count_A = 1
count_B = 1

def clear_region_if_needed(start_ea, end_ea):
need_clear = False
ea = start_ea
while ea < end_ea:
flags = ida_bytes.get_full_flags(ea)
if ida_bytes.is_code(flags) or ida_funcs.get_func(ea) is not None:
need_clear = True
break
ea = ida_bytes.next_head(ea, end_ea)
if ea == BADADDR: break
if not need_clear:
print(f"[+] No Code in Region 0x{start_ea:X}-0x{end_ea:X}")
return
print(f"[*] Clearing 0x{start_ea:X}-0x{end_ea:X}...")
f = ida_funcs.get_next_func(start_ea)
while f and f.start_ea < end_ea:
next_f = ida_funcs.get_next_func(f.start_ea)
print(f" - delfunc 0x{f.start_ea:X}")
ida_funcs.del_func(f.start_ea)
f = next_f
ida_bytes.del_items(start_ea, ida_bytes.DELIT_SIMPLE, end_ea - start_ea)
print(f"[+] Cleared 0x{start_ea:X}-0x{end_ea:X}")

def process_region(start_ea, end_ea):
global count_A, count_B
ea = ida_bytes.find_bytes(pattern, start_ea, range_end=end_ea)
while ea != BADADDR and ea < end_ea:
flags = ida_bytes.get_full_flags(ea)
if ida_bytes.is_unknown(flags):
ida_ua.create_insn(ea)
ida_funcs.add_func(ea)
print(f"Created function at 0x{ea:X}")
if ea < 0x960000:
name = f"func_A_{count_A}"
count_A += 1
else:
name = f"func_B_{count_B}"
count_B += 1
ida_name.set_name(ea, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK)
print(f"Renamed 0x{ea:X} to {name}")
ea = ida_bytes.find_bytes(pattern, ea + 1, range_end=end_ea)

for idx, (r_start, r_end) in enumerate(REGIONS):
clear_region_if_needed(r_start, r_end)
process_region(r_start, r_end)

修复诡异跳转导致的func_A中代码混乱问题:

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
import ida_ida, ida_idaapi, ida_bytes, ida_funcs
import ida_ua
import ida_name
import idaapi
import ida_idp

pattern = b"\\xF3\\x0F\\x1E\\xFA"
BADADDR = idaapi.BADADDR
REGION = (0x1F2D00, 0x933C88)
count_A = 1

def fix_sub_funcs_in_A():
A_START, A_END = REGION
sub_funcs = []
f = ida_funcs.get_next_func(A_START)
while f and f.start_ea < A_END:
fname = ida_funcs.get_func_name(f.start_ea) or ""
if fname.startswith("sub_"):
sub_funcs.append(f)
f = ida_funcs.get_next_func(f.start_ea)
print(f"[*] found {len(sub_funcs)} funcs")
for sub_f in sub_funcs:
sub_start = sub_f.start_ea
sub_end = sub_f.end_ea
last_ret = BADADDR
last_ret_end = BADADDR
ea = sub_start
while ea < sub_end:
insn = ida_ua.insn_t()
size = ida_ua.decode_insn(insn, ea)
if size <= 0:
ea += 1
continue
if ida_idp.is_ret_insn(insn):
last_ret = ea
last_ret_end = ea + size
ea += size
if last_ret == BADADDR:
print(f"[!] func 0x{sub_start:X} has no retn")
continue
prev = ida_funcs.get_prev_func(sub_start)
target = None
while prev and prev.start_ea >= A_START:
pname = ida_funcs.get_func_name(prev.start_ea) or ""
if pname.startswith("func_A_"):
target = prev
break
prev = ida_funcs.get_prev_func(prev.start_ea)
if not target:
print(f"[!] No func_A above sub func 0x{sub_start:X}")
continue
target_start = target.start_ea
target_name = ida_funcs.get_func_name(target_start) or ""
print(f"[*] Merge sub_0x{sub_start:X} to {target_name} (0x{target_start:X}), new end=0x{last_ret_end:X}")
ida_funcs.del_func(sub_start)
ida_funcs.del_func(target_start)
if not ida_funcs.add_func(target_start, last_ret_end):
print(f"[!] Make function {target_name} @0x{target_start:X} failed")
continue
ida_name.set_name(target_start, target_name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK)

fix_sub_funcs_in_A()

统计A和B的指令长度:

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
# -*- coding: utf-8 -*-
import ida_funcs
import ida_ua
import ida_name
import ida_bytes
from collections import defaultdict

output_file_path = r"E:/CTF/temp/basic_data.txt"
funcA_instruction_count = defaultdict(int)
funcB_instruction_count = defaultdict(int)

def get_instruction_mnem(ea): return ida_ua.print_insn_mnem(ea)

with open(output_file_path, "w", encoding="utf-8") as f:
func = ida_funcs.get_next_func(0)
while func:
func_name = ida_funcs.get_func_name(func.start_ea)
func_start = func.start_ea
func_end = func.end_ea
if not (func_name.startswith("func_A_") or func_name.startswith("func_B_")):
func = ida_funcs.get_next_func(func.start_ea)
continue
ea = func_start
endbr_addr = None
while ea < func_end and ea != idaapi.BADADDR:
mnem = get_instruction_mnem(ea)
if mnem == "endbr64":
endbr_addr = ea
break
size = ida_bytes.get_item_size(ea)
if size <= 0: ea += 1
else: ea += size
if endbr_addr is None:
func = ida_funcs.get_next_func(func.start_ea)
continue
instruction_count = 0
current = endbr_addr
found_retn = False
while current < func_end and current != idaapi.BADADDR:
mnem = get_instruction_mnem(current)
size = ida_bytes.get_item_size(current)
if size <= 0:
current += 1
continue
instruction_count += 1
if mnem in ("retn", "ret"):
found_retn = True
break
current += size
if not found_retn:
func = ida_funcs.get_next_func(func.start_ea)
continue
if func_name.startswith("func_A_"):
funcA_instruction_count[instruction_count] += 1
f.write(f"{func_name} {func_start:08X} {instruction_count:08d}\\n")
elif func_name.startswith("func_B_"):
funcB_instruction_count[instruction_count] += 1
f.write(f"{func_name} {func_start:08X} {instruction_count:08d}\\n")
func = ida_funcs.get_next_func(func.start_ea)
f.write("\\n[*] 汇总:\\n")
f.write("[*] func_A 指令条数统计:\\n")
for count, qty in sorted(funcA_instruction_count.items()): f.write(f"指令条数 {count}: {qty} 个函数\\n")
f.write("[*] func_B 指令条数统计:\\n")
for count, qty in sorted(funcB_instruction_count.items()): f.write(f"指令条数 {count}: {qty} 个函数\\n")
print(f"[+] 数据已输出到 {output_file_path}")

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[*] 汇总:
[*] func_A 指令条数统计:
指令条数 56: 18868 个函数
指令条数 68: 1 个函数
指令条数 70: 9 个函数
指令条数 71: 367 个函数
指令条数 72: 2469 个函数
指令条数 75: 1 个函数
指令条数 76: 10 个函数
指令条数 77: 21 个函数
指令条数 78: 28 个函数
指令条数 79: 1086 个函数
指令条数 80: 3303 个函数
指令条数 81: 2 个函数
指令条数 82: 8 个函数
指令条数 95: 395 个函数
指令条数 96: 2560 个函数
[*] func_B 指令条数统计:
指令条数 81: 19457 个函数

注意到B的长度均相同,而A的长度略有差异,先观察函数B的特征:

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
__int64 __fastcall func_B_1(_DWORD *a1, __int64 a2, char a3)
{
v5 = __readfsqword(0x28u);
if ( (a3 & 1) != 0 )
{
if ( !*a1 )
*a1 = 1;
}
else if ( *a1 == 1 )
{
*a1 = 2;
n38914_1 = 0xFFFFFFFFLL;
goto LABEL_12;
}
if ( (a3 & 2) != 0 )
n38914 = -1;
else
n38914 = 38914;
if ( n38914 == -1 )
{
*a1 = 2;
n38914_1 = 0xFFFFFFFFLL;
}
else
{
a1[4] = n38914;
++a1[5];
n38914_1 = n38914;
}
LABEL_12:
if ( v5 != __readfsqword(0x28u) )
return MEMORY[0xFFFFFFFFFFDDC722]();
return n38914_1;
}

__int64 __fastcall func_B_2(_DWORD *a1, __int64 a2, char a3)
{
v5 = __readfsqword(0x28u);
if ( (a3 & 1) != 0 )
{
if ( !*a1 )
*a1 = 1;
}
else if ( *a1 == 1 )
{
*a1 = 2;
n38913_1 = 0xFFFFFFFFLL;
goto LABEL_10;
}
if ( (a3 & 2) != 0 )
n38913 = 38913;
else
n38913 = 38912;
a1[4] = n38913;
++a1[5];
n38913_1 = n38913;
LABEL_10:
if ( v5 != __readfsqword(0x28u) )
return MEMORY[0xFFFFFFFFFFDDC95D]();
return n38913_1;
}

__int64 __fastcall func_B_8(_DWORD *a1, __int64 a2, char a3)
{
v4 = __readfsqword(0x28u);
if ( (a3 & 1) != 0 )
{
if ( !*a1 )
*a1 = 1;
}
else if ( *a1 == 1 )
{
*a1 = 2;
result = 0xFFFFFFFFLL;
goto LABEL_7;
}
*a1 = 2;
result = 0xFFFFFFFFLL;
LABEL_7:
if ( v4 != __readfsqword(0x28u) )
return MEMORY[0xFFFFFFFFFFDDD6BF]();
return result;
}

可以看到,funcB会根据某种条件从两个候选值中选择一个return,有的时候return的两个值都是合法值,有的时候则只有1个甚至无合法值,注意到这两个值位于funcB的偏移量0x7B((a3 & 2) != 0)和0x74((a3 & 2) == 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
# -*- coding: utf-8 -*-
import ida_funcs
import idc
import os

output_path = r"E:/CTF/temp/func_B_branches.txt"

def get_imm_from_mov(ea):
if idc.print_insn_mnem(ea) != "mov": return None
op1_type = idc.get_operand_type(ea, 1)
if op1_type == idc.o_imm: return idc.get_operand_value(ea, 1)
return None

with open(output_path, "w", encoding="utf-8") as f:
func = ida_funcs.get_next_func(0)
while func:
func_name = ida_funcs.get_func_name(func.start_ea)
if not func_name or not func_name.startswith("func_B_"):
func = ida_funcs.get_next_func(func.start_ea)
continue
if idc.print_insn_mnem(func.start_ea) != "endbr64":
func = ida_funcs.get_next_func(func.start_ea)
continue
addr1 = func.start_ea + 0x74
addr2 = func.start_ea + 0x7B
imm1 = get_imm_from_mov(addr1)
imm2 = get_imm_from_mov(addr2)
if imm1 is not None and imm2 is not None:
val1 = imm1 & 0xFFFFFFFF
val2 = imm2 & 0xFFFFFFFF
f.write(f"{func_name} {val2} {val1}\\n")
func = ida_funcs.get_next_func(func.start_ea)
print(f"[+] func_B 分支数据已输出到: {output_path}")

提取后发现B中的合法值均在38914~2(倒序,无1和0),且其他值被替换成了-1.

结合整个程序的二进制结构:

地址 大小 / 项数 作用 / 描述
0x11E2C0 - 0x12007D (text) - text段
0x121088 - 0x17FEE0 (sizes) 48587 记录了funcA和funcB加密块的大小
0x182040 - 0x1CE050 (func_A_size) 38914 稀疏存储了每个func_A加密块的大小
0x1F3D00 - 0x934C84 (func_A) 29130 func_A
0x934CA0 - 0x980CB0 (func_A_off) 38914 稀疏倒序存储了每个func_A的地址
0x9A6960 - 0x9D97C0 (func_B_size) 26057 稀疏存储了每个func_B加密块的大小
0x9D97C0 - 0xF318DA (func_B) 19457 func_B
0xF318E0 - 0xF64728 (func_B_off) 26057 稀疏倒序存储了每个func_B的地址

分析func_B中的几个指针,和任意一个func_A的结构:

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
__int64 __fastcall func_A_1(_DWORD *a1)
{
if ( *a1 == 1 )
{
*((_QWORD *)a1 + 1) += 1111LL; // func_A全局权重和
if ( a1[262] <= 0x3FFu )
{
v1 = a1[262];
a1[262] = v1 + 1;
a1[v1 + 6] = 1111;
}}
if ( *a1 ){if ( *a1 == 1 ) {a1[1] = 2;}}
else { a1[1] = 0;}
a1[4] = 0x9802;
return 0;
}
// ↓ 简化为
__int64 __fastcall func_A_1(_DWORD *a1)
{
if ( *a1 == 1 )
{
*((_QWORD *)a1 + 1) += 1111LL; // func_A全局权重和
if ( a1[262] <= 0x3FFu )
{
v1 = a1[262];
a1[262] = v1 + 1;
a1[v1 + 6] = 1111;
}
a1[1] = 2;
}
else { a1[1] = 0;}
a1[4] = 0x9802;
return 0;
}

__int64 __fastcall func_A_4(_DWORD *a1, unsigned __int8 *a2) // a2是flag
{
if ( *a1 == 1 )
{
*((_QWORD *)a1 + 1) += 7276LL;
if ( a1[262] <= 0x3FFu )
{
v2 = a1[262];
a1[262] = v2 + 1;
a1[v2 + 6] = 7276;
}
}
if ( *a1 )
{
if ( *a1 == 1 )
{
if ( (a2[11] + a2[4]) * a2[9] * a2[2] == 119 )
a1[1] = 1;
else
a1[1] = 2;
}
}
else
{
a1[1] = 0;
}
a1[4] = 38911;
return 0;
}

结合parse的处理逻辑,整体推断,注意到parse中的buf和func_A、func_B的第一个参数是一个巨大的结构体,其组成为:

1
2
3
4
5
6
7
0x000 int func_A_state; // A的运行状态,为1时运行其内部逻辑,为0时不运行
0x004 int func_A_result; // A的返回状态,不运行时为0,运行时,若其内部无比较flag的逻辑,则为2;若有,若正确则为1,不正确则为2
0x008 long long func_A_weight; // A的权重,最终加和要等于0x17E1
0x010 int func_index; // A/B共用的func索引,是稀疏偏移量表中的索引
0x014 int Total_func_B_Called; // B的调用次数
0x018 int func_A_weight_hash[256]; // 一个256项的int数组,统计了经过的所有A的权重
0x418 int Total_func_A_Called; // A的调用次数

A和B的实际逻辑是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def func_A_<id> (Stucture, flag):                          # flag约束逻辑
if func_A_state == 1: # 如果执行内部代码
func_A_weight += <weight> # 权重总和更新
if Total_func_A_Called <= 0x3FF:
func_A_weight_hash[Total_func_A_Called] = <weight> # 权重列表更新
Total_func_A_Called += 1 # 调用次数+1
if <assert flag... == ...>: func_A_result = 1 # 校验flag / 不校验直接返回2(死节点)
else: func_A_result = 2
else:
func_A_result = 0 # 不执行,转到func_B
func_index = <idx in func_A_off> # func_A全过程都没修改过idx,此处为多余操作
return 0

def func_B_<id> (Stucture, func_index, file[i]): # 控制流修改逻辑
if (file[i] & 0b01) != 0: # 如果最低位为1(1、3、5、7、9)
if func_A_state == 0: func_A_state = 1 # 如果func_A没有激活,则激活func_A(注意没有取消激活)
elif func_A_state == 1: # 如果最低位不为1(0、2、4、6、8)且A已激活
func_A_state = 2 # 将func_A_state改为2,进入最终校验逻辑(结束处理循环)
return 0xFFFFFFFF
if (file[i] & 0b10) != 0: func_index = <num at 0x7B> # 如果第二位为1(2、3、6、7),更新func_index为0x7B处的值
else: func_index = <num at 0x74> # 如果第二位不为1(0、1、4、5、8、9),更新func_index为0x74处的值
Total_func_B_Called += 1 # 调用次数+1
res = next_idx
return res

处理逻辑为:初始化index为1(实际上指向稀疏偏移量表[0])→ parse循环体 → func_A_29130(0) → 返回index 1 → func_B_19457 此时文件数据开始生效,我们可以控制先不激活A,让index增加一会,然后从某个时机开始激活A并逐步通过约束flag,最终在B改状态位为2进入最终校验.

提取A的权重:

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
# -*- coding: utf-8 -*-
import ida_funcs
import idc
import os

output_path = r"E:/CTF/temp/func_A_weights.txt"

def get_imm_from_mov_to_rbp_offset(ea):
if idc.print_insn_mnem(ea) != "mov": return None
op0_type = idc.get_operand_type(ea, 0)
if op0_type != idc.o_displ: return None
op1_type = idc.get_operand_type(ea, 1)
if op1_type == idc.o_imm: return idc.get_operand_value(ea, 1)
return None

with open(output_path, "w", encoding="utf-8") as f:
func = ida_funcs.get_next_func(0)
while func:
func_name = ida_funcs.get_func_name(func.start_ea)
if not func_name or not func_name.startswith("func_A_"):
func = ida_funcs.get_next_func(func.start_ea)
continue
if idc.print_insn_mnem(func.start_ea) != "endbr64":
func = ida_funcs.get_next_func(func.start_ea)
continue
weight_insn_ea = func.start_ea + 0x10
weight = get_imm_from_mov_to_rbp_offset(weight_insn_ea)
if weight is not None:
weight &= 0xFFFFFFFF
f.write(f"{func_name} {weight}\\n")
else: print(f"[!] Failed to extract weight from {func_name} at {hex(weight_insn_ea)}")
func = ida_funcs.get_next_func(func.start_ea)
print(f"[+] func_A 权重数据已输出到: {output_path}")

接下来进行权重和约束,先提取func_A_off和func_B_off:

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

def extract_uint64s_to_hex(input_file, offset, count, output_file):
with open(input_file, 'rb') as f:
f.seek(offset)
data = f.read(8 * count)
values = []
for i in range(count):
val = struct.unpack_from('<Q', data, i * 8)[0]
hex_str = f'0x{val:08X}'
values.append(hex_str)
with open(output_file, 'w') as out_f:
out_f.write('\\n'.join(values))
out_f.write('\\n')

input_bin = 'Determinism'
extract_uint64s_to_hex(
input_file=input_bin,
offset=0x933CA0,
count=38914,
output_file='func_A_off.txt'
)
extract_uint64s_to_hex(
input_file=input_bin,
offset=0xF308E0,
count=26057,
output_file='func_B_off.txt'
)

接下来映射为func编号:

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
def build_address_to_func_map(basic_file, num_lines=48587):
addr_to_func = {}
with open(basic_file, 'r', encoding='utf-8') as f:
for i, line in enumerate(f):
if i >= num_lines: break
parts = line.strip().split()
if len(parts) < 2: continue
func_name = parts[0]
addr_hex = parts[1].upper().zfill(8)[-8:]
addr_to_func[addr_hex] = func_name
return addr_to_func

def resolve_offsets(off_file, addr_map, output_file):
found = 0
not_found = 0
results = []
with open(off_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line: continue
try: full_val = int(line, 16)
except ValueError:
results.append("ERROR")
not_found += 1
continue
low32 = full_val & 0xFFFFFFFF
addr_key = f"{low32:08X}"
func_name = addr_map.get(addr_key, "ERROR")
results.append(func_name)
if func_name == "ERROR": not_found += 1
else: found += 1

with open(output_file, 'w', encoding='utf-8') as out_f:
out_f.write('\\n'.join(results))
out_f.write('\\n')
return found, not_found

addr_map = build_address_to_func_map('basic_data.txt', num_lines=48587)
found_a, not_found_a = resolve_offsets('func_A_off.txt', addr_map, 'off_A.txt')
print(f"func_A: 找到 {found_a} 个,未找到 {not_found_a} 个")
found_b, not_found_b = resolve_offsets('func_B_off.txt', addr_map, 'off_B.txt')
print(f"func_B: 找到 {found_b} 个,未找到 {not_found_b} 个")

编写脚本求解约束:

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
import sys
from collections import defaultdict

TARGET = 0x17E1
HASH_TARGET = 0x18C3D466784B5624

def fnv1_hash(weights):
v12 = 0x14650FB0739D0383
for weight in weights:
v14 = v12 ^ weight & 0xFFFFFFFFFFFFFFFF # 这里拓展到64位了
v12 = (0x100000001B3 * v14) & 0xFFFFFFFFFFFFFFFF
return v14

addr_to_A = {}
addr_to_B = {}
print("Loading basic_data.txt...")
with open("basic_data.txt", "r", encoding='utf-8') as f: lines = f.readlines()
for i, line in enumerate(lines):
parts = line.strip().split()
if len(parts) < 3: continue
name, addr_hex, size_hex = parts[0], parts[1], parts[2]
try: size = int(size_hex, 16)
except ValueError: continue
if name.startswith("func_A"): addr_to_A[name] = {"size": size}
elif name.startswith("func_B"): addr_to_B[name] = {"size": size}
print("Loading func_A_weights.txt...")
with open("func_A_weights.txt", "r", encoding='utf-8') as f:
for line in f:
parts = line.strip().split()
if len(parts) < 2: continue
name, w_str = parts[0], parts[1]
try: w_uint = int(w_str)
except ValueError: continue
if w_uint >= 2**31: w_int = w_uint - 2**32
else: w_int = w_uint
if name in addr_to_A: addr_to_A[name]["weight"] = w_int
print("Loading func_B_branches.txt...")
with open("func_B_branches.txt", "r", encoding='utf-8') as f:
for line in f:
parts = line.strip().split()
if len(parts) < 3: continue
name, b1_str, b2_str = parts[0], parts[1], parts[2]
try:
b1 = int(b1_str)
b2 = int(b2_str)
except ValueError: continue
if name in addr_to_B:
addr_to_B[name]["branch1"] = b1 if b1 != 0xFFFFFFFF else -1
addr_to_B[name]["branch2"] = b2 if b2 != 0xFFFFFFFF else -1
def load_off(filename):
lst = []
with open(filename, "r", encoding='utf-8') as f:
for line in f:
s = line.strip()
if s == "ERROR": lst.append(None)
else: lst.append(s)
return lst
print("Loading off_A.txt and off_B.txt...")
off_A = load_off("off_A.txt")
off_B = load_off("off_B.txt")

def get_A_name(func_index):
idx = func_index - 1
if idx < 0 or idx >= len(off_A): return None
return off_A[idx]

def get_B_name(func_index):
idx = func_index - 1
if idx < 0 or idx >= len(off_B): return None
return off_B[idx]

def get_A_info(func_index):
idx = func_index - 1
if idx < 0 or idx >= len(off_A): return None
name = off_A[idx]
if name is None: return None
if name not in addr_to_A: return None
return addr_to_A[name]
def get_B_info(func_index):
idx = func_index - 1
if idx < 0 or idx >= len(off_B): return None
name = off_B[idx]
if name is None: return None
if name not in addr_to_B: return None
return addr_to_B[name]
sys.setrecursionlimit(10000)
valid_paths = []
MAX_DEPTH = 2000
visited = set()
def dfs(step_type, func_index, activation_from, weight, activated_weights, path, depth):
if depth > 200: return
if weight == TARGET:
if activated_weights:
hash_result = fnv1_hash(activated_weights)
if hash_result == HASH_TARGET:
activation_from_name = get_B_name(activation_from) if activation_from else None
path_info = {
'path': path.copy(),
'activated_weights': activated_weights.copy(),
'activation_from': activation_from_name,
'final_weight': weight,
'hash_result': hex(hash_result)
}
valid_paths.append(path_info)
print(f"Found valid path! Steps: {len(path)}, weight={weight}, hash=0x{hash_result:016X}")
print(f"Activation started from: {activation_from_name}")
print(f"Activated weights: {activated_weights}")
return
weights_tuple = tuple(activated_weights)
state = (step_type, func_index, activation_from, weight, weights_tuple)
if state in visited: return
visited.add(state)
if step_type == 'A':
a_info = get_A_info(func_index)
if a_info is None: return
if a_info["size"] == 56: return
a_name = get_A_name(func_index)
new_weight = weight
new_activated_weights = activated_weights.copy()
if activation_from is not None:
w = a_info.get("weight", 0)
new_weight = weight + w
new_activated_weights.append(w)
path.append(f"{a_name}" + ("*" if activation_from is not None else ""))
dfs('B', func_index, activation_from, new_weight, new_activated_weights, path, depth + 1)
path.pop()
else:
b_info = get_B_info(func_index)
if b_info is None: return
b_name = get_B_name(func_index)
b1 = b_info.get("branch1", -1)
b2 = b_info.get("branch2", -1)
next_indices = []
if b1 != -1: next_indices.append(b1)
if b2 != -1: next_indices.append(b2)
if not next_indices: return
path.append(f"{b_name}")
for next_idx in next_indices:
if next_idx <= 0: continue
if activation_from is not None: dfs('A', next_idx, activation_from, weight, activated_weights, path, depth + 1)
else:
dfs('A', next_idx, None, weight, activated_weights, path, depth + 1)
dfs('A', next_idx, func_index, weight, activated_weights, path, depth + 1)
path.pop()
print("Starting DFS with hash constraint...")
dfs('A', 1, None, 0, [], [], 0)
print(f"\\nTotal valid paths found: {len(valid_paths)}")
for i, path_info in enumerate(valid_paths):
print(f"\\nPath {i+1}:")
print(f" Route: {' -> '.join(path_info['path'])}")
print(f" Activation started from: {path_info['activation_from']}")
print(f" Activated weights: {path_info['activated_weights']}")
print(f" Final weight: {path_info['final_weight']}")
print(f" Hash result: {path_info['hash_result']}")

得到唯一解:

1
2
3
4
5
6
7
8
9
10
11
12
Found valid path! Steps: 41, weight=6113, hash=0x18C3D466784B5624
Activation started from: func_B_19446
Activated weights: [5783, -7171, -1733, -3415, 7703, -8204, 2875, 7495, 7641, -183, -7455, 3473, 2466, -17, 2740, -5885]

Total valid paths found: 1

Path 1:
Route: func_A_29130 -> func_B_19457 -> func_A_29129 -> func_B_19456 -> func_A_29127 -> func_B_19454 -> func_A_29124 -> func_B_19451 -> func_A_29119 -> func_B_19446 -> func_A_29110* -> func_B_19437 -> func_A_29098* -> func_B_19425 -> func_A_29078* -> func_B_19405 -> func_A_29046* -> func_B_19373 -> func_A_28997* -> func_B_19324 -> func_A_28922* -> func_B_19249 -> func_A_28814* -> func_B_19141 -> func_A_28650* -> func_B_18977 -> func_A_28403* -> func_B_18730 -> func_A_28037* -> func_B_18364 -> func_A_27475* -> func_B_17802 -> func_A_26636* -> func_B_16963 -> func_A_25362*-> func_B_15689 -> func_A_23492* -> func_B_13819 -> func_A_20708* -> func_B_11035 -> func_A_16548*
Activation started from: func_B_19446
Activated weights: [5783, -7171, -1733, -3415, 7703, -8204, 2875, 7495, 7641, -183, -7455, 3473, 2466, -17, 2740, -5885]
Final weight: 6113
Hash result: 0x18c3d466784b5624

进行约束的A有16个(连同parse刚好17个):

1
2
3
4
func_A_29110* func_A_29098* func_A_29078* func_A_29046*
func_A_28997* func_A_28922* func_A_28814* func_A_28650*
func_A_28403* func_A_28037* func_A_27475* func_A_26636*
func_A_25362* func_A_23492* func_A_20708* func_A_16548*

提取这些func_A中的约束(连同parse):

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
def hashfunc(a, b):
v4 = (((a << 8) | b) - 0x61C88647) & 0xFFFFFFFF
tmp = (0x85EBCA6B * (v4 ^ HIWORD(v4))) & 0xFFFFFFFF
v5 = (0xC2B2AE35 * (tmp ^ (tmp >> 13)))
return v5 & 0xFFFFFFFF

def HIWORD(x): return (x >> 16) & 0xFFFF

def LOWORD(x): return x & 0xFFFF

# func_A_29110
assert flag[6] ^ ((flag[9] >> 1) + 2 * flag[12]) == 0xD9
# func_A_29098
v5 = hashfunc(flag[6], flag[15])
assert HIWORD(v5) ^ LOWORD(v5) == 0x885B
# func_A_29078
assert (flag[3] * (flag[8] + 3)) ^ (2 * flag[5]) == 0xB4
# func_A_29046
assert ((3 * flag[9]) ^ (flag[6] + flag[2])) + 17 == 0x71
# func_A_28997
assert (flag[4] ^ flag[10]) + 2 * flag[5] == 0xF7
# func_A_28922
v5 = hashfunc(flag[10], flag[13])
assert HIWORD(v5) ^ LOWORD(v5) == 0x13DE
# func_A_28814
assert (flag[4] * (flag[7] + 3)) ^ (2 * flag[16]) == 0x25
# func_A_28650 (无效项)
assert not flag[8] == (flag[1] == 3)
# func_A_28403
v5 = hashfunc(flag[11], flag[2])
assert HIWORD(v5) ^ LOWORD(v5) == 0xD9B4
# func_A_28037
assert (flag[10] * (flag[15] + 3)) ^ (2 * flag[11]) == 0x64
# func_A_27475
assert ((3 * flag[11]) ^ (flag[1] + flag[16])) + 17 == 0xF1
# func_A_26636
assert (flag[1] ^ flag[2]) + 2 * flag[16] == 0x0D
# func_A_25362
v5 = hashfunc(flag[0], flag[13])
assert HIWORD(v5) ^ LOWORD(v5) == 0xE64D
# func_A_23492
assert (flag[0] * (flag[7] + 3)) ^ (2 * flag[16]) == 0x91
# func_A_20708
assert flag[14] ^ flag[2] == 0x54
# func_A_16548
v5 = hashfunc(flag[0], flag[2])
assert HIWORD(v5) ^ LOWORD(v5) == 0xC3DD
# parse
assert flag[14] ^ ((flag[4] >> 1) + 2 * flag[0]) == 0xD0

注意到约束是欠定的(17项flag,16个有效约束),求所有解:

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
def HIWORD(x): return (x >> 16) & 0xFFFF

def LOWORD(x): return x & 0xFFFF

def hashfunc(a, b):
v4 = (((b << 8) | a) - 0x61C88647) & 0xFFFFFFFF
tmp = (0x85EBCA6B * (v4 ^ HIWORD(v4))) & 0xFFFFFFFF
v5 = (0xC2B2AE35 * (tmp ^ (tmp >> 13))) & 0xFFFFFFFF
return v5

chars = []
chars.extend(range(ord('A'), ord('Z') + 1))
chars.extend(range(ord('a'), ord('z') + 1))
chars.extend(range(ord('0'), ord('9') + 1))
chars.append(ord('_'))

hash_constraints = [
("func_A_29098", 0x885B, "flag[6], flag[15]"),
("func_A_28922", 0x13DE, "flag[10], flag[13]"),
("func_A_28403", 0xD9B4, "flag[11], flag[2]"),
("func_A_25362", 0xE64D, "flag[0], flag[13]"),
("func_A_16548", 0xC3DD, "flag[0], flag[2]"),
]

hash_solutions = {}

for name, expected, param_desc in hash_constraints:
solutions = []
for a in chars:
for b in chars:
for order, (p1, p2) in enumerate([(a, b), (b, a)]):
v5 = hashfunc(p1, p2)
result = HIWORD(v5) ^ LOWORD(v5)
if result == expected: solutions.append((p1, p2, order))
hash_solutions[name] = solutions

from z3 import *

def is_valid_char(c):
return (
(c >= ord('A') and c <= ord('Z')) or
(c >= ord('a') and c <= ord('z')) or
(c >= ord('0') and c <= ord('9')) or
(c == ord('_'))
)

def solve_all():
flag = [BitVec(f'flag_{i}', 8) for i in range(17)]
s = Solver()
for i in range(17):
char_constraints = Or([flag[i] == c for c in chars])
s.add(char_constraints)
s.add(flag[6] ^ ((flag[9] >> 1) + 2 * flag[12]) == 0xD9) # func_A_29110
s.add((flag[3] * (flag[8] + 3)) ^ (2 * flag[5]) == 0xB4) # func_A_29078
s.add(((3 * flag[9]) ^ (flag[6] + flag[2])) + 17 == 0x71) # func_A_29046
s.add((flag[4] ^ flag[10]) + 2 * flag[5] == 0xF7) # func_A_28997
s.add((flag[4] * (flag[7] + 3)) ^ (2 * flag[16]) == 0x25) # func_A_28814
s.add((flag[10] * (flag[15] + 3)) ^ (2 * flag[11]) == 0x64) # func_A_28037
s.add(((3 * flag[11]) ^ (flag[1] + flag[16])) + 17 == 0xF1) # func_A_27475
s.add((flag[1] ^ flag[2]) + 2 * flag[16] == 0x0D) # func_A_26636
s.add((flag[0] * (flag[7] + 3)) ^ (2 * flag[16]) == 0x91) # func_A_23492
s.add(flag[14] ^ flag[2] == 0x54) # func_A_20708
s.add(flag[14] ^ ((flag[4] >> 1) + 2 * flag[0]) == 0xD0) # parse
def add_hash_constraint(solver, solutions, flag_indices):
if not solutions: return
constraints = []
for sol in solutions:
a, b, order = sol
if order == 0: constraints.append(And(flag[flag_indices[0]] == a, flag[flag_indices[1]] == b))
else: constraints.append(And(flag[flag_indices[0]] == b, flag[flag_indices[1]] == a))
if constraints: solver.add(Or(constraints))
if hash_solutions.get("func_A_29098"): add_hash_constraint(s, hash_solutions["func_A_29098"], [6, 15])
if hash_solutions.get("func_A_28922"): add_hash_constraint(s, hash_solutions["func_A_28922"], [10, 13])
if hash_solutions.get("func_A_28403"): add_hash_constraint(s, hash_solutions["func_A_28403"], [11, 2])
if hash_solutions.get("func_A_25362"): add_hash_constraint(s, hash_solutions["func_A_25362"], [0, 13])
if hash_solutions.get("func_A_16548"): add_hash_constraint(s, hash_solutions["func_A_16548"], [0, 2])
solutions = []
solution_count = 0
print("\nSearching for solutions...")
while True:
result = s.check()
if result == sat:
solution_count += 1
model = s.model()
flag_values = [model[flag[i]].as_long() for i in range(17)]
all_valid = all(is_valid_char(v) for v in flag_values)
flag_string = ''.join([chr(x) for x in flag_values])
solutions.append((flag_values, flag_string))
print(f"Solution {solution_count}: RCTF{{{flag_string}}}")
exclude_constraint = Or([flag[i] != flag_values[i] for i in range(17)])
s.add(exclude_constraint)
else: break

print(f"\nTotal solutions found: {len(solutions)}")
return solutions

all_solutions = solve_all()

print("\n=== All Solutions ===")
for i, (values, string) in enumerate(all_solutions):
print(f"Solution {i+1}: {[chr(v) for v in values]}")
print(f"ASCII values: {values}")
print(f"Flag: RCTF{{{string}}}")
print()

RCTF{Ct1l_fL0w_th3_eNd}