N1CTF Junior 2025 2/2 出题记录

9.3k 词

第一次在大比赛出题,本来出了4个逆向,最后只上了两个,也是Re所有题目里面最难的两个(mmm和loda)

Multiple Magic Matrices

出题记录

第一部分的约束矩阵:灵感来源于朋友发我的一张 5*5 逻辑bingo图,有点像小学那种推断谁说的对谁说的错的脑筋急转弯题,当时发现转为布尔矩阵后用z3进行约束能出结果,遂扩展到 8*8,又加入了一些条件,本意也是想让用AI直接提取约束条件,然后写z3脚本跑出来约束

第二部分的加密算法:来源于不久之前想自己设计的基于洛书幻方的分组加密算法,随便搓的,同构还算简单,没有什么拐弯抹角的加密方式

第三部分的数织:灵感同样来自朋友,随便搓了个字符画当图案用 (后面还为能让加密结果尽量贴近这个图案找了半天的最佳iv值)

WP

main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+48h] [rbp-78h] BYREF
int v5; // [rsp+4Ch] [rbp-74h] BYREF
_BYTE v6[79]; // [rsp+50h] [rbp-70h] BYREF
_BYTE v7[9]; // [rsp+9Fh] [rbp-21h] BYREF
void *Block; // [rsp+A8h] [rbp-18h]
__int64 v9; // [rsp+B0h] [rbp-10h]
__int64 v10; // [rsp+B8h] [rbp-8h]

sub_140004C50(argc, argv, envp);
printf("Please input your key: ");
sub_140002CC3((__int64)v7);
puts("Your target: \n");
sub_1400034C9(
13,
13,
(unsigned int)&unk_140007000,
(unsigned int)&unk_140007220,
(__int64)&unk_140007260,
(__int64)&unk_140007480);
printf("\nYour input?: ");
scanf("%s");
v10 = sub_140002F00(v6, &v5);
if ( !v10 || v5 <= 143 )
return 1;
sub_140003070(v10, (unsigned int)v5, v7);
v9 = v4 - 1LL;
Block = (void *)sub_14000334F(v10, (unsigned int)v5, &v4);
if ( !Block )
return 1;
sub_1400036CC(
v4,
v4,
(_DWORD)Block,
(unsigned int)&unk_140007000,
(__int64)&unk_140007220,
(__int64)&unk_140007260,
(__int64)&unk_140007480);
free(Block);
return 0;
}

先查看对于key的校验:

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
__int64 __fastcall sub_140002CC3(__int64 a1)
{
_QWORD v2[8]; // [rsp+20h] [rbp-60h] BYREF
char v3; // [rsp+60h] [rbp-20h]
unsigned __int64 v4; // [rsp+68h] [rbp-18h]
int k; // [rsp+70h] [rbp-10h]
int j; // [rsp+74h] [rbp-Ch]
int i; // [rsp+78h] [rbp-8h]
int v8; // [rsp+7Ch] [rbp-4h]

if ( (unsigned int)scanf("%llu") != 1 )
{
puts("Invalid input.");
sub_140005840(1);
}
memset(v2, 0, sizeof(v2));
v3 = 0;
sub_140001AAA(v4, (__int64)v2);
v8 = 0;
for ( i = 0; i <= 7; ++i )
{
for ( j = 0; j <= 7; ++j )
matrix[8 * i + j] = *((_BYTE *)v2 + v8++) == 49;
}
if ( (unsigned __int8)sub_140001BDB() != 1 )
{
puts("Wrong key!");
sub_140005840(1);
}
puts("Right key!");
for ( k = 7; k >= 0; --k )
{
*(_BYTE *)(a1 + k) = v4;
v4 >>= 8;
}
*(_BYTE *)(a1 + 8) = -17;
return 0;
}

char *__fastcall sub_140001AAA(unsigned __int64 a1, __int64 a2)
{
char *result; // rax
char v3; // cl
int v4; // eax
int v5; // [rsp+Ch] [rbp-4h]

v5 = 63;
result = (char *)(a2 + 64);
*(_BYTE *)(a2 + 64) = 0;
while ( v5 >= 0 )
{
if ( (a1 & 1) != 0 )
v3 = '1';
else
v3 = '0';
v4 = v5--;
result = (char *)(a2 + v4);
*result = v3;
a1 >>= 1;
}
return result;
}

输入的是一个uint64,在sub_140001AAA转为bit,然后填充入matrix中,查看matrix的校验函数:

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
_BOOL8 sub_140001BDB()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 0;
for ( i = 0; i <= 7; ++i )
{
for ( j = 0; j <= 7; ++j )
{
if ( matrix[8 * i + j] )
{
++*((_DWORD *)&v8 + i);
++*((_DWORD *)&v4 + j);
}
}
}
if ( matrix[12] != 1 )
return 0;
if ( matrix[32] != (int)v6 <= 5 )
return 0;
v68 = 0;
for ( k = 0; k <= 7; ++k )
{
v66 = 1;
for ( m = 0; m <= 7; ++m )
{
if ( matrix[8 * k + m] != 1 )
{
v66 = 0;
break;
}
}
if ( v66 )
{
v68 = 1;
break;
}
}
if ( matrix[12] != v68 )
return 0;
if ( matrix[52] )
return 0;
if ( matrix[2] != SHIDWORD(v4) < (int)v5 )
return 0;
v64 = 0;
for ( n = 0; n <= 7; ++n )
{
v62 = 1;
for ( ii = 0; ii <= 7; ++ii )
{
if ( matrix[8 * ii + n] != 1 )
{
v62 = 0;
break;
}
}
if ( v62 )
{
v64 = 1;
break;
}
}
if ( matrix[4] != v64 )
return 0;
if ( matrix[23] )
return 0;
if ( matrix[25] != SHIDWORD(v9) < SHIDWORD(v4) )
return 0;
v60 = 0;
v59 = 1;
for ( jj = 0; jj <= 7; ++jj )
{
if ( matrix[9 * jj] != 1 )
{
v59 = 0;
break;
}
}
if ( v59 )
v60 = 1;
if ( matrix[33] != 1 )
return 0;
v57 = 1;
for ( kk = 0; kk <= 7; ++kk )
{
if ( matrix[8 * kk + 7 - kk] != 1 )
{
v57 = 0;
break;
}
}
if ( v57 )
v60 = 1;
if ( matrix[20] != v60 )
return 0;
if ( matrix[63] )
return 0;
v55 = 0;
if ( matrix[0] )
++v55;
if ( matrix[7] )
++v55;
if ( matrix[56] )
++v55;
if ( matrix[63] )
++v55;
if ( matrix[43] != 1 )
return 0;
if ( (v55 == 1) != matrix[45] )
return 0;
v12 = sub_140001B0C(1, 0, v3, v2);
v54 = 0;
for ( mm = 0; mm < v12; ++mm )
{
if ( matrix[8 * v3[mm] + v2[mm]] )
++v54;
}
if ( matrix[8] != (v54 % 2 == 1) )
return 0;
if ( matrix[21] )
return 0;
v12 = sub_140001B0C(5, 6, v3, v2);
v52 = 0;
for ( nn = 0; nn < v12; ++nn )
{
if ( matrix[8 * v3[nn] + v2[nn]] )
++v52;
}
if ( matrix[46] != !(v52 & 1) )
return 0;
if ( matrix[25] != 1 )
return 0;
if ( matrix[34] != (int)v4 > 5 )
return 0;
v50 = 0;
for ( i1 = 0; i1 <= 7; ++i1 )
{
for ( i2 = 0; i2 <= 7; ++i2 )
{
v47 = 1;
v12 = sub_140001B0C((unsigned int)i1, (unsigned int)i2, v3, v2);
for ( i3 = 0; i3 < v12; ++i3 )
{
if ( matrix[8 * v3[i3] + v2[i3]] )
{
v47 = 0;
break;
}
}
if ( v47 && v12 > 0 )
{
v50 = 1;
break;
}
}
if ( v50 )
break;
}
if ( (unsigned __int8)matrix[10] != ((unsigned __int8)v50 ^ 1) )
return 0;
if ( matrix[16] != matrix[18] )
return 0;
v45 = 0;
for ( i4 = 0; i4 <= 2; ++i4 )
{
for ( i5 = 0; i5 <= 2; ++i5 )
{
if ( matrix[8 * i4 + i5] )
++v45;
}
}
if ( v45 > 4 != matrix[17] )
return 0;
if ( matrix[48] != SHIDWORD(v7) <= 3 )
return 0;
v42 = 0;
for ( i6 = 0; i6 <= 3; ++i6 )
{
for ( i7 = 0; i7 <= 1; ++i7 )
{
if ( matrix[8 * i6 + i7] )
v42 += 4;
else
v42 += 3;
}
}
if ( v42 <= 3 != matrix[22] )
return 0;
if ( v42 > 7 != matrix[14] )
return 0;
if ( (v55 == 3) != matrix[11] )
return 0;
v39 = 0;
for ( i8 = 0; i8 <= 4; ++i8 )
{
for ( i9 = 0; i9 <= 2; ++i9 )
{
if ( matrix[8 * i8 + i9] )
v39 += 2;
else
v39 += 4;
}
}
if ( v39 <= 2 != matrix[30] )
return 0;
if ( matrix[40] != SHIDWORD(v10) <= 1 )
return 0;
v36 = 0;
for ( i10 = 0; i10 <= 6; ++i10 )
{
for ( i11 = 0; i11 <= 7; ++i11 )
{
if ( matrix[8 * i10 + i11] && matrix[8 * i10 + 8 + i11] )
{
v36 = 1;
break;
}
}
if ( v36 )
break;
}
if ( (unsigned __int8)matrix[18] != ((unsigned __int8)v36 ^ 1) )
return 0;
if ( matrix[43] != (int)v8 < SHIDWORD(v5) )
return 0;
if ( (v55 == 4) != matrix[57] )
return 0;
v33 = 0;
for ( i12 = 1; i12 <= 6; ++i12 )
{
for ( i13 = 1; i13 <= 6; ++i13 )
{
if ( matrix[8 * i12 - 8 + i13]
&& matrix[8 * i12 + 8 + i13]
&& matrix[8 * i12 - 1 + i13]
&& matrix[8 * i12 + 1 + i13]
&& matrix[8 * i12 + i13] != 1 )
{
v33 = 1;
break;
}
}
if ( v33 )
break;
}
if ( (unsigned __int8)matrix[19] != ((unsigned __int8)v33 ^ 1) )
return 0;
if ( matrix[47] != v50 )
return 0;
v30 = 0;
for ( i14 = 0; i14 <= 7; ++i14 )
{
for ( i15 = 0; i15 <= 7; ++i15 )
{
v12 = sub_140001B0C((unsigned int)i14, (unsigned int)i15, v3, v2);
v27 = 0;
for ( i16 = 0; i16 < v12; ++i16 )
{
if ( matrix[8 * v3[i16] + v2[i16]] )
++v27;
}
if ( v27 > 6 )
{
v30 = 1;
break;
}
}
if ( v30 )
break;
}
if ( matrix[24] != v30 )
return 0;
if ( matrix[51] != matrix[61] )
return 0;
v25 = 0;
for ( i17 = 0; i17 <= 6; ++i17 )
{
for ( i18 = 0; i18 <= 6; ++i18 )
{
if ( matrix[8 * i17 + i18] && matrix[8 * i17 + 8 + i18] && matrix[8 * i17 + 1 + i18] && matrix[8 * i17 + 9 + i18] )
{
v25 = 1;
break;
}
}
if ( v25 )
break;
}
if ( matrix[26] != v25 )
return 0;
if ( (v55 == 2) != matrix[46] )
return 0;
v22 = 0;
v12 = sub_140001B0C(3, 3, v3, v2);
for ( i19 = 0; i19 < v12; ++i19 )
{
if ( matrix[8 * v3[i19] + v2[i19]] )
++v22;
}
if ( matrix[27] != !(v22 & 1) )
return 0;
v20 = 0;
v12 = sub_140001B0C(6, 4, v3, v2);
for ( i20 = 0; i20 < v12; ++i20 )
{
if ( matrix[8 * v3[i20] + v2[i20]] )
++v20;
}
if ( matrix[22] != (v20 % 2 == 1) )
return 0;
if ( matrix[54] != SHIDWORD(v9) > SHIDWORD(v7) )
return 0;
v18 = 1;
for ( i21 = 0; i21 <= 7; ++i21 )
{
if ( (int)v7 > *((_DWORD *)&v4 + i21) )
{
v18 = 0;
break;
}
}
if ( matrix[35] != v18 )
return 0;
if ( matrix[39] != SHIDWORD(v10) > SHIDWORD(v4) )
return 0;
v16 = 0;
for ( i22 = 0; i22 <= 7; ++i22 )
{
if ( !*((_DWORD *)&v8 + i22) )
{
v16 = 1;
break;
}
}
if ( matrix[42] != (int)v8 < SHIDWORD(v4) )
return 0;
if ( matrix[7] != (int)v8 > 4 )
return 0;
v14 = 0;
for ( i23 = 0; i23 <= 7; ++i23 )
{
if ( !*((_DWORD *)&v4 + i23) )
{
v14 = 1;
break;
}
}
v1 = v16 || v14;
return matrix[36] == v1 && matrix[58] == (int)v8 > (int)v5;
}

