2021蓝帽总决赛

因为疫情,本来的北京国际会议中心的总决赛改成了线上,模式也从原来的awdplus改成了CTF解题赛,每个方向2个题。

以下是此次比赛中的两道逆向题,其中第二题因为在题目中嵌入了一个rsa密码学问题,比赛时也是一直卡在这里。

abc

题目中加了下面这种混淆,就是把要用的字符串或数据运算解密出来:

image-20210827115605895

然后很多的同一类型花指令,简单用idapython去除一下就好,主要看看函数大体就可以了,调试就很快。

1
2
3
4
5
6
7
8
9
10
11
12
from ida_bytes import *
addr = 0x400640

while addr <= 0x401592:
if get_byte(addr) == 0xe8 and get_byte(addr+1) == 0x0:
for i in range(17):
if i not in [10, 11, 12, 13, 14]:
patch_byte(addr+i, 0x90)
elif i == 10:
patch_byte(addr+i, 0xe8)
addr += 1
print('*'*100)

对输入下内存断点,断到处理输入的核心函数:

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
void __fastcall __noreturn sub_401308(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rax
__int64 v3; // rbp
char *v4; // rax
int v5; // ecx

if ( *(int *)(v3 - 16) >= v2 )
{
*(_BYTE *)(v3 - 128) = __ROR4__(__ROL4__(-1109410466, 15) ^ 0xDEADBEEF, 10);
*(_BYTE *)(v3 - 127) = __ROR4__(__ROL4__(-1109410466, 15) ^ 0xDEADBEEF, 10);
*(_BYTE *)(v3 - 126) = __ROR4__(__ROL4__(-2048934564, 15) ^ 0xDEADBEEF, 10);
for ( *(_DWORD *)(v3 - 16) = __ROR4__(__ROL4__(2111815003, 15) ^ 0xDEADBEEF, 10);
(unsigned __int64)*(int *)(v3 - 16) < 3;
*(_DWORD *)(v3 - 16) = *(_DWORD *)(v3 - 16) - 118 + 119 )
{
sub_4014B2();
*(_BYTE *)(*(int *)(v3 - 16) + v3 - 128) = ~*(_BYTE *)(*(int *)(v3 - 16) + v3 - 128);
}
sub_401718();
}
else
{
++*(_DWORD *)(v3 - 16);
sub_40133C();
v5 = *v4;
switch ( v5 )
{
case '#':
sub_400A65();
break;
case '$':
sub_40085B(a1, a2);
break;
case '%':
sub_400B6D();
break;
case '@':
sub_40095D(a1, a2);
break;
default:
sub_4013EE();
sub_400C6F(a1, a2);
break;
}
}
}

调试可以知道,我们的输入要对应case语句的4个参数。

看到第一个case对应的处理函数:就是对box进行数据换位置。

image-20210824170542878

再看最后的判断条件:

image-20210824171449397

从上可以抽象出来是一个4*4的华容道游戏。

开始我是写的一个dfs来搜索,因为限定条件比较少,很花时间,算法太菜了:

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

char enc[] = {1, 0x0A, 2, 3, 5, 0x0D, 6, 4, 9, -1, 7, 0x0B, 0x0E, 0x0F, 0x0C, 8};
char d[] = "#$%@";

char
char flag[200];

void fun1(char *a, int index)
{
char tmp = a[index];
a[index] = a[index+1];
a[index+1] = tmp;
}

void fun2(char *a, int index)
{
char tmp = a[index];
a[index] = a[index-4];
a[index-4] = tmp;

}

void fun3(char *a, int index)
{
char tmp = a[index];
a[index] = a[index-1];
a[index-1] = tmp;
}

void fun4(char *a, int index)
{
char tmp = a[index];
a[index] = a[index+4];
a[index+4] = tmp;

}

int find_index(char *a)
{
int index = 0;
for(int i = 0; i < 16; i++)
{
if(a[i] == -1)
{
index = i;
break;
}
}

return index;
}

