Python逆向

Re_Point

感觉好久没有好好做过 CTF 了,想念那种坐牢的感觉了(楽🙇‍♀️

第一次认真接触算是在省赛的时候吧,当时给的是一个 exe 类型的文件,通过 pyinstaller 将它转变成 .pyc 文件之后由于没有工具可以把它变成 .py 文件,也不知道有 dis 这个库可以将 pyc 文件转换成 python汇编,所以当时就 g 掉了。

省赛连续两年都出了相关的题目,是得了解一下了

python 汇编

pyc 是二进制文件,可以直接由 python 虚拟机来执行。显而易见,这种玩应我们是看不懂的,通过一些工具,例如:uncompyle6。通过命令 uncompyle6 -o xxxx.py xxxx.pyc 就可以将 pyc 文件直接转换为 py 文件,那逆向难度就直接降低了,只要看懂 py 文件就可以了。但是这都是最理想的状态,如果我们对 pyc 文件进行一些防止反编译的操作导致 uncompyle6 没有办法工作,那么我们就只能通过分析 python 汇编还原出基本的 py 文件。

通过 python 中的 dis 模块,可以将 pyc 文件转换为 python 汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2           0 LOAD_CONST               1 ('1234abcd')
2 STORE_FAST 0 (str)

3 4 LOAD_GLOBAL 0 (range)
6 LOAD_FAST 0 (str)
8 CALL_FUNCTION 1
10 GET_ITER
>> 12 FOR_ITER 6 (to 26)
14 STORE_FAST 1 (i)

4 16 LOAD_GLOBAL 1 (print)
18 LOAD_FAST 1 (i)
20 CALL_FUNCTION 1
22 POP_TOP
24 JUMP_ABSOLUTE 6 (to 12)

句子的结构如下

1
源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值

纯 python 逆向

很简单的一个 py 文件逆向,直接对照 py 文件写出相对应的 exp 即可

1
2
3
4
5
6
7
8
9
10
11
12
print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
num = ((input1[i] + i) % 128 + 128) % 128
code += num

for i in range(l - 1):
code[i] = code[i] ^ code[(i + 1)]

print code
code = ['\x1f', '\x12', '\x1d', '(', '0', '4', '\x01', '\x06', '\x14', '4', ',', '\x1b', 'U', '?', 'o', '6', '*', ':', '\x01', 'D', ';', '%', '\x13']

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
code = ['\x1f', '\x12', '\x1d', '(', '0', '4', '\x01', '\x06', '\x14', '4', ',', '\x1b', 'U', '?', 'o', '6', '*', ':', '\x01', 'D', ';', '%', '\x13']
code = [ord(x) for x in code]

for i in range(len(code)-2, -1, -1):
code[i] ^= code[i+1]
# code[i] = code[i]%128

for i in range(len(code)):
code[i] -= i
if code[i] < 0:
code[i] += 128
code[i] = code[i]%128
print(code)
print("".join([chr(x) for x in code]))

使用工具进行反编译

在前面的 python 汇编里简单介绍了 uncompyle6 的使用,这里介绍另一个工具 pycdc

通过命令下载 pycdc,或者直接下载 zip 包解压

1
git clone https://github.com/zrax/pycdc.git

执行命令安装 cmake

1
apt-get install cmake

进入下载 \ 解压缩的文件夹中,执行命令生成 CMakeFiles

1
cmake .

之后就可以利用 pycdc 对 pyc 文件进行反编译了

1
./pycdc <pyc path>

就可以得到 python 原始代码了,得到原始代码之后的操作步骤就和上面的纯 python 逆向一样了

image-20221105163152219

geek大挑战2020_python1,反编译之后得到的原始代码如下

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

def b(a):
return a & 0xFFFFFFFFFFFFFFFF


def c(str):
return struct.unpack('<Q', str)[0]


def d(a):
for i in range(64):
a = a * 2
if a > 0xFFFFFFFFFFFFFFFF:
a = b(a)
a = b(a ^ 0xB0004B7679FA26B3)
continue
return a

if __name__ == '__main__':
cmp_data = [
0x6E8DD76D3B876F95,
0xE206DA09DAF4BED6,
0x77559D346E134BF1,
0x61CE39CAC5EAF891,
0x656C3C155520E36F]
input = raw_input('plz input your flag:')
if len(input) % 8 != 0:
for i in range(8 - len(input) % 8):
input += '\x00'

arr = []
for i in range(len(input) / 8):
value = d(c(input[i * 8:i * 8 + 8]))
arr.append(value)

for i in range(5):
if arr[i] != cmp_data[i]:
print 'fail'
time.sleep(5)
exit()
continue
print 'success'
time.sleep(5)
exit()

个人觉得 pycdc 比 uncompyle6 更加友好,反编译的内容和精确度也更胜一筹👍

这道 python 逆向的难点就在于加密的时候看起来好像是没有判断是否有进入过函数 d(a) 的 if 语句中的,但实际上,如果有经过这个 if 分支,那么出来的数字就必定为奇数,如果没有,那么出来的数字就必定为偶数。

按照这个理论,我们就可以编写出 exp

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

def re_d(a):
for _ in range(64):
if (a%2 == 1): # 若为奇数
tmp = a ^ 0xB0004B7679FA26B3
a = tmp + 0xFFFFFFFFFFFFFFFF + 1
a = a//2
return a



cmp_data = [
0x6E8DD76D3B876F95,
0xE206DA09DAF4BED6,
0x77559D346E134BF1,
0x61CE39CAC5EAF891,
0x656C3C155520E36F]

flag = ""
for i in cmp_data:
tmp = re_d(i)
print(hex(tmp))
flag += struct.pack('<Q',tmp).decode("utf-8")

print("flag:",flag)

python 汇编还原 py 文件

当然了,上面都是最理想的一种状态因为可以直接得到 py 文件,下面来说说通过 python 字节码还原 py 文件。

上面也已经介绍了 python 汇编语句的格式,也有文章将 python 汇编各种语言类型进行了总结,以及官网 dis 模块也给出了字节码指令的解释,对照着这些就可以将 pyc 文件所转换的 python 汇编还原成 py 文件。

下面对一个例子进行逐句分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
1           0 LOAD_CONST               0 (<code object keyinit at 0x0000028C1CC11D20, file "crackPYC.py", line 1>)          //载入函数内容
2 LOAD_CONST 1 ('keyinit')
4 MAKE_FUNCTION 0 //定义函数
6 STORE_NAME 0 (keyinit) //存储函数

8 8 LOAD_NAME 1 (__name__)
10 LOAD_CONST 2 ('__main__')
12 COMPARE_OP 2 (==)
14 POP_JUMP_IF_FALSE 250 // if __name__ =='__name__'

9 16 LOAD_NAME 2 (print)
18 LOAD_CONST 3 ('Can you crack pyc?')
20 CALL_FUNCTION 1
22 POP_TOP // print('Can you crack pyc?')

10 24 LOAD_NAME 3 (input)
26 LOAD_CONST 4 ('Plz give me your flag:')
28 CALL_FUNCTION 1
30 STORE_NAME 4 (str) // str=input('Plz give me your flag:')

11 32 LOAD_CONST 5 (108)
34 LOAD_CONST 6 (17)
36 LOAD_CONST 7 (42)
38 LOAD_CONST 8 (226)
40 LOAD_CONST 9 (158)
42 LOAD_CONST 10 (180)
44 LOAD_CONST 11 (96)
46 LOAD_CONST 12 (115)
48 LOAD_CONST 13 (64)
50 LOAD_CONST 14 (24)
52 LOAD_CONST 15 (38)
54 LOAD_CONST 16 (236)
56 LOAD_CONST 17 (179)
58 LOAD_CONST 18 (173)
60 LOAD_CONST 19 (34)
62 LOAD_CONST 20 (22)
64 LOAD_CONST 21 (81)
66 LOAD_CONST 22 (113)
68 LOAD_CONST 15 (38)
70 LOAD_CONST 23 (215)
72 LOAD_CONST 24 (165)
74 LOAD_CONST 25 (135)
76 LOAD_CONST 26 (68)
78 LOAD_CONST 27 (7)

12 80 LOAD_CONST 28 (119)
82 LOAD_CONST 29 (97)
84 LOAD_CONST 30 (45)
86 LOAD_CONST 31 (254)
88 LOAD_CONST 32 (250)
90 LOAD_CONST 33 (172)
92 LOAD_CONST 34 (43)
94 LOAD_CONST 35 (62)
96 BUILD_LIST 32
98 STORE_NAME 5 (text) // text=[108, 17, 42, ... , 172, 43, 62]

13 100 LOAD_NAME 6 (len)
102 LOAD_NAME 4 (str)
104 CALL_FUNCTION 1
106 LOAD_CONST 36 (32)
108 COMPARE_OP 3 (!=)
110 POP_JUMP_IF_TRUE 140 // if(len(str) != 32)
112 LOAD_NAME 4 (str)
114 LOAD_CONST 37 (0)
116 LOAD_CONST 27 (7)
118 BUILD_SLICE 2
120 BINARY_SUBSCR // TOS = str[0:7]
122 LOAD_CONST 38 ('DASCTF{')
124 COMPARE_OP 3 (!=)
126 POP_JUMP_IF_TRUE 140 // if(TOS != 'DASCTF{')
128 LOAD_NAME 4 (str)
130 LOAD_CONST 39 (31)
132 BINARY_SUBSCR // TOS = str[31]
134 LOAD_CONST 40 ('}')
136 COMPARE_OP 3 (!=)
138 POP_JUMP_IF_FALSE 154 // if(TOS != '{')

14 >> 140 LOAD_NAME 2 (print)
142 LOAD_CONST 41 ('Bye bye~~')
144 CALL_FUNCTION 1 // print('Bye bye~~')
146 POP_TOP

15 148 LOAD_NAME 7 (exit)
150 CALL_FUNCTION 0
152 POP_TOP // exit()

16 >> 154 LOAD_NAME 8 (list)
156 LOAD_NAME 4 (str)
158 CALL_FUNCTION 1
160 STORE_NAME 9 (st) // st = list(str)

17 162 BUILD_LIST 0
164 STORE_NAME 10 (key) // key = []

18 166 LOAD_NAME 0 (keyinit)
168 LOAD_NAME 10 (key)
170 CALL_FUNCTION 1
172 POP_TOP // key = keyinit()

19 174 SETUP_LOOP 48 (to 224)
176 LOAD_NAME 11 (range)
178 LOAD_CONST 36 (32)
180 CALL_FUNCTION 1
182 GET_ITER
>> 184 FOR_ITER 36 (to 222)
186 STORE_NAME 12 (i) // for i in range(32)

20 188 LOAD_NAME 13 (ord)
190 LOAD_NAME 4 (str)
192 LOAD_NAME 12 (i)
194 BINARY_SUBSCR
196 CALL_FUNCTION 1 // TOS = ord(str[i])
198 LOAD_NAME 10 (key)
200 LOAD_NAME 12 (i)
202 LOAD_NAME 6 (len)
204 LOAD_NAME 10 (key)
206 CALL_FUNCTION 1 // TOS = len(key)
208 BINARY_MODULO // TOS = i % len(key)
210 BINARY_SUBSCR // TOS = key[i % len(key)]
212 BINARY_XOR // TOS = ord(str[i]) ^ key[i % len(key)]
214 LOAD_NAME 9 (st)
216 LOAD_NAME 12 (i)
218 STORE_SUBSCR // st[i] = ord(str[i]) ^ key[i % len(key)]
220 JUMP_ABSOLUTE 184
>> 222 POP_BLOCK

21 >> 224 LOAD_NAME 9 (st)
226 LOAD_NAME 5 (text)
228 COMPARE_OP 2 (==)
230 POP_JUMP_IF_FALSE 242 // if(st == text)

22 232 LOAD_NAME 2 (print)
234 LOAD_CONST 42 ('Congratulations and you are good at PYC!')
236 CALL_FUNCTION 1 // print('Congratulations and you are good at PYC!')
238 POP_TOP
240 JUMP_FORWARD 8 (to 250)

24 >> 242 LOAD_NAME 2 (print)
244 LOAD_CONST 43 ('Sorry,plz learn more about pyc.')
246 CALL_FUNCTION 1 // print('Sorry,plz learn more about pyc.')
248 POP_TOP
>> 250 LOAD_CONST 44 (None)
252 RETURN_VALUE

Disassembly of <code object keyinit at 0x0000028C1CC11D20, file "crackPYC.py", line 1>:
2 0 LOAD_CONST 1 (0)
2 STORE_FAST 1 (num) //num=0

3 4 SETUP_LOOP 42 (to 48)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 2 (8)
10 CALL_FUNCTION 1
12 GET_ITER
>> 14 FOR_ITER 30 (to 46)
16 STORE_FAST 2 (i) //for i in range(8)

4 18 LOAD_FAST 1 (num)
20 LOAD_CONST 3 (7508399208111569251)
22 BINARY_SUBTRACT
24 LOAD_CONST 4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST 1 (num) // num=(num-7508399208111569251)%4294967295

5 30 LOAD_FAST 0 (key)
32 LOAD_METHOD 1 (append)
34 LOAD_FAST 1 (num)
36 LOAD_CONST 5 (24)
38 BINARY_RSHIFT
40 CALL_METHOD 1 // key.append(num>>24)
42 POP_TOP
44 JUMP_ABSOLUTE 14
>> 46 POP_BLOCK
>> 48 LOAD_CONST 0 (None)
50 RETURN_VALUE

对各汇编语句进行分析之后,原始 py 文件的大致意思也就出来了:使用 keyinit 函数生成 8 个数字储存在列表 key 中,将输入字符串的字符转为 ascii 码值之后利用 key 进行循环异或,将最后得到的结果和 text 进行比较。exp 也可以出来了

1
2
3
4
5
6
7
8
9
10
11
key=[]
for i in range(8):
num=(num-7508399208111569251)%4294967295
key.append(num>>24)

text=[108,17,42,226,158,180,96,115,64,24,38,236,179,173,34,22,81,113,38,215,165,135,68,7,119,97,45,254,250,172,43,62]

flag=""
for i in range(32):
flag+=chr(text[i]^key[i % len(key)] )
print(flag)

增加亿点点难度

题目来源:hgame2022 babyPyc

首先利用工具对 pyc 文件进行反编译,看看是否能够直接获得 py 文件,但是不管是 uncompyle6 还是 pycdc 都寄了(hhh,那就只能用通过 dis.dis() 获得 python 汇编来分析了

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
>>> import dis, marshal
>>> with open("babyPyc.pyc", 'rb') as fp:
... print(fp.read(16)) // 读取前16字节的 pyc 文件头
... raw = marshal.loads(fp.read())
... print(dis.dis(raw))
...
b'B\r\r\n\x00\x00\x00\x00\xdaR%^ \x04\x00\x00' //此为 pyc 文件头
3 0 JUMP_ABSOLUTE 2
>> 2 LOAD_CONST 0 (0)
4 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (os)
8 STORE_NAME 0 (os)
10 LOAD_CONST 0 (0)
12 LOAD_CONST 1 (None)
14 IMPORT_NAME 1 (sys)

4 16 STORE_NAME 1 (sys)
18 LOAD_CONST 0 (0)
20 LOAD_CONST 2 (('b64encode',))
22 IMPORT_NAME 2 (base64)
24 IMPORT_FROM 3 (b64encode)
26 STORE_NAME 3 (b64encode)

6 28 POP_TOP
30 LOAD_CONST 3 (b'/KDq6pvN/LLq6tzM/KXq59Oh/MTqxtOTxdrqs8OoR3V1X09J')

8 32 STORE_GLOBAL 4 (O0o)
34 LOAD_CONST 4 (<code object getFlag at 0x000001CB0B452030, file "task.py", line 8>)
36 LOAD_CONST 5 ('getFlag')
38 MAKE_FUNCTION 0

16 40 STORE_NAME 5 (getFlag)
42 LOAD_NAME 5 (getFlag)
44 CALL_FUNCTION 0

18 46 STORE_NAME 6 (flag)
48 LOAD_NAME 6 (flag)
50 LOAD_CONST 1 (None)
52 LOAD_CONST 6 (6)
54 BUILD_SLICE 2
56 BINARY_SUBSCR
58 LOAD_CONST 7 (b'hgame{')
60 COMPARE_OP 3 (!=)
62 POP_JUMP_IF_TRUE 76
64 LOAD_NAME 6 (flag)
66 LOAD_CONST 8 (-1)
68 BINARY_SUBSCR
70 LOAD_CONST 9 (125)
72 COMPARE_OP 3 (!=)

19 74 POP_JUMP_IF_FALSE 94
>> 76 LOAD_NAME 7 (print)
78 LOAD_CONST 10 ('Incorrect format!')
80 CALL_FUNCTION 1

20 82 POP_TOP
84 LOAD_NAME 1 (sys)
86 LOAD_METHOD 8 (exit)
88 LOAD_CONST 11 (1)
90 CALL_METHOD 1

22 92 POP_TOP
>> 94 LOAD_NAME 6 (flag)
96 LOAD_CONST 6 (6)
98 LOAD_CONST 8 (-1)
100 BUILD_SLICE 2
102 BINARY_SUBSCR

23 104 STORE_NAME 9 (raw_flag)
106 LOAD_NAME 10 (len)
108 LOAD_NAME 6 (flag)
110 CALL_FUNCTION 1
112 LOAD_CONST 12 (7)
114 BINARY_SUBTRACT
116 LOAD_CONST 13 (36)
118 COMPARE_OP 3 (!=)

24 120 POP_JUMP_IF_FALSE 140
122 LOAD_NAME 7 (print)
124 LOAD_CONST 14 ('Wrong length!')
126 CALL_FUNCTION 1

25 128 POP_TOP
130 LOAD_NAME 1 (sys)
132 LOAD_METHOD 8 (exit)
134 LOAD_CONST 15 (2)
136 CALL_METHOD 1

27 138 POP_TOP
>> 140 LOAD_NAME 9 (raw_flag)
142 LOAD_CONST 1 (None)
144 LOAD_CONST 1 (None)
146 LOAD_CONST 8 (-1)
148 BUILD_SLICE 3
150 BINARY_SUBSCR

28 152 STORE_NAME 9 (raw_flag)
154 LOAD_CONST 16 (<code object <listcomp> at 0x000001CB0B44DE40, file "task.py", line 28>)
156 LOAD_CONST 17 ('<listcomp>')
158 MAKE_FUNCTION 0
160 LOAD_NAME 11 (range)
162 LOAD_CONST 6 (6)
164 CALL_FUNCTION 1
166 GET_ITER
168 CALL_FUNCTION 1

30 170 STORE_NAME 12 (ciphers)
172 SETUP_LOOP 86 (to 260)
174 LOAD_NAME 11 (range)
176 LOAD_CONST 18 (5)
178 CALL_FUNCTION 1
180 GET_ITER
>> 182 FOR_ITER 74 (to 258)

31 184 STORE_NAME 13 (row)
186 SETUP_LOOP 68 (to 256)
188 LOAD_NAME 11 (range)
190 LOAD_CONST 6 (6)
192 CALL_FUNCTION 1
194 GET_ITER
>> 196 FOR_ITER 56 (to 254)

32 198 STORE_NAME 14 (col)
200 LOAD_NAME 12 (ciphers)
202 LOAD_NAME 13 (row)
204 BINARY_SUBSCR
206 LOAD_NAME 14 (col)
208 DUP_TOP_TWO
210 BINARY_SUBSCR
212 LOAD_NAME 12 (ciphers)
214 LOAD_NAME 13 (row)
216 LOAD_CONST 11 (1)
218 BINARY_ADD
220 BINARY_SUBSCR
222 LOAD_NAME 14 (col)
224 BINARY_SUBSCR
226 INPLACE_ADD
228 ROT_THREE

33 230 STORE_SUBSCR
232 LOAD_NAME 12 (ciphers)
234 LOAD_NAME 13 (row)
236 BINARY_SUBSCR
238 LOAD_NAME 14 (col)
240 DUP_TOP_TWO
242 BINARY_SUBSCR
244 LOAD_CONST 19 (256)
246 INPLACE_MODULO
248 ROT_THREE
250 STORE_SUBSCR
252 JUMP_ABSOLUTE 196
>> 254 POP_BLOCK
>> 256 JUMP_ABSOLUTE 182

35 >> 258 POP_BLOCK
>> 260 LOAD_CONST 20 (b'')

36 262 STORE_NAME 15 (cipher)
264 SETUP_LOOP 70 (to 336)
266 LOAD_NAME 11 (range)
268 LOAD_CONST 6 (6)
270 CALL_FUNCTION 1
272 GET_ITER
>> 274 FOR_ITER 58 (to 334)

37 276 STORE_NAME 13 (row)
278 LOAD_CONST 0 (0)

38 280 STORE_NAME 14 (col)
282 SETUP_LOOP 46 (to 330)
>> 284 LOAD_NAME 14 (col)
286 LOAD_CONST 6 (6)
288 COMPARE_OP 0 (<)
290 EXTENDED_ARG 1

39 292 POP_JUMP_IF_FALSE 328
294 LOAD_NAME 15 (cipher)
296 LOAD_NAME 16 (bytes)
298 LOAD_NAME 12 (ciphers)
300 LOAD_NAME 13 (row)
302 BINARY_SUBSCR
304 LOAD_NAME 14 (col)
306 BINARY_SUBSCR
308 BUILD_LIST 1
310 CALL_FUNCTION 1
312 INPLACE_ADD

40 314 STORE_NAME 15 (cipher)
316 LOAD_NAME 14 (col)
318 LOAD_CONST 11 (1)
320 INPLACE_ADD
322 STORE_NAME 14 (col)
324 EXTENDED_ARG 1
326 JUMP_ABSOLUTE 284
>> 328 POP_BLOCK
>> 330 EXTENDED_ARG 1
332 JUMP_ABSOLUTE 274

42 >> 334 POP_BLOCK
>> 336 LOAD_NAME 3 (b64encode)
338 LOAD_NAME 15 (cipher)
340 CALL_FUNCTION 1

44 342 STORE_NAME 15 (cipher)
344 LOAD_NAME 15 (cipher)
346 LOAD_GLOBAL 4 (O0o)
348 COMPARE_OP 2 (==)
350 EXTENDED_ARG 1

45 352 POP_JUMP_IF_FALSE 364
354 LOAD_NAME 7 (print)
356 LOAD_CONST 21 ('Great, this is my flag.')
358 CALL_FUNCTION 1
360 POP_TOP

47 362 JUMP_FORWARD 8 (to 372)
>> 364 LOAD_NAME 7 (print)
366 LOAD_CONST 22 ('Wrong flag.')
368 CALL_FUNCTION 1
370 POP_TOP
>> 372 LOAD_CONST 1 (None)
374 RETURN_VALUE

Disassembly of <code object getFlag at 0x000001CB0B452030, file "task.py", line 8>:
10 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Give me the flag')
4 CALL_FUNCTION 1
6 POP_TOP

11 8 LOAD_GLOBAL 1 (input)
10 LOAD_CONST 2 ('> ')
12 CALL_FUNCTION 1
14 STORE_FAST 0 (flag)

12 16 LOAD_FAST 0 (flag)
18 LOAD_METHOD 2 (encode)
20 CALL_METHOD 0
22 STORE_FAST 0 (flag)

13 24 LOAD_CONST 3 (b'QreZsrN+Wp3C0rJ4ccfXpJO8n9jIkIDNrteuxK6pR3JPZX5Q')
26 STORE_GLOBAL 3 (O0o)

14 28 LOAD_FAST 0 (flag)
30 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x000001CB0B44DE40, file "task.py", line 28>:
28 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 26 (to 32)
6 STORE_DEREF 0 (col)
8 LOAD_CLOSURE 0 (col)
10 BUILD_TUPLE 1
12 LOAD_CONST 0 (<code object <listcomp> at 0x000001CB0B44D150, file "task.py", line 28>)
14 LOAD_CONST 1 ('<listcomp>.<listcomp>')
16 MAKE_FUNCTION 8
18 LOAD_GLOBAL 0 (range)
20 LOAD_CONST 2 (6)
22 CALL_FUNCTION 1
24 GET_ITER
26 CALL_FUNCTION 1
28 LIST_APPEND 2
30 JUMP_ABSOLUTE 4
>> 32 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x000001CB0B44D150, file "task.py", line 28>:
28 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 20 (to 26)
6 STORE_FAST 1 (row)
8 LOAD_GLOBAL 0 (raw_flag)
10 LOAD_CONST 0 (6)
12 LOAD_FAST 1 (row)
14 BINARY_MULTIPLY
16 LOAD_DEREF 0 (col)
18 BINARY_ADD
20 BINARY_SUBSCR
22 LIST_APPEND 2
24 JUMP_ABSOLUTE 4
>> 26 RETURN_VALUE

python 汇编很长,在这里就不做一一分析了,对照下面的所还原出来的 py 文件内容,大致就可以读懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import os,sys
from base64 import b64encode

O0o=b'/KDq6pvN/LLq6tzM/KXq59Oh/MTqxtOTxdrqs8OoR3V1X09J'

def getFlag():
global O0o
print('Give me the flag')
flag=input('>')
flag=flag.encode()
O0o=b'QreZsrN+Wp3C0rJ4ccfXpJO8n9jIkIDNrteuxK6pR3JPZX5Q'
return flag


flag=getFlag()

if flag[:6]!='hgame{' or flag[-1]!='}':
print('Incorrect format!')
sys.exit(1)

raw_flag=flag[6:-1]

if(len(flag)-7 != 36):
print('Wrong length')
sys.exit(2)

raw_flag=raw_flag[::-1]

ciphers=[ [raw_flag[row*6+col] for row in range(6)] for col in range(6)]

for row in range(5):
for col in range(6):
ciphers[row][col]=(ciphers[row+1][col] + ciphers[row][col]) % 256

cipher=b''
for row in range(6):
col=0
while(col<6):
cipher+=bytes([ciphers[row][col]])
col+=1

cipher=b64encode(cipher)
if(cipher == O0o):
print('Great, this is my flag.')
else:
print("Wrong flag.")

感觉这一块最难的就是能从 python 汇编中看出 ciphers 的获取,这里面涉及到了列表推导式,其实现在我也看不太懂这个东西到底是怎么出来的(x

1
2
3
4
5
6
7
8
9
10
11
12
13
ciphers=[ [raw_flag[row*6+col] for row in range(6)] for col in range(6)]
# 列表生成的方式: 从里到外,一维一维生成的,一个[ ]就是一维
# 每一维中元素的个数就是[ ]中 range() 的个数
# 在本题中 ciphers 里的元素和 raw_flag 的元素位置对应关系如下
| 0 1 2 3 4 5
—— —— —— —— —— —— ——
0 | 0 6 12 18 24 30
1 | 1 7 13 19 25 31
2 | 2 8 14 20 26 32
3 | 3 9 15 21 27 33
4 | 4 10 16 22 28 34
5 | 5 11 17 23 29 35
# 就相当于是做了一个转置的操作

看明白这个,那么写 exp 也就没什么问题了

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
import os,sys
from base64 import b64decode

aim=b'QreZsrN+Wp3C0rJ4ccfXpJO8n9jIkIDNrteuxK6pR3JPZX5Q'
aim=b64decode(aim)


ciphers=[[] for i in range(6)]
for row in range(6):
for col in range(6):
ciphers[row].append(aim[row*6+col])

for row in range(4,-1,-1):
for col in range(5,-1,-1):
if(ciphers[row][col]-ciphers[row+1][col] < 0):
ciphers[row][col]=ciphers[row][col]+256-ciphers[row+1][col]
else:
ciphers[row][col]-=ciphers[row+1][col]

ciphers = [ [ ciphers[row][col] for row in range(6) ] for col in range(6)] //转置回正确是顺序


flag=''
for row in range(6):
for col in range(6):
flag+=chr(ciphers[row][col])

flag=flag[::-1]

print(flag)

小结

目前 python 汇编接触到的还没有很难,也更加了解了 python 一些其他知识,例如:接触到了列表推导式,python 汇编中函数的调用形式 …

依旧存在着一些问题:

  • 列表推导式对应的 python 汇编了解依旧不是很清晰

  • 对于 hgame 中的那一道题目,python 汇编语言的第一句是 3 0 JUMP_ABSOLUTE 2

    其实这句话算是一句干扰,它可以导致反编译工具无法使用,如果我可以通过二进制文件编辑器将这句汇编语言所对应的机器码给 nop 掉并且修改掉 pyc 头部中的相关信息,再使用反编译工具去进行工作是不是就可以直接得到 py 文件🤔 感觉之后可以尝试一下

本文作者:GhDemi

本文链接: https://ghdemi.github.io/2022/11/05/Python%E9%80%86%E5%90%91/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。