其中有很多对矩阵的约束,转为python的z3脚本:

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
from z3 import *

# 创建一个 8x8 的布尔矩阵
ROWS = 8
COLS = 8
S = [[Bool(f'S_{i+1}_{j+1}') for j in range(COLS)] for i in range(ROWS)]
solver = Solver()

# 要求某些项必须是正确的或者错误的
solver.add(S[1][4] == True)
solver.add(S[6][4] == False)
solver.add(S[2][7] == False)
solver.add(S[4][1] == True)
solver.add(S[7][7] == False)
solver.add(S[5][3] == True)
solver.add(S[2][5] == False)
solver.add(S[3][1] == True)

# 第x行/列正确数量小于/大于第x行/列
row_1_count = Sum([If(S[0][j], 1, 0) for j in range(COLS)])
row_4_count = Sum([If(S[3][j], 1, 0) for j in range(COLS)])
row_6_count = Sum([If(S[5][j], 1, 0) for j in range(COLS)])

col_1_count = Sum([If(S[i][0], 1, 0) for i in range(ROWS)])
col_2_count = Sum([If(S[i][1], 1, 0) for i in range(ROWS)])
col_3_count = Sum([If(S[i][2], 1, 0) for i in range(ROWS)])
col_4_count = Sum([If(S[i][3], 1, 0) for i in range(ROWS)])
col_5_count = Sum([If(S[i][4], 1, 0) for i in range(ROWS)])
col_8_count = Sum([If(S[i][7], 1, 0) for i in range(ROWS)])

solver.add(S[4][7] == (row_6_count > col_2_count))
solver.add(S[0][2] == (col_2_count < col_3_count))
solver.add(S[3][1] == (row_4_count < col_2_count))
solver.add(S[6][6] == (row_4_count > col_8_count))
solver.add(S[5][3] == (row_1_count < col_4_count))
solver.add(S[5][2] == (row_1_count < col_2_count))
solver.add(S[7][2] == (row_1_count > col_3_count))

# 第n列正确数量xx
solver.add(S[4][0] == (col_5_count < 6))
solver.add(S[4][2] == (col_1_count > 5))
solver.add(S[6][0] == (col_8_count < 4))
solver.add(S[0][7] == (row_1_count > 4))
solver.add(S[5][0] == (row_6_count < 2))

# 会有8个正确项连成一整行/列/对角线
horizontal_streak_of_8 = Or([And([S[i][j] for j in range(k, k+8)]) for i in range(ROWS) for k in range(COLS - 7)])
solver.add(S[1][4] == horizontal_streak_of_8)

vertical_streak_of_8 = Or([And([S[i][j] for i in range(k, k+8)]) for j in range(COLS) for k in range(ROWS - 7)])
solver.add(S[0][4] == vertical_streak_of_8)

exists_8_diag = Or(Or([And([S[i][i] for i in range(8)])]), Or([And([S[i][COLS-1-i] for i in range(8)])]))
solver.add(S[2][4] == exists_8_diag)

# 矩阵四个角的项恰好有n个是对的
corners = [S[0][0], S[0][COLS-1], S[ROWS-1][0], S[ROWS-1][COLS-1]]
corner_count = Sum([If(b, 1, 0) for b in corners])
solver.add(S[5][5] == (corner_count == 1))
solver.add(S[5][6] == (corner_count == 2))
solver.add(S[1][3] == (corner_count == 3))
solver.add(S[7][1] == (corner_count == 4))

# 这一项周围的5项中正确的数量是奇数/偶数
def get_neighbors(r, c):
neighbors = []
for i in range(max(0, r-1), min(ROWS, r+2)):
for j in range(max(0, c-1), min(COLS, c+2)):
if (i, j) != (r, c):
neighbors.append(S[i][j])
return neighbors

neighbors_2_1_count = Sum([If(b, 1, 0) for b in get_neighbors(1, 0)])
solver.add(S[1][0] == (neighbors_2_1_count % 2 == 1))

neighbors_6_7_count = Sum([If(b, 1, 0) for b in get_neighbors(5, 6)])
solver.add(S[5][6] == (neighbors_6_7_count % 2 == 0))

# 存在/不存在一个项,这一项的周围8个项全是错的项
all_false_neighbors_exist = Or([And([Not(n) for n in get_neighbors(i, j)]) for i in range(ROWS) for j in range(COLS)])
solver.add(S[1][2] == Not(all_false_neighbors_exist))
solver.add(S[5][7] == all_false_neighbors_exist)

# x,x是正确的/错误的
solver.add(S[2][0] == S[2][2])
solver.add(S[6][3] == S[7][5])

# 坐标x,x范围内的九宫格里面有xx个正确的
subgrid_count_1 = Sum([If(S[i][j], 1, 0) for i in range(3) for j in range(3)])
solver.add(S[2][1] == (subgrid_count_1 >= 5))

subgrid_count_2 = Sum([If(S[i][j], 4, 3) for i in range(4) for j in range(2)])
solver.add(S[2][6] == (subgrid_count_2 <= 3))

subgrid_count_3 = Sum([If(S[i][j], 2, 4) for i in range(5) for j in range(3)])
solver.add(S[1][6] == (subgrid_count_2 > 7))

subgrid_count_4 = Sum([If(S[i][j], 3, 3) for i in range(3) for j in range(4)])
solver.add(S[3][6] == (subgrid_count_2 < 3))

# 存在/不存在上下相邻且均正确的情况
adjacent_vertical = Or([And(S[i][j], S[i+1][j]) for i in range(ROWS - 1) for j in range(COLS)])
solver.add(S[2][2] == Not(adjacent_vertical))

# 存在/不存在上下左右均正确但自身不正确的情况
adjacent_plus = Or([And(S[i-1][j], S[i+1][j], S[i][j-1], S[i][j+1], Not(S[i][j])) for i in range(1, ROWS-1) for j in range(1, COLS-1)])
solver.add(S[2][3] == Not(adjacent_plus))

# 存在/不存在某一项,周围的8个格子内正确的数量xx
exists_high_density_spot = Or([Sum([If(n, 1, 0) for n in get_neighbors(i, j)]) >= 7 for i in range(ROWS) for j in range(COLS)])
solver.add(S[3][0] == exists_high_density_spot)

# 存在/不存在一个形状,其内部的k个均正确
exists_2x2_square = Or([And(S[i][j], S[i+1][j], S[i][j+1], S[i+1][j+1]) for i in range(ROWS-1) for j in range(COLS-1)])
solver.add(S[3][2] == exists_2x2_square)

# 周围8个格子有偶数个/奇数个是正确的
neighbors_4_4_count = Sum([If(b, 1, 0) for b in get_neighbors(3, 3)])
solver.add(S[3][3] == (neighbors_4_4_count % 2 == 0))

neighbors_7_5_count = Sum([If(b, 1, 0) for b in get_neighbors(6, 4)])
solver.add(S[2][6] == (neighbors_7_5_count % 2 == 1))

# 第x列正确数量在所有列中最少
col_counts = [Sum([If(S[i][j], 1, 0) for i in range(ROWS)]) for j in range(COLS)]
solver.add(S[4][3] == And([col_counts[6] <= col_counts[j] for j in range(COLS)]))