int check(char *a)
{
for(int i = 0; i < 15; i++)
{
if(a[i] != i+1)
return 0;
}
return 1;
}

void dfs(char *a, int step)
{
if(check(a))
{
printf("found: %s", flag);
}
if(step > 16)
return ;

puts(flag);
for(int i = 0; i < 4; i++)
{
char *tmp = (char *)alloca(16);
memcpy(tmp, enc, 16);
int index = find_index(enc);

if((i == 0 && (index%4) >= 3) || (i == 1 && (index/4) <= 0) || (i == 2 && (index%4) <= 0) || (i == 3 && (index/4) >= 3))
continue;

if(i == 0)
{
flag[step] = d[i];
fun1(enc, index);
}
else if(i == 1)
{
flag[step] = d[i];
fun2(enc, index);
}
else if(i == 2)
{
flag[step] = d[i];
fun3(enc, index);
}
else if(i == 3)
{
flag[step] = d[i];
fun4(enc, index);
}
dfs(enc, step+1);
memcpy(enc, tmp, 16);

}

}


int main(void)
{
for(int i = 0; i < 16; i++)
{
printf("%d, ", enc[i]);
}
/* for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
printf("%02d ", enc[4*i+j]);
}
putchar(10);
}*/



dfs(enc, 0);
}

最后找到github上一个解华容道的项目https://github.com/Dpxx/Klotski-15puzzles

快的离谱:

image-20210824171658704

最后转化一下:$$##$$%%@@##$$%%@@

en

这个题目最后也只有2解。我比赛时一直卡在rsa算法上,没想到逆向题还真就嵌入一个rsa的题。

通过这个题学习基本的pyc文件,对对抗混淆做好基础。

看题目的给的密文,加上看pyc文件中的字符串可以猜测是rsa加密。

尝试使用uncompyle6对该pyc文件反编译,报错。

image-20210827120432079

这个问题了解过的原因基本上就是对字节码加了混淆,如下面举例这个。

1
2
3
4
5
6
7
|   1           0 JUMP_ABSOLUTE        [71 06 00]     6 
| 3 LOAD_CONST [64 FF FF] 65535 (FAKE!)
| >> 6 LOAD_CONST [64 00 00] 0 (Hello World)
| 9 PRINT_ITEM [47 -- --]
| 10 PRINT_NEWLINE [48 -- --]
| 11 LOAD_CONST [64 01 00] 1 (None)
| 14 RETURN_VALUE [53 -- --]

我们看到第二条指令3 LOAD_CONST [64 FF FF] 65535,对于如何解析这个指令我们可以看到这两篇官方文档:https://docs.python.org/2/library/dis.html https://github.com/Python/cpython/blob/2.7/Include/opcode.h

64 FF FF是实际在pyc文件中存放的字节码,也是这一条指令的组成,其中64是表示操作指令,FF FF表示所带参数。(在字节码对象的co_code中分为无参数指令(1字节)和有参数指令(3字节))。

image-20210827133938306

所以第二条指令的意思就是加载代码对象的常量表的第65535项到栈顶,常量表是存储了一个PyCodeObject中的常量的数组。而这里的index是65535,显然是超过了这个数组的大小。报的错也是数组越界。

但是第一条指令是一个绝对跳转,它跳到了编号为6的位置(也就是用>>符号标明了的)进而跳过他后面一条错误指令(3 LOAD_CONST)的执行。

所以实际执行的指令其实是这样:

1
2
3
4
5
|               6 LOAD_CONST           [64 00 00]     0 (Hello World)
| 9 PRINT_ITEM [47 -- --]
| 10 PRINT_NEWLINE [48 -- --]
| 11 LOAD_CONST [64 01 00] 1 (None)
| 14 RETURN_VALUE [53 -- --]

我们实验一下如何看一个PyCodeObject中常量的个数,如下所示。

1
2
3
4
5
6
7
8
9
10
11
>>> def p():
... a = 12
... b = 13
...
>>>
>>> p.__code__
<code object p at 0x000002509037CEA0, file "<stdin>", line 1>
>>> p = p.__code__
>>> p.co_consts
(None, 12, 13)
>>>

这里提到了PyCodeObject,那什么是PyCodeObject呢。

从code.h头文件中找到它的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Bytecode object */
typedef struct {
PyObject_HEAD
int co_argcount; /* #arguments, except *args */
int co_nlocals; /* #local variables */
int co_stacksize; /* #entries needed for evaluation stack */
int co_flags; /* CO_..., see below */
PyObject *co_code; /* instruction opcodes */
PyObject *co_consts; /* list (constants used) */
PyObject *co_names; /* list of strings (names used) */
PyObject *co_varnames; /* tuple of strings (local variable names) */
PyObject *co_freevars; /* tuple of strings (free variable names) */
PyObject *co_cellvars; /* tuple of strings (cell variable names) */
/* The rest doesn't count for hash/cmp */
PyObject *co_filename; /* string (where it was loaded from) */
PyObject *co_name; /* string (name, for reference) */
int co_firstlineno; /* first source line number */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
} PyCodeObject;

在我们写好python代码,执行的时候它是先编译成pyc字节码,再通过解释执行pyc字节码。pyc文件PyCodeObject对象的持久化保存方式。 它按照python代码中代码的作用域来划分成PyCodeObject的。

写了如下简单的py代码来实验:

它就应该是有3个PyCodeObject的,一个整体的module,和funa,q

1
2
3
4
5
6
7
8
9
10
11
12
13
def funa():
a = 1
print(a)

class q():
a = 2

def funb(self):
b = 3
print(b)

funa()
q().funb()

编译:

1
python2 -m py_compile 1.py

载入生成的1.pyc文件到010editor使用模板查看:一个大的module对象的object consts包括了funa,q的PyCodeObject对象。

image-20210827150600761

整体上来说这个pyc文件格式是很简单的,很多结构都是:类型+长度+内容 的格式。

回到做题的时候来。上面是用绝对跳转跳过错误代码的混淆字节码方式,其它还有虚假分支,重叠之类等,他们的原理其实都是差不多的,和x86平台下加的一般花指令极其类似。

然后回到这个题目上来,因为使用uncomple6没有反编译成功,我去尝试了使用marshal来反序列化pyc文件中的PyCodeObject,再使用diss模块进行反汇编。

注意:

pyc文件由三部分组成magic number + 源代码文件信息 + PyCodeObject

不同版本的python编译的pyc文件的magic number + 源代码文件信息都是不同的。

python2.7中除了4字节的magic number还有4字节的时间戳,所以PyCodeObject在第8个字节后面。

python3.5与python3.6在python2.7的基础上还多了4字节的源文件大小,所以PyCodeObject在12字节后面。

python3.7以上还增加了校验hash值,当4-8字节为全0时表示没有启用hash校验,其后的8-16字节表示时间戳与源文件大小;但当4-8字节为0100 0000或者0300 0000时,8-16字节表示文件的hash值。所以PyCodeObject在16字节后面。

我们来看一下这个pyc文件头部信息:420D0D0A表示python版本为3.7,它的4-8字节全0表明后面的8-16字节为时间戳与源文件大小。

image-20210831121607930

从上可以知道这个文件的PyCodeObject在16字节后面,因此下面的代码中使用的f.seek(16)

1
2
3
4
5
6
import marshal, dis
with open('en.pyc', 'rb') as f:
f.seek(16)
code = marshal.load(f)
f.close()
print(dis.dis(code))

可是在使用marshal进行反序列化时就出现了问题。

1
2
3
4
Traceback (most recent call last):
File "1.py", line 4, in <module>
code = marshal.load(f)
ValueError: bad marshal data (unknown type code)

这是python版本不匹配出现的问题,该pyc文件是python3.7编译的,而我的环境是python3.8。

最后使用pycdas成功反汇编了一部分,看到没有反汇编成功的提示:

1
Error disassembling en.pyc: vector::_M_range_check: __n (which is 63) >= this->size() (which is 14)