# 5,5: 存在一整行或一整列都是错的
all_false_row = Or([And([Not(S[i][j]) for j in range(COLS)]) for i in range(ROWS)])
all_false_col = Or([And([Not(S[i][j]) for i in range(ROWS)]) for j in range(COLS)])
solver.add(S[4][4] == Or(all_false_row, all_false_col))

solution_count = 0
while solver.check() == sat:
solution_count += 1
model = solver.model()
matrix_solution = []
for i in range(ROWS):
row_solution = []
for j in range(COLS):
if model.evaluate(S[i][j]):
row_solution.append("1")
else:
row_solution.append("0")
matrix_solution.append(row_solution)

for i, row in enumerate(matrix_solution):
print(f" ".join(row))

true_count = sum(row.count("1") for row in matrix_solution)

current_solution_negation = []
for i in range(ROWS):
for j in range(COLS):
if model.evaluate(S[i][j]):
current_solution_negation.append(Not(S[i][j]))
else:
current_solution_negation.append(S[i][j])

solver.add(Or(current_solution_negation))

得到解:1001011111111111010000000110100111010110000110000111000110111110(十进制为10952543642096005566),此时程序打印出来一个图案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 2 2 1 3
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 3 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1 3
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 0
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 2 2 2 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1 2
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 2 1 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 3 2 1 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 2 1 1
□ □ □ □ □ □ □ □ □ □ □ □ □ ║ 1 1 1 1 1 1
══════════════════════════╝
4 1 1 1 4 5 3 1 3 4 4 1 1
1 4 5 1 4 1 1 1 1 1 1 2
3 1 1 1 1 1 3 1
1 2 2 1
1
1

显然,这是一个数织问题,我们可以通过在线网站恢复出这里所需的数织图案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
████  ████    ██    ██████║ 2 2 1 3
██ ██ ██ ██ ██ ██ ║ 1 1 1 1 1 1
██ ██ ██████ ██ ██║ 1 1 3 1 1
██ ██ ██ ██ ██████║ 1 1 1 1 3
║ 0
██ ████ ████ ████ ██║ 1 2 2 2 1
██ ██ ██ ██ ██ ║ 1 1 1 1 1
██ ██ ██ ██ ████║ 1 1 1 1 2
██ ████ ██ ██ ██ ║ 1 2 1 1 1
██████ ████ ██ ██ ██║ 3 2 1 1 1
██ ██ ██ ██ ║ 1 1 1 1
██ ████ ██ ██ ║ 1 2 1 1
██ ██ ██ ██ ██ ██║ 1 1 1 1 1 1
══════════════════════════╝
4 1 1 1 4 5 3 1 3 4 4 1 1
1 4 5 1 4 1 1 1 1 1 1 2
3 1 1 1 1 1 3 1
1 2 2 1
1
1

接下来跟踪这个数织是如何构造的,注意到之前密钥正确后被转为了hex并插入了一个字节0xEF(97ff4069d61871beef),查看后续逻辑:

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
_DWORD *__fastcall sub_140002F00(const char *a1, _DWORD *a2)
{
int v3; // eax
_DWORD v4[7]; // [rsp+20h] [rbp-40h] BYREF
unsigned int v5; // [rsp+3Ch] [rbp-24h]
_DWORD *v6; // [rsp+40h] [rbp-20h]
int v7; // [rsp+48h] [rbp-18h]
int v8; // [rsp+4Ch] [rbp-14h]
int k; // [rsp+50h] [rbp-10h]
int j; // [rsp+54h] [rbp-Ch]
int v11; // [rsp+58h] [rbp-8h]
int i; // [rsp+5Ch] [rbp-4h]

if ( a1 )
{
sub_140002E30();
v8 = strlen(a1);
v7 = 6 * v8;
*a2 = 6 * v8;
for ( i = 0; i < v8; ++i )
{
if ( dword_14000B080[(unsigned __int8)a1[i]] == -1 )
{
*a2 = 0;
return 0;
}
}
v6 = malloc(4LL * v7);
if ( v6 )
{
v11 = 0;
for ( j = 0; j < v8; ++j )
{
v5 = dword_14000B080[(unsigned __int8)a1[j]];
sub_140002EAE(v5, v4);
for ( k = 0; k <= 5; ++k )
{
v3 = v11++;
v6[v3] = v4[k];
}
}
return v6;
}
else
{
*a2 = 0;
return 0;
}
}
else
{
*a2 = 0;
return 0;
}
}

_DWORD *sub_140002E30()
{
_DWORD *result; // rax
int j; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 255; ++i )
{
result = dword_14000B080;
dword_14000B080[i] = -1;
}
for ( j = 0; j <= 63; ++j )
{
result = dword_14000B080;
dword_14000B080[(unsigned __int8)aAbcdefghijklmn[j]] = j;
}
return result;
}

此函数初始化了一个类似base64的映射表(结尾是_!),然后把字符换成在表中的索引,并转为6位2进制,之后在main函数中要求bit总数大于144,相当于输入的字符长度大于24,继续查看sub_140003070:

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
__int64 __fastcall sub_140003070(__int64 a1, int a2, __int64 a3)
{
_BYTE v4[432]; // [rsp+20h] [rbp-60h] BYREF
_BYTE v5[27]; // [rsp+1D0h] [rbp+150h] BYREF
__int64 v6; // [rsp+1EBh] [rbp+16Bh] BYREF
char v7; // [rsp+1F3h] [rbp+173h]
int v8; // [rsp+1F4h] [rbp+174h]
int v9; // [rsp+1F8h] [rbp+178h]
int v10; // [rsp+1FCh] [rbp+17Ch]
int v11; // [rsp+200h] [rbp+180h]
int v12; // [rsp+204h] [rbp+184h]
void *Block; // [rsp+208h] [rbp+188h]
int m; // [rsp+210h] [rbp+190h]
int k; // [rsp+214h] [rbp+194h]
int j; // [rsp+218h] [rbp+198h]
int i; // [rsp+21Ch] [rbp+19Ch]

v6 = 0x5FB548D0433F93FALL;
v7 = 62;
if ( !a1 || a2 <= 143 )
return 0xFFFFFFFFLL;
Block = malloc(0x240u);
if ( !Block )
return 0xFFFFFFFFLL;
for ( i = 0; i < a2 - 143; ++i )
{
for ( j = 0; j <= 143; ++j )
*((_DWORD *)Block + j) = *(_DWORD *)(a1 + 4LL * (i + j));
memset(v5, 0, 0x12u);
for ( k = 0; k <= 143; ++k )
{
v9 = k / 8;
v8 = 7 - k % 8;
v5[k / 8] |= *((_BYTE *)Block + 4 * k) << (7 - k % 8);
}
sub_140001780(v4, a3, &v6, 1);
sub_14000196F(v4, v5, 18);
for ( m = 0; m <= 143; ++m )
{
v12 = m / 8;
v11 = 7 - m % 8;
v10 = ((int)(unsigned __int8)v5[m / 8] >> (7 - m % 8)) & 1;
*(_DWORD *)(4LL * (i + m) + a1) = v10;
}
}
free(Block);
return 0;
}

这是一个很明显的滑动窗口加密,一次带入144bit(18字节),key使用刚才的key,还初始化了一个额外的iv值(注意key和iv都是9字节),查看核心加密函数:

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
__int64 __fastcall sub_140001780(_DWORD *a1, _BYTE *a2, _BYTE *a3, int a4)
{
size_t v4; // rcx
size_t v5; // rcx

memset(a1, 0, 9u);
memset((char *)a1 + 9, 0, 9u);
if ( a2 )
{
if ( *a2 )
v4 = 9;
else
v4 = 0;
memcpy(a1, a2, v4);
}
if ( a3 )
{
if ( *a3 )
v5 = 9;
else
v5 = 0;
memcpy((char *)a1 + 9, a3, v5);
}
a1[106] = a4;
return sub_1400016A8(a1);
}

unsigned __int64 __fastcall sub_1400016A8(__int64 a1)
{
unsigned __int64 result; // rax
int v2; // r8d
int j; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
int v5; // [rsp+2Ch] [rbp-4h]

result = (unsigned __int64)memcpy((void *)(a1 + 18), (const void *)a1, 9u);
v5 = 0;
for ( i = 0; i <= 8; ++i )
{
result = luoshu_magic_matrix[i];
v5 += result;
}
for ( j = 9; j <= 404; ++j )
{
v2 = *(unsigned __int8 *)(a1 + j - 9 + 18);
result = v5 * (v2 + (unsigned int)luoshu_magic_matrix[j % 9]);
*(_BYTE *)(a1 + j + 18) = v5 * (v2 + luoshu_magic_matrix[j % 9]);
}
return result;
}

__int64 __fastcall sub_14000196F(__int64 a1, __int64 a2, int a3)
{
__int64 result; // rax
int v4; // [rsp+20h] [rbp-10h]
int v5; // [rsp+24h] [rbp-Ch]
unsigned int j; // [rsp+28h] [rbp-8h]
int i; // [rsp+2Ch] [rbp-4h]

v5 = (9 - a3 % 9) % 9;
v4 = a3 + v5;
for ( i = 0; i < v5; ++i )
*(_BYTE *)(a2 + a3 + i) = v5;
if ( *(_DWORD *)(a1 + 424) == 1 )
sub_1400014BE(a2, a1 + 9);
for ( j = 0; ; j += 9 )
{
result = j;
if ( (int)j >= v4 )
break;
sub_140001850(a1, (int)j + a2);
if ( *(_DWORD *)(a1 + 424) == 1 && v4 > (int)(j + 9) )
sub_1400014BE((int)j + 9LL + a2, (int)j + a2);
}
return result;
}

__int64 __fastcall sub_1400014BE(__int64 a1, __int64 a2)
{
int v2; // ecx
__int64 result; // rax
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
{
v2 = *(unsigned __int8 *)(a2 + i);
result = v2 ^ (unsigned int)*(unsigned __int8 *)(a1 + i);
*(_BYTE *)(i + a1) = v2 ^ *(_BYTE *)(a1 + i);
}
return result;
}