根据提示把对应的数字改小就可以了。

最后得到全部的反汇编结果:可能部分指令是不正确的,因为我上面对pyc文件的修改也是大概来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
en.pyc (Python 3.7)
[Code]
File Name: /somewhere/encrypt.py
Object Name: <module>
Arg Count: 0
KW Only Arg Count: 0
Locals: 0
Stack Size: 3
Flags: 0x00000040 (CO_NOFREE)
[Names]
'gmpy2'
'g'
'Crypto.Util.number'
'long_to_bytes'
'bytes_to_long'
'random'
'gen_num'
'gen_prime'
'po'
'e2'
'__name__'
'sys'
'len'
'argv'
'encode'
'base64'
'B'
'b64decode'
'flag'
[Var Names]
[Free Vars]
[Cell Vars]
[Constants]
0
None
(
'long_to_bytes'
'bytes_to_long'
)
[Code]
File Name: /somewhere/encrypt.py
Object Name: gen_num
Arg Count: 1
KW Only Arg Count: 0
Locals: 3
Stack Size: 6
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
'range'
'random'
'choice'
[Var Names]
'n_bits'
'res'
'i'
[Free Vars]
[Cell Vars]
[Constants]
None
0
1
[Disassembly]
0 LOAD_CONST 1: 0
2 STORE_FAST 1: res
4 SETUP_LOOP 50 (to 56)
6 LOAD_GLOBAL 0: range
8 LOAD_FAST 0: n_bits
10 CALL_FUNCTION 1
12 GET_ITER
14 FOR_ITER 38 (to 54)
16 STORE_FAST 2: i
18 LOAD_FAST 1: res
20 LOAD_CONST 1: 0
22 COMPARE_OP 3 (!=)
24 POP_JUMP_IF_FALSE 34
26 LOAD_FAST 1: res
28 LOAD_CONST 2: 1
30 INPLACE_LSHIFT
32 STORE_FAST 1: res
34 LOAD_FAST 1: res
36 LOAD_GLOBAL 1: random
38 LOAD_METHOD 2: choice
40 LOAD_CONST 1: 0
42 LOAD_CONST 2: 1
44 BUILD_LIST 2
46 CALL_METHOD 1
48 UNARY_POSITIVE
50 STORE_FAST 1: res
52 JUMP_ABSOLUTE 14
54 POP_BLOCK
56 LOAD_FAST 1: res
58 RETURN_VALUE
'gen_num'
[Code]
File Name: /somewhere/encrypt.py
Object Name: gen_prime
Arg Count: 1
KW Only Arg Count: 0
Locals: 3
Stack Size: 3
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
'gen_num'
'g'
'is_prime'
[Var Names]
'n_bits'
'res'
'b'
[Free Vars]
[Cell Vars]
[Constants]
None
1
0
[Disassembly]
0 LOAD_GLOBAL 0: gen_num
2 LOAD_FAST 0: n_bits
4 CALL_FUNCTION 1
6 STORE_FAST 1: res
8 SETUP_LOOP 54 (to 64)
10 LOAD_GLOBAL 1: g
12 LOAD_METHOD 2: is_prime
14 LOAD_FAST 1: res
16 CALL_METHOD 1
18 POP_JUMP_IF_TRUE 62
20 LOAD_CONST 1: 1
22 STORE_FAST 2: b
24 SETUP_LOOP 34 (to 60)
26 LOAD_FAST 2: b
28 LOAD_CONST 2: 0
30 COMPARE_OP 3 (!=)
32 POP_JUMP_IF_FALSE 58
34 LOAD_FAST 1: res
36 LOAD_FAST 2: b
38 BINARY_XOR
40 LOAD_FAST 1: res
42 LOAD_FAST 2: b
44 BINARY_AND
46 LOAD_CONST 1: 1
48 BINARY_LSHIFT
50 ROT_TWO
52 STORE_FAST 1: res
54 STORE_FAST 2: b
56 JUMP_ABSOLUTE 26
58 POP_BLOCK
60 JUMP_ABSOLUTE 10
62 POP_BLOCK
64 LOAD_FAST 1: res
66 RETURN_VALUE
'gen_prime'
[Code]
File Name: /somewhere/encrypt.py
Object Name: po
Arg Count: 3
KW Only Arg Count: 0
Locals: 5
Stack Size: 2
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
[Var Names]
'a'
'b'
'n'
'res'
'aa'
[Free Vars]
[Cell Vars]
[Constants]
None
1
0
[Disassembly]
0 LOAD_CONST 1: 1
2 STORE_FAST 3: res
4 LOAD_FAST 0: a
6 STORE_FAST 4: aa
8 SETUP_LOOP 52 (to 62)
10 LOAD_FAST 1: b
12 LOAD_CONST 2: 0
14 COMPARE_OP 3 (!=)
16 POP_JUMP_IF_FALSE 60
18 LOAD_FAST 1: b
20 LOAD_CONST 1: 1
22 BINARY_AND
24 LOAD_CONST 1: 1
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 42
30 LOAD_FAST 3: res
32 LOAD_FAST 4: aa
34 BINARY_MULTIPLY
36 LOAD_FAST 2: n
38 BINARY_MODULO
40 STORE_FAST 3: res
42 LOAD_FAST 4: aa
44 LOAD_FAST 4: aa
46 INPLACE_MULTIPLY
48 STORE_FAST 4: aa
50 LOAD_FAST 1: b
52 LOAD_CONST 1: 1
54 INPLACE_RSHIFT
56 STORE_FAST 1: b
58 JUMP_ABSOLUTE 10
60 POP_BLOCK
62 LOAD_FAST 3: res
64 LOAD_FAST 2: n
66 BINARY_MODULO
68 RETURN_VALUE
'po'
[Code]
File Name: /somewhere/encrypt.py
Object Name: e2
Arg Count: 1
KW Only Arg Count: 0
Locals: 14
Stack Size: 5
Flags: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[Names]
'type'
'bytes'
'AssertionError'
'len'
'bytes_to_long'
'gen_prime'
'g'
'next_prime'
'long_to_bytes'
'pow'
'print'
'str'
'digits'
'hex'
[Var Names]
'm'
'l'
'm1'
'm2'
'p'
'q'
'pp'
'qq'
'e'
'ee'
'n'
'nn'
'c1'
'c2'
[Free Vars]
[Cell Vars]
[Constants]
None
512
2
1024
2333
65535
[Disassembly]
0 LOAD_GLOBAL 0: type
2 LOAD_FAST 0: m
4 CALL_FUNCTION 1
6 LOAD_GLOBAL 1: bytes
8 COMPARE_OP 2 (==)
10 POP_JUMP_IF_TRUE 32
12 LOAD_GLOBAL 2: AssertionError
14 RAISE_VARARGS 1
16 LOAD_GLOBAL 11: str
18 LOAD_FAST 11: nn
20 CALL_FUNCTION 31
22 LOAD_CONST 1: 512
24 COMPARE_OP 0 (<)
26 POP_JUMP_IF_TRUE 47
28 LOAD_GLOBAL 2: AssertionError
30 RAISE_VARARGS 1
32 LOAD_GLOBAL 3: len
34 LOAD_FAST 0: m
36 CALL_FUNCTION 1
38 LOAD_CONST 2: 2
40 BINARY_FLOOR_DIVIDE
42 STORE_FAST 1: l
44 LOAD_GLOBAL 4: bytes_to_long
46 LOAD_FAST 0: m
48 LOAD_CONST 0: None
50 LOAD_FAST 1: l
52 BUILD_SLICE 2
54 BINARY_SUBSCR
56 CALL_FUNCTION 1
58 STORE_FAST 2: m1
60 LOAD_GLOBAL 4: bytes_to_long
62 LOAD_FAST 0: m
64 LOAD_FAST 1: l
66 LOAD_CONST 0: None
68 BUILD_SLICE 2
70 BINARY_SUBSCR
72 CALL_FUNCTION 1
74 STORE_FAST 3: m2
76 LOAD_GLOBAL 5: gen_prime
78 LOAD_CONST 3: 1024
80 CALL_FUNCTION 1
82 STORE_FAST 4: p
84 LOAD_GLOBAL 5: gen_prime
86 LOAD_CONST 3: 1024
88 CALL_FUNCTION 1
90 STORE_FAST 5: q
92 LOAD_GLOBAL 6: g
94 LOAD_METHOD 7: next_prime
96 LOAD_FAST 4: p
98 LOAD_CONST 4: 2333
100 BINARY_ADD
102 CALL_METHOD 1
104 STORE_FAST 6: pp
106 LOAD_GLOBAL 6: g
108 LOAD_METHOD 7: next_prime
110 LOAD_FAST 5: q
112 LOAD_CONST 4: 2333
114 BINARY_ADD
116 CALL_METHOD 1
118 STORE_FAST 7: qq
120 LOAD_GLOBAL 6: g
122 LOAD_METHOD 7: next_prime
124 LOAD_CONST 5: 65535
126 CALL_METHOD 1
128 STORE_FAST 8: e
130 LOAD_GLOBAL 6: g
132 LOAD_METHOD 7: next_prime
134 LOAD_FAST 8: e
136 CALL_METHOD 1
138 STORE_FAST 9: ee
140 LOAD_FAST 4: p
142 LOAD_FAST 5: q
144 BINARY_MULTIPLY
146 STORE_FAST 10: n
148 LOAD_FAST 6: pp
150 LOAD_FAST 7: qq
152 BINARY_MULTIPLY
154 STORE_FAST 11: nn
156 LOAD_GLOBAL 8: long_to_bytes
158 LOAD_GLOBAL 9: pow
160 LOAD_FAST 2: m1
162 LOAD_FAST 8: e
164 LOAD_FAST 10: n
166 CALL_FUNCTION 3
168 CALL_FUNCTION 1
170 STORE_FAST 12: c1
172 LOAD_GLOBAL 8: long_to_bytes
174 LOAD_GLOBAL 9: pow
176 LOAD_FAST 3: m2
178 LOAD_FAST 9: ee
180 LOAD_FAST 11: nn
182 CALL_FUNCTION 3
184 CALL_FUNCTION 1
186 STORE_FAST 13: c2
188 LOAD_GLOBAL 10: print
190 LOAD_GLOBAL 11: str
192 LOAD_FAST 10: n
194 CALL_FUNCTION 1
196 LOAD_FAST 11: nn
198 LOAD_METHOD 12: digits
200 CALL_METHOD 0
202 LOAD_FAST 12: c1
204 LOAD_FAST 13: c2
206 BINARY_ADD
208 LOAD_METHOD 13: hex
210 CALL_METHOD 0
212 CALL_FUNCTION 3
214 POP_TOP
216 LOAD_FAST 12: c1
218 LOAD_FAST 13: c2
220 BINARY_ADD
222 RETURN_VALUE
'e2'
'__main__'
2
1
b'ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0='
[Disassembly]
0 JUMP_ABSOLUTE 4
2 LOAD_CONST 11: '__main__'
4 LOAD_CONST 0: 0
6 LOAD_CONST 1: None
8 IMPORT_NAME 0: gmpy2
10 STORE_NAME 1: g
12 LOAD_CONST 0: 0
14 LOAD_CONST 2: ('long_to_bytes', 'bytes_to_long')
16 IMPORT_NAME 2: Crypto.Util.number
18 IMPORT_FROM 3: long_to_bytes
20 STORE_NAME 3: long_to_bytes
22 IMPORT_FROM 4: bytes_to_long
24 STORE_NAME 4: bytes_to_long
26 POP_TOP
28 LOAD_CONST 0: 0
30 LOAD_CONST 1: None
32 IMPORT_NAME 5: random
34 STORE_NAME 5: random
36 LOAD_CONST 3: <CODE> gen_num
38 LOAD_CONST 4: 'gen_num'
40 MAKE_FUNCTION 0
42 STORE_NAME 6: gen_num
44 LOAD_CONST 5: <CODE> gen_prime
46 LOAD_CONST 6: 'gen_prime'
48 MAKE_FUNCTION 0
50 STORE_NAME 7: gen_prime
52 LOAD_CONST 7: <CODE> po
54 LOAD_CONST 8: 'po'
56 MAKE_FUNCTION 0
58 STORE_NAME 8: po
60 LOAD_CONST 9: <CODE> e2
62 LOAD_CONST 10: 'e2'
64 MAKE_FUNCTION 0
66 STORE_NAME 9: e2
68 LOAD_NAME 10: __name__
70 LOAD_CONST 11: '__main__'
72 COMPARE_OP 2 (==)
74 POP_JUMP_IF_FALSE 144
76 LOAD_CONST 0: 0
78 LOAD_CONST 1: None
80 IMPORT_NAME 11: sys
82 STORE_NAME 11: sys
84 LOAD_NAME 12: len
86 LOAD_NAME 11: sys
88 LOAD_ATTR 13: argv
90 CALL_FUNCTION 1
92 LOAD_CONST 12: 2
94 COMPARE_OP 5 (>=)
96 POP_JUMP_IF_FALSE 118
98 LOAD_NAME 9: e2
100 LOAD_NAME 11: sys
102 LOAD_ATTR 13: argv
104 LOAD_CONST 13: 1
106 BINARY_SUBSCR
108 LOAD_METHOD 14: encode
110 CALL_METHOD 0
112 CALL_FUNCTION 1
114 POP_TOP
116 JUMP_FORWARD 26 (to 144)
118 LOAD_CONST 0: 0
120 LOAD_CONST 1: None
122 IMPORT_NAME 15: base64
124 STORE_NAME 16: B
126 LOAD_NAME 16: B
128 LOAD_METHOD 17: b64decode
130 LOAD_CONST 14: b'ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0='
132 CALL_METHOD 1
134 STORE_NAME 18: flag
136 LOAD_NAME 9: e2
138 LOAD_NAME 18: flag
140 CALL_FUNCTION 1
142 POP_TOP
144 LOAD_CONST 1: None
146 RETURN_VALUE

都是很简单的代码,稍微熟悉下指令结构就能很快知道代码的功能。

开始是一个gen_numer的方法:

1
2
3
4
5
6
def gen_num(n_bits):
res = 1
for i in range(n_bits):
if res != 0:
res <<= 1
res |= random.choice([0, 1])

然后gem_prime的方法:

1
2
3
4
5
6
7
def gen_prime(n_bits):
res = gen_num(n_bits)
while !gmpy2.is_prime(res):
b = 1
while b != 0:
res, b = res^b, (res&b) << 1
return res

接着是一个powmod运算:这个它是用模重复平方实现的。

1
2
3
4
5
6
7
8
9
10
11
def po(a, b, n):
res = 1
aa = a
while b != 0:
if b&1 == 1:
res = res*aa%n
aa *= aa
b >> 1
res = res%n

return res

再就是本题的关键加密函数e2,从反汇编结果来看这里也是对代码加了混淆的,因为我们的参数是bytes形式的,所以肯定跳过那个异常执行,但是反汇编或反编译器工具就没有那么智能了。

image-20210827161449207

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def e2(m):
if type(m) == bytes:
l = len(m)//2
m1 = bytes_to_long(m[:l])
m2 = bytes_to_long(m[l:])
p = gen_prime(1024)
q = gen_prime(1024)
pp = gmpy2.next_prime(p+2333)
qq = gmpy2.next_prime(q+2333)
e = gmpy2.next_prime(65535)
ee = gmpy2.next_prime(e)
n = p*q
nn = pp*qq
c1 = long_to_bytes(pow(m1, e, n))
c2 = long_to_bytes(pow(m2, ee, nn))
print(n)
print(nn)
print((c1+c2).hex())