__int64 *__fastcall sub_140001850(__int64 a1, __int64 *a2)
{
__int64 *result; // rax
__int64 v3; // [rsp+27h] [rbp-19h] BYREF
char v4; // [rsp+2Fh] [rbp-11h]
__int64 v5; // [rsp+30h] [rbp-10h]
int i; // [rsp+3Ch] [rbp-4h]

v3 = *a2;
v4 = *((_BYTE *)a2 + 8);
for ( i = 0; i <= 44; ++i )
{
v5 = a1 + 18 + 9 * i;
sub_1400014BE((__int64)&v3, (__int64)luoshu_magic_matrix);
if ( i % 2 == 1 )
sub_1400015D4(&v3, v5);
else
sub_14000163E(&v3, v5);
sub_14000151B(&v3);
sub_1400014BE((__int64)&v3, v5);
if ( (i & 1) != 0 )
sub_14000163E(&v3, luoshu_magic_matrix);
else
sub_1400015D4(&v3, luoshu_magic_matrix);
sub_14000157E(&v3, luoshu_sbox);
}
result = a2;
*a2 = v3;
*((_BYTE *)a2 + 8) = v4;
return result;
}

void __fastcall sub_1400015D4(__int64 a1, __int64 a2)
{
int i; // [rsp+2Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
*(_BYTE *)(i + a1) = sub_140001450(*(unsigned __int8 *)(a1 + i), *(unsigned __int8 *)(a2 + i));
}

__int64 __fastcall sub_140001450(unsigned __int8 a1, char a2)
{
return (a1 << (a2 & 7)) | (unsigned int)((int)a1 >> (8 - (a2 & 7)));
}

void __fastcall sub_14000163E(__int64 a1, __int64 a2)
{
int i; // [rsp+2Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
*(_BYTE *)(i + a1) = sub_140001487(*(unsigned __int8 *)(a1 + i), *(unsigned __int8 *)(a2 + i));
}

__int64 __fastcall sub_140001487(unsigned __int8 a1, char a2)
{
return ((int)a1 >> (a2 & 7)) | (a1 << (8 - (a2 & 7)));
}

__int64 __fastcall sub_14000151B(__int64 a1)
{
__int64 result; // rax
__int64 v2; // [rsp+3h] [rbp-Dh]
char v3; // [rsp+Bh] [rbp-5h]
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
*((_BYTE *)&v2 + i) = *(_BYTE *)(luoshu_magic_matrix[i] - 1LL + a1);
result = a1;
*(_QWORD *)a1 = v2;
*(_BYTE *)(a1 + 8) = v3;
return result;
}

__int64 __fastcall sub_14000157E(__int64 a1, __int64 a2)
{
__int64 result; // rax
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
{
result = *(unsigned __int8 *)(a2 + *(unsigned __int8 *)(a1 + i));
*(_BYTE *)(a1 + i) = result;
}
return result;
}

是一个很明显的分组加密算法,一组9字节,采用CBC模式,用到了两个矩阵,一个是洛书幻方(492357816),还有一个是S盒,同构这个加密算法:

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
class Luo_Shu:
order = [4, 9, 2, 3, 5, 7, 8, 1, 6]
inv_order = [8, 3, 4, 1, 5, 9, 6, 7, 2]
sbox = bytearray.fromhex("38d6180ec6a4474a97a1a279e3f9610bc3fa08325f734f6cbe687bb34c1b8d3c63f5e8d8cbcfbcc19a3f6f9f70ca604930e68690c81fe56e8e002e36ea915d922d6befc9dfacf7209b9958b8741642f3b5892cda1287e1adff199e8027b68f5365de242a788295093448d233e23d55bb0d6a8a6dab0259012b56dc1472b01537ce8bb439af83108826f2408498c25bdb46517ea0a3d48543dde03a17d9aa234dfe2144c51a319d2fa5a771545c5ec441b7b1f0c0051c667f2977cc57fd4e13285af4d15096d752d3bdee9c7af8eb933bd06981032245e40a7ca9f662a83ebf7d67ec0c1de74bcded94a68c0475fc1efbb2070fd5b976112535baf1c764ae06e9")
inv_sbox = bytearray.fromhex("397775d3ebb4fef11267d70fe27003f286f654be7b7e4d9b0259a41db5e3ee3547a1d49e62f7885cbfb8637852403aa730a5136b68f83b7f00839acf1f6ddd298aaf4e97a2d59006692f07e51c9fbd16c391c65fab6e79bb4a76c08eac3ead142e0edb20fc60b6e019d171411773372a2caa7c154cecf5b9640bcb1ad8df92b75bd265858b96325587517281ea1e385e333d3fcee866c4088c492848caa65a2b93090a9405a8e9a9dcd99d744557fd847db1f01b82505db04bf4f96f26c818deb3278d10aea304fb34432d24bae68025d0c26ac795f301c5239c538f7a98614499566c0cd63631e422ff3ccde1e7c942b2fa894fc121da46cc0d11efedbca058")
mode_tag = 0
key_stream = bytearray(405)

def __init__(self, message, mode_tag, key=bytearray(9), iv=bytearray(9)):
self.mode_tag = 0 if mode_tag == 0 else 1
try: self.message = bytearray(message)
except: self.message = bytearray(message.encode())
try: self.key = bytearray(key)[0:9]
except: self.key = bytearray(key.encode())[0:9]
try: self.iv = bytearray(iv)[0:9]
except: self.iv = bytearray(iv.encode())[0:9]
if len(self.key) < 9: self.key+=bytearray(9-len(self.key))
if len(self.iv) < 9: self.iv+=bytearray(9-len(self.iv))
if not len(self.message) % 9 == 0: self.message+=bytearray(9-len(self.message)%9)

def rol(self, b, shift):
shift &= 7
return ((b << shift) | (b >> (8-shift))) & 0xFF

def ror(self, b, shift):
shift &= 7
return ((b >> shift) | (b << (8-shift))) & 0xFF

def xor_arrays(self, data, key):
for i in range(9): data[i] ^= key[i]
return data

def swap_with_order(self, data, tag):
tmp = bytearray(len(data))
for i in range(9): tmp[i] = data[self.order[i]-1] if tag == 0 else data[self.inv_order[i]-1]
return tmp

def rol_arrays(self, data, key):
for i in range(9): data[i] = self.rol(data[i], key[i])
return data

def ror_arrays(self, data, key):
for i in range(9): data[i] = self.ror(data[i], key[i])
return data

def sbox_swap(self, data, sbox):
for i in range(9): data[i] = sbox[data[i]]
return data

def inv_sbox_swap(self, data, inv_sbox):
for i in range(9): data[i] = inv_sbox[data[i]]
return data

def Key_Stream_Generate(self):
self.key_stream[0:9] = self.key
for i in range(9, 405):
self.key_stream[i] = ((self.key_stream[i-9] + self.order[i%9]) * sum(self.order)) & 0xFF

def Encrypt_Block(self, block):
for r in range(45):
block = self.xor_arrays(block, self.order)
block = self.rol_arrays(block, self.key_stream[r*9:(r+1)*9]) if r % 2 == 1 else self.ror_arrays(block, self.key_stream[r*9:(r+1)*9])
block = self.swap_with_order(block, 0)
block = self.xor_arrays(block, self.key_stream[r*9:(r+1)*9])
block = self.rol_arrays(block, self.order) if r % 2 == 0 else self.ror_arrays(block, self.order)
block = self.sbox_swap(block, self.sbox)
return block

def Decrypt_Block(self, block):
for r in range(44, -1, -1):
block = self.inv_sbox_swap(block, self.inv_sbox)
block = self.rol_arrays(block, self.order) if r % 2 == 1 else self.ror_arrays(block, self.order)
block = self.xor_arrays(block, self.key_stream[r*9:(r+1)*9])
block = self.swap_with_order(block, 1)
block = self.rol_arrays(block, self.key_stream[r*9:(r+1)*9]) if r % 2 == 0 else self.ror_arrays(block, self.key_stream[r*9:(r+1)*9])
block = self.xor_arrays(block, self.order)
return block