最后就是main函数的地方:

1
2
3
4
5
6
if __name__ == '__main__':
if len(sys.argv) >= 2:
e2(sys.argv[1].encode())
else:
flag = base64.b64decode('ZmxhZ3t0aGlzaXNhZmFrZWZsYWdhZ2Fhc2FzaGRhc2hkc2hkaH0=')
e2(flag)

至此我将整个pyc文件手动的读代码恢复到了原始的py文件。

比赛时我也只是关心了那个加密函数e2,生成素数算法就是随机生成素数而已。

加密就是用2对rsa公钥加密对我们明文分为2组加密,然后密文给了2组公钥的n与nn:

1
2
3
4
5
6
7
8
p = 
q =
pp = gmpy2.next_prime(p+2333)
qq = gmpy2.next_prime(q+2333)
n = p*q
nn = pp*qq

n与nn已知

开始还以为可以直接用factor把n分解出来,并不行,比赛时也是卡在了怎么根据以上的条件求解出p,q,pp,qq

以前在逆向中遇到的rsa都是n可以直接分解的,这次还真就在逆向中放一个要利用信安数学相关知识来分解n的题。

1
10300808326934539089496666241808264289631957459372648156286399524715435483257526083909012656240599916663153630994400397551123967736269088895097145175999170121832669199408651009372742448196961434772052680714475103278704994244915332148949308972258132346198706995878511207707020032852291909422169657384059305615332901933166314692127020030962059133945677194815714731744932280037687773557589292839426111679593131496468880818820566335362063945141576571029271455695757725169819071536590541808603312689890186432713168331831945391117398124164372551511615664022982639779869597584768094658974144703654232643726744397158318139843 10300808326934539089496666241808264289631957459372648156286399524715435483257526083909012656240599916663153630994400397551123967736269088895097145175999170121832669199408651009372742448196961434772052680714475103278704994244915332148949308972258132346198706995878511207707020032852291909422169657384059306119730985949350246133999803589372738154347587848281413687500584822677442973180875153089761224816081452749380588888095064009160267372694200256546854314017937003988172151851703041691419537865664897608475932582537945754540823276273979713144072687287826518630644255675609067675836382036436064703619178779628644141463 22cca5150ca0bb2132f68302dc7441e52b91ae7252e44cc13ed83e58253a9aaaa55e095ba36748dff7ea21fff553f8c4656e77a508b64da054f1381b7e2d0600bcec6ed9e1cc8d14c2362aaef7a972a714f88e5afb2d39e2d77d0c22a449ca2cfb0802c138f20e0ecbd3c174151cdb8e8ca6d89aa3c503615ebfbc851af5ac51dcfa8b5869b775b57a27b9e4346979180d89b303cae2c5d9e6cabb3c9947837bd8f92333532d4b54dd72ea354000600066328f6f4329147df195ec78a7ab9d39973ce0fd6511e7a0de54737bee64476ba531604f0375b08adf7d768c41ba9e2ba88468d126561a134de79dc0217c1c56d219ca6747103618e46f35281feb9e6050c93e32e26e21ee2c3d495f60db2fad9f9a5c570c9f97aee698024ebff6163ef26e32958872db7c593d7f41f90981b8db45aa01085be1e61f7603ecf3d5c032dd90dea791cd9825299548c0cbe7dadabc157048a7fd5cd4bcb1cfeaf0bd2d679f66ceb0b1c33ec04bd20317f872c85d500a3475833f983fdee59b3f61a731e2a8b9a60bd7d840f46e97f06dd4fd8ad1cb4d13a82da01938801c33835ceaf34e1cf62ebdde7ac68b17c2a236b64ffacd2a0e7258571ce570871aea9ff309df63c0a3abcfa0c05d159a82f9fa3f3ad73944e4ae33c3432c8b65c0d6fe9b560220b14abe5886188fc1e6afa4bb4395669618387224422acf20b519af902225e270
-------------本文结束感谢您的阅读-------------