def Encrypt(self):
self.Key_Stream_Generate()
if self.mode_tag == 1:
self.message[0:9] = self.xor_arrays(self.message[0:9], self.iv)
self.message[0:9] = self.Encrypt_Block(self.message[0:9])
for i in range(1, len(self.message)//9):
if self.mode_tag == 1:
self.message[i*9:(i+1)*9] = self.xor_arrays(self.message[i*9:(i+1)*9], self.message[(i-1)*9:i*9])
self.message[i*9:(i+1)*9] = self.Encrypt_Block(self.message[i*9:(i+1)*9])
return self.message

def Decrypt(self):
self.Key_Stream_Generate()
for i in range(len(self.message)//9-1, 0, -1):
self.message[i*9:(i+1)*9] = self.Decrypt_Block(self.message[i*9:(i+1)*9])
if self.mode_tag == 1:
self.message[i*9:(i+1)*9] = self.xor_arrays(self.message[i*9:(i+1)*9], self.message[(i-1)*9:i*9])
self.message[0:9] = self.Decrypt_Block(self.message[0:9])
if self.mode_tag == 1:
self.message[0:9] = self.xor_arrays(self.message[0:9], self.iv)
return self.message

加密完成后,有一个函数将比特流转为布尔方阵:

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
_DWORD *__fastcall sub_14000334F(_DWORD *a1, int a2, int *a3)
{
double v4; // xmm0_8
_DWORD *v5; // [rsp+28h] [rbp-28h]
int v6; // [rsp+48h] [rbp-8h]
int i; // [rsp+4Ch] [rbp-4h]

if ( a1 && a2 >= 0 )
{
v4 = sqrt((double)a2);
v6 = (int)ceil(v4);
v5 = calloc(v6, 4LL * v6);
if ( v5 )
{
for ( i = 0; i < a2; ++i )
v5[i / v6 * (__int64)v6 + i % v6] = a1[i];
free(a1);
if ( a3 )
*a3 = v6;
return v5;
}
else
{
free(a1);
if ( a3 )
*a3 = 0;
return 0;
}
}
else
{
if ( a1 )
free(a1);
if ( a3 )
*a3 = 0;
return 0;
}
}

会将比特流转为比其长度大一点的最小方阵,剩下位置填充为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
__int64 __fastcall sub_1400036CC(int a1, int a2, _DWORD *a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7)
{
void *v9; // rsp
void *v10; // rsp
void *v11; // rsp
void *v12; // rsp
int v13; // r8d
int v14; // eax
int v15; // eax
int v16; // eax
int v17; // eax
int v18; // eax
int v19; // eax
void *v20[2]; // [rsp+20h] [rbp-70h] BYREF
void **v21; // [rsp+30h] [rbp-60h]
__int64 v22; // [rsp+38h] [rbp-58h]
void *v23; // [rsp+40h] [rbp-50h]
__int64 v24; // [rsp+48h] [rbp-48h]
void **v25; // [rsp+50h] [rbp-40h]
__int64 v26; // [rsp+58h] [rbp-38h]
__int64 v27; // [rsp+60h] [rbp-30h]
const char *v28; // [rsp+68h] [rbp-28h]
int v29; // [rsp+74h] [rbp-1Ch]
int v30; // [rsp+78h] [rbp-18h]
int v31; // [rsp+7Ch] [rbp-14h]
unsigned int v32; // [rsp+80h] [rbp-10h]
int k; // [rsp+84h] [rbp-Ch]
int j; // [rsp+88h] [rbp-8h]
int i; // [rsp+8Ch] [rbp-4h]

v27 = a2 - 1LL;
if ( a1 != 13 || a2 != 13 )
return 0;
a3[4] ^= 1u;
a3[5] ^= 1u;
a3[6] ^= 1u;
a3[11] ^= 1u;
a3[15] ^= 1u;
a3[16] ^= 1u;
a3[17] ^= 1u;
a3[21] ^= 1u;
a3[24] ^= 1u;
a3[25] ^= 1u;
a3[27] ^= 1u;
a3[38] ^= 1u;
a3[41] ^= 1u;
a3[44] ^= 1u;
a3[46] ^= 1u;
a3[53] ^= 1u;
a3[55] ^= 1u;
a3[59] ^= 1u;
a3[61] ^= 1u;
a3[62] ^= 1u;
a3[69] ^= 1u;
a3[71] ^= 1u;
a3[72] ^= 1u;
a3[76] ^= 1u;
a3[77] ^= 1u;
a3[79] ^= 1u;
a3[82] ^= 1u;
a3[83] ^= 1u;
a3[87] ^= 1u;
a3[88] ^= 1u;
a3[89] ^= 1u;
a3[92] ^= 1u;
a3[94] ^= 1u;
a3[95] ^= 1u;
a3[107] ^= 1u;
a3[114] ^= 1u;
a3[117] ^= 1u;
a3[118] ^= 1u;
a3[120] ^= 1u;
a3[122] ^= 1u;
a3[132] ^= 1u;
a3[134] ^= 1u;
a3[135] ^= 1u;
a3[142] ^= 1u;
a3[146] ^= 1u;
a3[149] ^= 1u;
a3[157] ^= 1u;
a3[158] ^= 1u;
a3[162] ^= 1u;
a3[164] ^= 1u;
a3[166] ^= 1u;
a3[168] ^= 1u;
SetConsoleOutputCP(0xFDE9u);
v32 = 1;
v26 = 12;
v9 = alloca(sub_140005C60());
v25 = v20;
v24 = 12;
v10 = alloca(sub_140005C60());
v23 = v20;
v22 = 12;
v11 = alloca(sub_140005C60());
v21 = v20;
v20[1] = (void *)12;
v12 = alloca(sub_140005C60());
v20[0] = v20;
memset(v23, 0, 4LL * v13);
memset(v20[0], 0, 0x34u);
for ( i = 0; i < 13; ++i )
{
v31 = 0;
for ( j = 0; j < 13; ++j )
{
if ( a3[i * (__int64)a2 + j] == 1 )
{
++v31;
}
else if ( v31 > 0 )
{
v14 = *((_DWORD *)v23 + i);
*((_DWORD *)v23 + i) = v14 + 1;
*((_DWORD *)&v25[5 * i] + v14) = v31;
v31 = 0;
}
}
if ( v31 > 0 )
{
v15 = *((_DWORD *)v23 + i);
*((_DWORD *)v23 + i) = v15 + 1;
*((_DWORD *)&v25[5 * i] + v15) = v31;
}
if ( !*((_DWORD *)v23 + i) )
{
v16 = *((_DWORD *)v23 + i);
*((_DWORD *)v23 + i) = v16 + 1;
*((_DWORD *)&v25[5 * i] + v16) = 0;
}
}
for ( j = 0; j < 13; ++j )
{
v30 = 0;
for ( i = 0; i < 13; ++i )
{
if ( a3[i * (__int64)a2 + j] == 1 )
{
++v30;
}
else if ( v30 > 0 )
{
v17 = *((_DWORD *)v20[0] + j);
*((_DWORD *)v20[0] + j) = v17 + 1;
*((_DWORD *)&v21[5 * j] + v17) = v30;
v30 = 0;
}
}
if ( v30 > 0 )
{
v18 = *((_DWORD *)v20[0] + j);
*((_DWORD *)v20[0] + j) = v18 + 1;
*((_DWORD *)&v21[5 * j] + v18) = v30;
}
if ( !*((_DWORD *)v20[0] + j) )
{
v19 = *((_DWORD *)v20[0] + j);
*((_DWORD *)v20[0] + j) = v19 + 1;
*((_DWORD *)&v21[5 * j] + v19) = 0;
}
}
for ( i = 0; i < 13; ++i )
{
if ( *((_DWORD *)v23 + i) == *(_DWORD *)(4LL * i + a5) )
{
for ( k = 0; k < *(_DWORD *)(4LL * i + a5); ++k )
{
if ( *((_DWORD *)&v25[5 * i] + k) != *(_DWORD *)(a4 + 40LL * i + 4LL * k) )
{
v32 = 0;
break;
}
}
}
else
{
v32 = 0;
}
}
for ( j = 0; j < 13; ++j )
{
if ( *((_DWORD *)v20[0] + j) == *(_DWORD *)(4LL * j + a7) )
{
for ( k = 0; k < *(_DWORD *)(4LL * j + a7); ++k )
{
if ( *((_DWORD *)&v21[5 * j] + k) != *(_DWORD *)(a6 + 40LL * j + 4LL * k) )
{
v32 = 0;
break;
}
}
}
else
{
v32 = 0;
}
}
v29 = 0;
for ( j = 0; j < 13; ++j )
{
if ( v29 < *((_DWORD *)v20[0] + j) )
v29 = *((_DWORD *)v20[0] + j);
}
putchar(10);
for ( i = 0; i < 13; ++i )
{
for ( j = 0; j < 13; ++j )
{
if ( a3[i * (__int64)a2 + j] == 1 )
v28 = (const char *)&unk_1400081BA;
else
v28 = " ";
printf("%s");
}
printf((char *)&byte_1400081A6);
for ( k = 0; k < *((_DWORD *)v23 + i); ++k )
printf("%d ");
putchar(10);
}
for ( j = 0; j < 26; ++j )
printf((char *)&byte_1400081AF);
puts(&byte_1400081B3);
for ( k = 0; k < v29; ++k )
{
for ( j = 0; j < 13; ++j )
{
if ( k >= *((_DWORD *)v20[0] + j) )
printf(" ");
else
printf("%d ");
}
putchar(10);
}
if ( v32 )
puts("\nRight input! Flag is flag{your input}.");
else
puts("\nWrong input!");
return v32;
}

校验了方阵是否为13*13的(169bit),如果是则对其某些位置取反,根据之前得到的目标matrix,可以恢复出来原矩阵的形态为:

1
2
3
4
5
6
7
8
9
10
11
12
13
1100101100101
0001011010100
1000111010001
0010101111011
0100100000111
1101111000101
1100010101010
1111010011001
0011010001010
1111101010101
0110101000000
0000111010010
1010000000000

发现是162bit,也就是输入是27字节,编写脚本解密:

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
charset = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_!")

def sliding_decrypt_144bit(bitstream):
key = bytearray.fromhex("97ff4069d61871beef")
iv = bytearray.fromhex("fa933f43d048b55f3e")
total_bits = len(bitstream)
for bit_offset in range(total_bits - 144, -1, -1):
temp_bits = bitstream[bit_offset:bit_offset + 144]
block_bytes = bytearray(18)
for i in range(144):
byte_idx = i // 8
bit_idx = 7 - (i % 8)
if temp_bits[i]:
block_bytes[byte_idx] |= (1 << bit_idx)
encrypted_block = Luo_Shu(block_bytes, 1, key, iv).Decrypt()
for i in range(144):
byte_idx = i // 8
bit_idx = 7 - (i % 8)
bit = (encrypted_block[byte_idx] >> bit_idx) & 1
bitstream[bit_offset + i] = bit

def bitstream_to_string(bitstream):
chars = []
num_groups = len(bitstream) // 6
for i in range(num_groups):
group = bitstream[i*6 : (i+1)*6]
n = 0
for bit in group:
n = (n << 1) | bit
if n >= len(charset):
return None
chars.append(charset[n])

return ''.join(chars)

enc = [1,1,0,1,0,1,1,1,0,0,1,0,1,1,0,0,1,0,0,1,0,0,0,1,1,1,1,1,0,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,0,0,0,1,0,1,1,0,0,1,0,1,1,1,1,0,1,1,1,0,1,0,0,1,1,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,0,0,0,1,0,1,1,1,0,1,0,0,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,0,1,0,1,0,1,1,0,0,1,1,0,0,0,0,1,1,0,1,0,1,1,1,1,0,1,0,0,1,0,0,0,1,0,1,0]
sliding_decrypt_144bit(enc)
print(''.join(map(str, enc)))
flag = bitstream_to_string(enc)
print(flag)

flag{M4g1c_m47r1x_15_ev3rYwH3rE!}

Let Operator Dance Alltime!!!

出题记录

这题本来该更难的, 但是觉得太难把payload换了 (之前的payload是一个更难的题)

外层是PE加载器,能用密钥解密payload并通过环境变量传参,用一个多步操作映射迷宫约束了密钥

payload魔改自于分布式计算项目LODA,是一个类汇编语言解释器,主要解决数学问题,其在github上开源并拥有官方文档官方在线运行可视化,payload层的主要思路也是diff源码,从main开始查找魔改点

WP

程序有UPX,脱壳.

脱壳后发现程序代码段很少,有一个巨大的数据段,且有几个额外段.prt.prd.prb.

给出的main函数显然是假的,解密后也是fakeflag,注意到.prt段的函数sub_1400035C5中有反调,如果有调试器则跳转至main,这说明起始函数应当位于.prt段中,不难发现是sub_140003659.

sub_140003659的起始部分打印有字符串加密,要求输入一个密钥通过sub_1400030E7的检验,sub_1400030E7是一个迷宫,检验输入的字符串是否在指定字符集A-Za-z0-9_!中,若存在则将其转换为对应的索引,还有一些其他校验(比如有4个下划线,结尾是感叹号等),然后解密迷宫地图,转为比特矩阵并走迷宫.

注意到每个字符控制的不止一步,而是很多步的组合,最终要到27,47的位置,输入的字符串会被xor 0xA作为解密后续数据的密钥.

解析迷宫并编写脚本求解:

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
mapping = {
'A': 'SSDD',
'B': 'WDWA',
'C': 'AWA',
'D': 'SSS',
'E': 'WDW',
'F': 'AASA',
'G': 'SAS',
'H': 'AAA',
'I': 'AWWA',
'J': 'WDDS',
'K': 'DWWS',
'L': 'WDAW',
'M': 'ASSA',
'N': 'SAAS',
'O': 'DSSS',
'P': 'WAWS',
'Q': 'AAAD',
'R': 'SDSS',
'S': 'WWDD',
'T': 'DDDS',
'U': 'SSSA',
'V': 'WDDD',
'W': 'SDDD',
'X': 'DDDW',
'Y': 'ASSS',
'Z': 'SDSD',
'a': 'SSDS',
'b': 'WSWD',
'c': 'SSD',
'd': 'DWDD',
'e': 'WAAW',
'f': 'SSA',
'g': 'AWWD',
'h': 'DDD',
'i': 'DDWW',
'j': 'SSSS',
'k': 'AAS',
'l': 'DDWS',
'm': 'AAWW',
'n': 'DDSS',
'o': 'AAWA',
'p': 'DDS',
'q': 'WDDA',
'r': 'WWWD',
's': 'AAAW',
't': 'DWWW',
'u': 'SDDW',
'v': 'AWDA',
'w': 'WAAS',
'x': 'DDWD',
'y': 'AWW',
'z': 'WAA',
'0': 'ASSD',
'1': 'WWWW',
'2': 'WWDW',
'3': 'AWWW',
'4': 'WWA',
'5': 'WDDW',
'6': 'DSDS',
'7': 'WAWW',
'8': 'SAAA',
'9': 'ASAA',
'_': 'WAAA',
'!': 'WWAA',
}

path = 'WAWWAAAWWWWWDDWWAAAAWWAAAAWWAAAWWAWAAAWAWWAWWWDWWWWWWDWWWWWDDWWAAAAAWWWWAWAAAWWWWWAA'

maze = '''
11111111111111111111111111111
10001111111111111111111111111
11101111111111111111111111111
11101111111111111111111111111
11101111111111111111111111111
11101111111111111111111111111
11100001111111111111111111111
11111100111111111111111111111
11111110111111111111111111111
11111110111111111111111111111
11111110111111111111111111111
11111110000001111111111111111
11111111111101111111111111111
11111111110001111111111111111
11111111110111111111111111111
11111111110111111111111111111
11111111110111111111111111111
11111111110111111111111111111
11111111100111111111111111111
11111111101111111111111111111
11111111101111111111111111111
11111111101111111111111111111
11111111101111111111111111111
11111111101111111111111111111
11111111001111111111111111111
11111111011111111111111111111
11111111011111111111111111111
11111111001111111111111111111
11111111101111111111111111111
11111111100111111111111111111
11111111110000111111111111111
11111111111110011111111111111
11111111111111011111111111111
11111111111111000011111111111
11111111111111111011111111111
11111111111111111000001111111
11111111111111111111101111111
11111111111111111111100000111
11111111111111111111111110111
11111111111111111111111000111
11111111111111111111111011111
11111111111111111111111011111
11111111111111111111111011111
11111111111111111111111011111
11111111111111111111111000011
11111111111111111111111111011
11111111111111111111111111001
11111111111111111111111111101
11111111111111111111111111111
'''

end_pos = (-26, -46)

def find_all_solutions(path, mapping,
target_length=None,
target_underscore=None,
require_exclamation_at_end=False):
from collections import defaultdict

seq_to_char = defaultdict(list)
for char, seq in mapping.items():
seq_to_char[seq].append(char)

len_groups = defaultdict(list)
for seq, chars in seq_to_char.items():
len_groups[len(seq)].append((seq, chars))

lengths = sorted(len_groups.keys())

results = []
current = []
count_underscore = 0

def backtrack(remaining_path):
nonlocal count_underscore

cur_len = len(current)

if target_length is not None and cur_len > target_length:
return

if target_underscore is not None and count_underscore > target_underscore:
return

if target_length is not None:
remaining_slots = target_length - cur_len
remaining_path_len = len(remaining_path)
min_seq_len = min(len_groups.keys())
max_seq_len = max(len_groups.keys())

if remaining_path_len < remaining_slots * min_seq_len:
return
if remaining_path_len > remaining_slots * max_seq_len:
return

if not remaining_path:
valid = True
if target_length is not None and cur_len != target_length:
valid = False
if target_underscore is not None and count_underscore != target_underscore:
valid = False
if require_exclamation_at_end:
if cur_len == 0 or current[-1] != '!':
valid = False

if valid:
results.append(''.join(current))
return

for L in lengths:
if L > len(remaining_path):
continue
prefix = remaining_path[:L]
if prefix not in seq_to_char:
continue

for char in seq_to_char[prefix]:
is_underscore = (char == '_')
is_exclamation = (char == '!')

if is_underscore:
count_underscore += 1
current.append(char)
backtrack(remaining_path[L:])
current.pop()
if is_underscore:
count_underscore -= 1

backtrack(path)
return results

solutions = find_all_solutions(path, mapping, 22, 4, 1)

print(solutions)

得到密钥7H15_Is_4_73tr15_m4z3!,发现可以继续向后执行逻辑了,之后是一个单字节解密和一个PE文件加载器,在

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
CurrentProcess = GetCurrentProcess();
FlushInstructionCache(CurrentProcess, lpBaseAddress, v6);
nSize = 4096;
v27 = 4095;
v8 = alloca(sub_7FF63B752640());
lpBuffer = v10;
EnvironmentVariableA = GetEnvironmentVariableA("LODA_ARGS", v10, nSize);
if ( EnvironmentVariableA && EnvironmentVariableA < nSize )
{
Destination = (char *)malloc(EnvironmentVariableA + 1);
if ( Destination )
{
strcpy(Destination, lpBuffer);
v53 = 0;
v52 = 0;
v53 = realloc(0, 8u);
if ( v53 )
{
*(_QWORD *)v53 = "loda.dll";
v52 = 1;
for ( ii = strtok(Destination, " "); ii; ii = strtok(0, " ") )
{
v53 = realloc(v53, 8LL * (v52 + 1));
if ( !v53 )
return;
*((_QWORD *)v53 + v52++) = ii;
}
v53 = realloc(v53, 8LL * (v52 + 1));
if ( v53 )
{
*((_QWORD *)v53 + v52) = 0;
v23 = *((_DWORD *)v46 + 10);
free(Block);
v22 = (void (__fastcall *)(LPCVOID, __int64, void *))((char *)lpBaseAddress + v23);
v22(lpBaseAddress, 1, v53);
free(Destination);
free(v53);
}
}
}
}
else
{
v21 = *((_DWORD *)v46 + 10);
free(Block);
v20 = (char *)lpBaseAddress + v21;
((void (__fastcall *)(LPCVOID, __int64, _QWORD))v20)(lpBaseAddress, 1, 0);
}

中读取了名为LODA_ARGS的环境变量值,并将其按空格拆分后解析,传入payload(实际为dll),函数在v22(lpBaseAddress, 1, v53);这一行进入dll内部,在此之前可以选择dump整个dll以便分析,也可以继续在动态调试中跟进.

之后进入dll,如果没有在LODA_ARGS中传参,那么程序会直接退出,我们可以试试创建LODA_ARGS并随机输入一些内容进行盲测,注意到此时弹出了Input your flag:,我们可以去dll中查找这个字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Hidden C++ exception states: #wind=2
void __fastcall __noreturn sub_180024D00(unsigned int a1, __int64 a2)
{
__int64 v4; // rax
_QWORD v5[3]; // [rsp+28h] [rbp-130h] BYREF
_BYTE v6[128]; // [rsp+40h] [rbp-118h] BYREF
_BYTE v7[128]; // [rsp+C0h] [rbp-98h] BYREF

sub_1801090E0(v7);
sub_180109550(v7, v5, a1, a2);
sub_18000A600(&qword_180190BB0, "Input your flag: ");
sub_180022040(&qword_180190D70, &qword_18018C030);
v4 = sub_180012930(v6, v7);
sub_1800224F0(v4, v5);
sub_18011C524(0);
JUMPOUT(0x180024D97LL);
}

此即为main函数,注意到程序在我们输入flag后弹出了一条消息:Unknown command: ,这说明LODA_ARGS中的第一个参数是一个指令,我们可以搜索字符串或者使用万能的help进行盲测,弹出:

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
Welcome to LODA CTF version. More information at https://loda-lang.org/

Usage: loda <command> <options>

Commands:
eval <program> Evaluate an integer sequence program (see -t,-b,-s)
check <program> Verify correctness of an integer sequence program (see -b)
mine Mine programs for integer sequences (see -i,-p,-P,-H)
submit <file> [id] Submit an integer sequence program to the central repository
export <program> Export a program and print the result (see -o,-t)
optimize <program> Optimize a program and print the result
minimize <program> Minimize a program and print the result (see -t)
profile <program> Measure program evaluation time (see -t)
fold <program> <id> Fold a subprogram given by ID into a seq-operation
unfold <program> Unfold the first seq-operation of a program
mutate <program> Mutate a program to mine for integer sequences
setup Run interactive setup to configure LODA
update Update integer sequence and program data
upgrade Check for and install the latest LODA version

Targets:
<file> Path to a LODA file (file extension: *.asm)
<id> ID of an integer sequence (example: A000045)
<program> Either an <file> or an <id>

Options:
-t <number> Number of sequence terms (default: 8)
-b Print result in the OEIS b-file format
-o <string> Export format (formula,loda,pari,range)
-d Export with dependencies to other programs
-s Evaluate program and return number of execution steps
-c <number> Maximum number of execution steps (no limit: -1)
-m <number> Maximum number of used memory cells (no limit: -1)
-z <number> Maximum evaluation time in seconds (no limit: -1)
-l <string> Log level (values: debug,info,warn,error,alert)
-i <string> Name of miner configuration from miners.json
-p Parallel mining using default number of instances
-P <number> Parallel mining using custom number of instances
-H <number> Number of mining hours (default: unlimited)

给出了一个网站https://loda-lang.org/,可以在这里查看LODA语言的官方文档,以及在github上获取原项目的源代码和编译程序.

注意到LODA是一门类汇编语言,不具有显式的堆栈结构,只有无数个不限制数值上限的寄存器$0,$1,$2…$n,且$0是主要寄存器,一般情况下不支持手动初始化值,只支持初始化偏移量代入,最终程序结束后$0的值会作为返回值输出.

LODA编译生成的exe文件是一个解释器而非编译器,因此在题目没有显式给出额外的可执行汇编代码的情况下,汇编代码一定以某种方式存于dll内部,并通过特殊的arg进行触发.

尝试各种指令后发现只有evaluate没有被ban,其余的指令均被禁止使用,那么可以推测evaluate指令的解析逻辑被魔改,我们先从cmd/main.cpp(也就是上面的程序主函数)开始,寻找所有被魔改的函数,首先查看Settings.parseArgs:

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
// Hidden C++ exception states: #wind=2
_QWORD *__fastcall settings_parseargs(__int64 a1, _QWORD *a2, int a3, __int64 a4)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v4 = a4;
v93 = a4;
v5 = a3;
v6 = a2;
v88 = a2;
v87 = a1;
v107 = a2;
for ( i = 0; i < 9; ++i )
{
v8 = byte_18015BEF8[i];
LOBYTE(v8) = v8 ^ 0xCC;
v9 = xmmword_18018C020;
if ( (unsigned __int64)xmmword_18018C020 >= *((_QWORD *)&xmmword_18018C020 + 1) )
{
sub_18000B0E0(&qword_18018C010, 1, 0, v8);
}
else
{
*(_QWORD *)&xmmword_18018C020 = xmmword_18018C020 + 1;
v10 = &qword_18018C010;
if ( *((_QWORD *)&v9 + 1) > 0xFu )
v10 = (__int64 *)qword_18018C010;
*((_BYTE *)v10 + v9) = v8;
*((_BYTE *)v10 + v9 + 1) = 0;
}
}
...
}

注意到其中多出了一个字符串解密逻辑,byte处解密后的值为Loda_Lang,可惜暂时还无法查到这个字符串的xref,继续查看main函数,找到这一行sub_180022040((__int64)&cin, (__int64)&input);,其中的input必定有xref,共计xref到两个主要的函数:sub_18004CCB0sub_18004D1C0,而这两个函数恰好均为eval/evaluator.cpp中的函数,可以发现输入的input每8字节按大端序被转为uint64后作为LODA-VM执行器的主寄存器$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
...
while ( 1 )
{
v74 = v14;
if ( v15 >= v10 || v14 >= v13 )
break;
v16 = v14 + v12;
if ( v14 + v12 >= v13 )
{
sub_18004DE30(a4, v15);
break;
}
v23 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v23 = (__int64 *)input;
v24 = *((unsigned __int8 *)v23 + v16);
v25 = 0;
if ( v16 + 1 < v13 )
{
v26 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v26 = (__int64 *)input;
v25 = *((_BYTE *)v26 + v16 + 1);
}
v27 = v25 | (unsigned __int64)(v24 << 8);
v28 = 0;
if ( v16 + 2 < v13 )
{
v29 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v29 = (__int64 *)input;
v28 = *((_BYTE *)v29 + v16 + 2);
}
v30 = v28 | (v27 << 8);
v31 = 0;
if ( v16 + 3 < v13 )
{
v32 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v32 = (__int64 *)input;
v31 = *((_BYTE *)v32 + v16 + 3);
}
v33 = v31 | (v30 << 8);
v34 = 0;
if ( v16 + 4 < v13 )
{
v35 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v35 = (__int64 *)input;
v34 = *((_BYTE *)v35 + v16 + 4);
}
v36 = v34 | (v33 << 8);
v37 = 0;
if ( v16 + 5 < v13 )
{
v38 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v38 = (__int64 *)input;
v37 = *((_BYTE *)v38 + v16 + 5);
}
v39 = v37 | (v36 << 8);
v40 = v16 + 6;
v41 = 0;
if ( v16 + 6 < v13 )
{
v42 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v42 = (__int64 *)input;
v41 = *((_BYTE *)v42 + v40);
}
v43 = v41 | (v39 << 8);
v44 = v16 + 7;
v45 = 0;
if ( v44 < v13 )
{
v46 = &input;
if ( *((_QWORD *)&xmmword_18018C040 + 1) > 0xFu )
v46 = (__int64 *)input;
v45 = *((_BYTE *)v46 + v44);
}
···
}

接下来查看main中的dispatch函数,在其中找到以下几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  if ( n4 == 8 && !(unsigned int)sub_18013ED70(v28, "evaluate", 8) )
goto LABEL_77;
v30 = v126;
if ( n0xF_1 > 0xF )
v30 = v6;
if ( n4 == 4 && !(unsigned int)sub_18013ED70(v30, "eval", 4) )
{
LABEL_77:
if ( (unsigned __int64)((__int64)(a2[1] - *a2) >> 5) > 1 )
{
sub_18001A7B0(&v123, *a2 + 32LL);
goto LABEL_341;
}
...
}

进入其中的函数(Commands::evaluate):

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
...
sub_18013E330(v67, &unk_180154070, 4148);
v50 = 0;
v51 = 0;
v28 = sub_180110524(4187);
if ( !v28 )
invalid_parameter_noinfo_noreturn();
v29 = (v28 + 39) & 0xFFFFFFFFFFFFFFE0uLL;
*(_QWORD *)(v29 - 8) = v28;
*(_QWORD *)&v50 = v29;
v51 = v29 + 4148;
sub_18013E330(v29, v67, 4148);
*((_QWORD *)&v50 + 1) = v29 + 4148;
if ( &v22[-v21] == (char *)4148 )
{
v30 = (_BYTE *)v29;
while ( v30[v21 - v29] == *v30 )
{
if ( (unsigned __int64)&(++v30)[-v29] >= 0x1034 )
{
v31 = "Right flag!";
goto LABEL_35;
}
}
}
v31 = "Wrong flag!";
...
else
{
v18 = sub_18000A600(&qword_180190BB0, "wrong");
LOBYTE(v19) = 10;
v20 = sub_18000A3A0(v18 + *(int *)(*(_QWORD *)v18 + 4LL), v19);
sub_1800112E0(v18, v20);
sub_180008F20(v18);
}
...

注意到了明显的密文比较逻辑,有一段巨大的密文byte_180154070,长达4148字节,这就是用于比较的密文,在其上还有一段异或逻辑:

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
if ( v11 )
{
if ( v11 > 0x7FFFFFFFFFFFFFFFLL )
std::vector<void *>::_Xlen(v13, v15, v16);
sub_180017600(&v45, &v44, v16);
v4 = v47;
v22 = v46;
v21 = v45;
}
v23 = 0;
if ( v11 )
{
do
{
v24 = &v63;
if ( v12 > 0xF )
v24 = (__m128i *)v17;
v25 = v24->m128i_i8[v23];
v26 = &qword_18018C010;
if ( *((_QWORD *)&xmmword_18018C020 + 1) > 0xFu )
v26 = (__int64 *)qword_18018C010;
v27 = v25 ^ *((_BYTE *)v26 + v23 % (unsigned __int64)xmmword_18018C020);
v43[0] = v27;
if ( v22 == v4 )
{
sub_1800172F0(&v45, v22, v43);
v4 = v47;
v22 = v46;
}
else
{
*v22++ = v27;
v46 = v22;
}
++v23;
}
while ( v23 < v11 );
v21 = v45;
}

注意到密钥v26来源于qword_18018C010(此时还是空的),查询这个数组的xref,查到一个函数sub_1800F1070

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
...
v8 = &key;
if ( *((_QWORD *)&xmmword_18018C020 + 1) > 0xFu )
v8 = (__int64 *)key;
v9 = v2[2];
v10 = v2;
if ( v2[3] > 0xFu )
v10 = (_QWORD *)*v2;
if ( v9 != (_QWORD)xmmword_18018C020 )
goto LABEL_50;
if ( (unsigned int)sub_18013ED70(v10, v8, v9) )
goto LABEL_50;
try
{
v61 = 0;
v17 = (__int128 *)sub_180110524(1296);
v16 = 1295;
v63 = 1295;
*(_BYTE *)v17 = 0;
*(_QWORD *)&v61 = v17;
v62 = 0;
v15 = 0;
while ( v15 < 0x50F )
{
v12 = v2;
if ( v2[3] > 0xFu )
v12 = (_QWORD *)*v2;
v13 = *((unsigned __int8 *)v12 + v15 % v2[2]);
LOBYTE(v13) = byte_18015A320[v15] ^ v13;
if ( v4 >= v16 )
{
sub_18000B0E0(&v61, 1, 0, v13);
}
else
{
v62 = v4 + 1;
v14 = &v61;
if ( v16 > 0xF )
v14 = v17;
*((_BYTE *)v14 + v4) = v13;
*((_BYTE *)v14 + v4 + 1) = 0;
}
++v15;
v16 = v63;
v4 = v62;
v17 = (__int128 *)v61;
}
...
}
...

注意到了明显的依然使用这个key进行循环异或解密的数组byte_18015A320,而继续查询key的xref,可以发现其正式parseArgs中自解密的Loda_Lang,因此两个密文全部可以解密,第一串密文解密得到:

1
2
3
4
5
6
7
8
9
10
583816102810052800589434335069219286405826406630421989889686064356068205809909774121462460601326025288509488060186540672688592635715723433315421893336296834148679608422191981907424653118438603335430859349845434037593630964251937832355292049020864523737762582094808993539638654563699089038209584551358553991909861266732226673248842442203439010762618318248702523232866474166818905065779215570910502879852007937495130,
827704072678612886223153147399290850661022945529286866943398734005195447510363078995087843368215874310970056068705803945250285962553669389323971931220479551505982213885700227711843124365943461519691125733003722837430472451500047875157396636076027077284317802011824248045810864881382082857918356507197182949446072224631984230135651996398164889038209542184754161017209072489158566575505424165294117177009771871228665,
449370908557206263669364433712787468540823965880521351163618231812271760706181910371788136598949467985463885699494505701231196552477959530645655847059958728478087576272283565004750568195960388680603835305536103068323871487543027935367209464374652644043011538829273839525068430517787713718151517918959960106113055813339343097787988060801557722656955387987222324706431849864596070783781808169580959353810072245736678,
136723235546734809341061710477970416021661770286965197229882277483672900055557397432619926493881674610500435355836723597075190507430965956414347394506902551894990907192821436606424776631381555435487208847436931557824635268620128775278744122665436584062015233116510653693457977181245539242143819576668315412385153611755087464387481198691220636622256544747711427314977305054701211178511629210462570158054037210559471,
582734364161892683523669045682477973399547644657127704051069076453665670665199839845898218709981400674198420657349992080050711964236166946872639282311768870391421467896217270868101814002100343832256594630059387278231073416779019701796525553394901837836140529304572096777588202847484039992411585527113959646097858840858316067820995591285034891757339656201014806182579727870789457554208061619207925734174094456906174,
859290962943512089620583884324807994259168069031934797815738073537690886391795396959608694116540742265952662151164052010187896166280164101129250748780586227476438406030729848603651164185966148760986364047271318256291035694005499647571728360419241738332540458799415836667591886691039111151968520788415157035929894877485129278065878964965062839767234103947916397846693111696357030783770940975047296121833459368179814,
597559553592567104251755654491068552734420942918132558186777351819277930284012291180303814813371477575242937917048475900273494053006530360987904199553195469301754073352414029996806374782850453601642716448300298513280528406165422169309241696970925786506055418197116733161648569447598597374767373408784717682835962672392156958575059887787184803167348493500601598385893665354868844213067991156387207893780603100533035,
51371459758899855390514976341374571021497511490267510786457964055298962757456676520029757438013806266752199653793584016794833425441823101379994221385544658220407840994428598261525351677673005392418066006814237184206332707747812431962668464758795676801576458672760565675146377651690769585285569954135143756577417154046779216874479143646863007967043831145618484607607908821492512151163088100333272555355395651817292,
701424428617789363861974889959692466334764618468458527064555566455417290608695655394264063740043008822041518107197889768213948494068810896579929622596820710394340299428108036305197469300377709904155842455439877158688532119755290910101965886099047283654442385419856452530763741924289269529802996787251638977873223698332245936564987502884804393263403453495216774156521258169977355669617271852237089492234534991667427,
302648813311899031744908856773255354187356978276630394356874821345787992614524284580778327246433455049291405778430845498486366587619983908976667771237650357706647753992753332814803365381856567734244278341148986294910526427214290214390515305801755021147278725497232669081941725830136195646455368286701243369402712793040371378301784682786599018039026609923216448179479921564914179775067903851681440055529845164654471

是十个巨大的数,猜测这就是程序运算后输出的结果,那么flag就应当长达80字节,继续对第二段密文进行解密:

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
mov $1, $0
mov $2, $0
div $1, 4294967296
ban $2, 4294967295
mov $3, 195948557
mov $4, 3735928559
mov $5, 181321950
mov $6, 4277009102
mov $7, 289739801
mov $8, 11325165
mov $9, 114
lpb $9
mov $10, $2
mul $10, 32
add $10, $3
mov $11, $2
div $11, 16
add $11, $4
mov $12, $2
add $12, $8
bxo $10, $11
bxo $10, $12
add $1, $10
ban $1, 4294967295
add $8, $7
ban $8, 4294967295
mov $10, $1
mul $10, 8
add $10, $5
mov $11, $1
div $11, 64
add $11, $6
mov $12, $1
add $12, $8
bxo $10, $11
bxo $10, $12
add $2, $10
ban $2, 4294967295
sub $9, 1
lpe
mov $10, 114
mov $3, 1145141919
mov $4, 1919810114
lpb $10
mov $5, $0
add $10, $5
mov $6, $1
sub $10, $6
ban $5, $3
ban $10, $5
ban $6, $4
bor $10, $6
mov $7, $5
bor $5, $6
bxo $10, $5
bxo $6, $7
bxo $10, $6
mov $3, $5
sub $10, $3
mov $4, $6
add $10, $4
ban $0, $3
ban $10, $0
ban $1, $4
bor $10, $1
lpe
mul $1, 4294967296
mov $0, $1
bor $0, $2
mov $1, 142291871983652659
mov $2, 98998887741403650
mov $3, 1
mov $4, 0
mov $5, 23
lpb $5
sub $5, 1
mov $4, $2
mul $4, $5
add $4, $1
mul $3, $4
lpe
mov $9, 1336
mov $1, $0
lpb $9
mul $0, $1
mod $0, $3
sub $9, 1
lpe

发现这正是题目的LODA汇编源代码,不难发现这是一个TEA加密和一个多质数RSA加密(中间的循环是junkcode,根据loda官方文档,若循环标签在循环过程中不自减,则循环不执行),编写脚本解密即可:

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
from Crypto.Util.number import *

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

def n2bbe(d, n):
b = bytearray()
for nw in d: b.extend(nw.to_bytes(n, byteorder='big'))
return b

enc = [583816102810052800589434335069219286405826406630421989889686064356068205809909774121462460601326025288509488060186540672688592635715723433315421893336296834148679608422191981907424653118438603335430859349845434037593630964251937832355292049020864523737762582094808993539638654563699089038209584551358553991909861266732226673248842442203439010762618318248702523232866474166818905065779215570910502879852007937495130,
827704072678612886223153147399290850661022945529286866943398734005195447510363078995087843368215874310970056068705803945250285962553669389323971931220479551505982213885700227711843124365943461519691125733003722837430472451500047875157396636076027077284317802011824248045810864881382082857918356507197182949446072224631984230135651996398164889038209542184754161017209072489158566575505424165294117177009771871228665,
449370908557206263669364433712787468540823965880521351163618231812271760706181910371788136598949467985463885699494505701231196552477959530645655847059958728478087576272283565004750568195960388680603835305536103068323871487543027935367209464374652644043011538829273839525068430517787713718151517918959960106113055813339343097787988060801557722656955387987222324706431849864596070783781808169580959353810072245736678,
136723235546734809341061710477970416021661770286965197229882277483672900055557397432619926493881674610500435355836723597075190507430965956414347394506902551894990907192821436606424776631381555435487208847436931557824635268620128775278744122665436584062015233116510653693457977181245539242143819576668315412385153611755087464387481198691220636622256544747711427314977305054701211178511629210462570158054037210559471,
582734364161892683523669045682477973399547644657127704051069076453665670665199839845898218709981400674198420657349992080050711964236166946872639282311768870391421467896217270868101814002100343832256594630059387278231073416779019701796525553394901837836140529304572096777588202847484039992411585527113959646097858840858316067820995591285034891757339656201014806182579727870789457554208061619207925734174094456906174,
859290962943512089620583884324807994259168069031934797815738073537690886391795396959608694116540742265952662151164052010187896166280164101129250748780586227476438406030729848603651164185966148760986364047271318256291035694005499647571728360419241738332540458799415836667591886691039111151968520788415157035929894877485129278065878964965062839767234103947916397846693111696357030783770940975047296121833459368179814,
597559553592567104251755654491068552734420942918132558186777351819277930284012291180303814813371477575242937917048475900273494053006530360987904199553195469301754073352414029996806374782850453601642716448300298513280528406165422169309241696970925786506055418197116733161648569447598597374767373408784717682835962672392156958575059887787184803167348493500601598385893665354868844213067991156387207893780603100533035,
51371459758899855390514976341374571021497511490267510786457964055298962757456676520029757438013806266752199653793584016794833425441823101379994221385544658220407840994428598261525351677673005392418066006814237184206332707747812431962668464758795676801576458672760565675146377651690769585285569954135143756577417154046779216874479143646863007967043831145618484607607908821492512151163088100333272555355395651817292,
701424428617789363861974889959692466334764618468458527064555566455417290608695655394264063740043008822041518107197889768213948494068810896579929622596820710394340299428108036305197469300377709904155842455439877158688532119755290910101965886099047283654442385419856452530763741924289269529802996787251638977873223698332245936564987502884804393263403453495216774156521258169977355669617271852237089492234534991667427,
302648813311899031744908856773255354187356978276630394356874821345787992614524284580778327246433455049291405778430845498486366587619983908976667771237650357706647753992753332814803365381856567734244278341148986294910526427214290214390515305801755021147278725497232669081941725830136195646455368286701243369402712793040371378301784682786599018039026609923216448179479921564914179775067903851681440055529845164654471]
dec = []
p = 142291871983652659
delta = 98998887741403650
n = 1
primes = []
for i in range(23):
ps = p + delta * i
n *= ps
primes.append(ps)
e = 1337
phin = 1
for i in range(23):
phin *= primes[i]-1
d = inverse(e, phin)
for i in range(len(enc)):
dec.append(pow(enc[i], d, n))
benc = n2bbe(dec, 8)
denc = b2nbe(benc, 4)

key = [195948557, 3735928559, 181321950, 4277009102]
delta = 289739801
result = []
for j in range(0, len(denc)//2):
m1 = denc[2*j]
m2 = denc[2*j+1]
ttl = (11325165 + 114 * delta) & 0xFFFFFFFF
for j in range(114):
m2 = (m2 - ((key[3] + (m1 >> 6)) ^ (ttl + m1) ^ (key[2] + (m1 << 3)))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
m1 = (m1 - ((key[1] + (m2 >> 4)) ^ (ttl + m2) ^ (key[0] + (m2 << 5)))) & 0xFFFFFFFF
result.append(m1)
result.append(m2)
flag = n2bbe(result, 4)
print(flag)

flag{G4m3_1ik3_D1l_DecRypt&L04d3r_w17H_L0d4_14nG-yE7_4nO7h3R_4s53mB1y_L4NgUAg3!}

谢罪

第一次给大比赛出题,本来排查下来觉得应该不会有什么问题的,但是实际比赛过程中还是出现了一点问题(比如mmm的少量多解问题和loda的约束迷宫未能完全约束的非预期问题等),今后在出题的时候会更认真检查潜在的漏洞,尽量避免这些问题再发生