昨天帮别人看了道逆向题,涉及到pyc逆向,由于是python3.12,pycdc无法全部反编译,只能根据python字节码来手撕源码。PS:之前没搞过python字节码逆向,今天狠狠的恶补了下
1、利用pyinstxtractor.py反编译出pyc文件:python3 pyinstxtractor.py pvm.exe
2、利用pycdc可以看到,部分源码是无法反编译成功的
3、利用pycdas查看字节码(这里只列出关键部分)
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + input
12 LOAD_CONST 1: 'flag:'
14 CALL 1
22 STORE_FAST 0: flag
24 LOAD_CONST 2: 0
26 LOAD_CONST 3: ('exit',)
28 IMPORT_NAME 1: sys
30 IMPORT_FROM 2: exit
32 STORE_FAST 1: exit
34 POP_TOP
36 LOAD_GLOBAL 7: NULL + len
46 LOAD_FAST 0: flag
48 CALL 1
56 LOAD_CONST 4: 42 //判断flag是否为42位
58 COMPARE_OP 55 (!=)
62 POP_JUMP_IF_FALSE 13 (to 90)
64 LOAD_CONST 2: 0
66 LOAD_CONST 3: ('exit',)
68 IMPORT_NAME 1: sys
70 IMPORT_FROM 2: exit
72 STORE_FAST 1: exit
74 POP_TOP
76 PUSH_NULL
78 LOAD_FAST 1: exit
80 CALL 0
88 POP_TOP
90 LOAD_FAST 0: flag
92 LOAD_CONST 0: None
94 LOAD_CONST 5: 5
96 BINARY_SLICE
98 LOAD_CONST 6: 'flag{' //输入是否以'flag{'开头
100 COMPARE_OP 55 (!=)
104 POP_JUMP_IF_FALSE 8 (to 122)
106 PUSH_NULL
108 LOAD_FAST 1: exit
110 LOAD_CONST 2: 0
112 CALL 1
120 POP_TOP
122 LOAD_FAST 0: flag
124 LOAD_CONST 7: 41
126 BINARY_SUBSCR
130 LOAD_CONST 8: '}' //输入是否以'{'结尾
132 COMPARE_OP 55 (!=)
136 POP_JUMP_IF_FALSE 8 (to 154)
138 PUSH_NULL
140 LOAD_FAST 1: exit
142 LOAD_CONST 2: 0
144 CALL 1
152 POP_TOP
154 LOAD_FAST 0: flag
156 LOAD_CONST 5: 5
158 LOAD_CONST 7: 41
160 BINARY_SLICE
162 STORE_FAST 2: s //将输入的第5至40位赋给s
164 NOP
166 LOAD_GLOBAL 9: NULL + map
176 LOAD_CONST 9: <CODE> <lambda>//没看懂,但大概意思就是map+lambda,即s=s.split('-')
178 MAKE_FUNCTION 0
180 LOAD_FAST 2: s
182 LOAD_ATTR 11: split
202 LOAD_CONST 10: '-'
204 CALL 1
212 CALL 2
220 GET_ITER //220-240类似于一个for循环,将s.split('-')的值赋给一个列表
222 LOAD_FAST_AND_CLEAR 3: i
224 SWAP 2
226 BUILD_LIST 0
228 SWAP 2
230 FOR_ITER 4 (to 240)
234 STORE_FAST 3: i
236 LOAD_FAST 3: i
238 LIST_APPEND 2
240 JUMP_BACKWARD 6 (to 230)
242 END_FOR
244 STORE_FAST 0: flag //flag=s.split('-')
246 STORE_FAST 3: i
248 LOAD_CONST 2: 0
250 LOAD_CONST 2: 0
252 LOAD_CONST 2: 0
254 LOAD_CONST 2: 0
256 LOAD_CONST 2: 0
258 LOAD_CONST 2: 0
260 LOAD_CONST 2: 0
262 LOAD_CONST 2: 0
264 LOAD_CONST 2: 0
266 LOAD_CONST 2: 0
268 LOAD_CONST 11: ('eax', 'ebx', 'ecx', 'edx', 'eip', 'e1', 'e2', 'e3', 'e4', 'e5')
270 BUILD_CONST_KEY_MAP 10
272 STORE_FAST 4: reg // reg={'eax':0,'ebx':0,...}
274 LOAD_GLOBAL 13: NULL + cstack
284 CALL 0
292 STORE_FAST 5: stack
294 LOAD_FAST 4: reg
296 LOAD_FAST 5: stack
298 BUILD_LIST 2
300 STORE_FAST 6: cpu //[reg,stack]
302 LOAD_GLOBAL 15: NULL + init
312 LOAD_FAST 6: cpu
314 LOAD_FAST 0: flag
316 CALL 2 //调用init(cpu,flag), init的返回值可以看下面的源码,这里就不多分析
324 POP_TOP
326 BUILD_LIST 0
328 LOAD_CONST 12: (145, 146, 2, 64, 146, 32768, 32, 146, 792896, 16, 146, 23751, 32, 146, 400000, 64, 146, 297, 32, 146, 200000, 64, 225, 145, 146, 324, 48, 146, 1368, 32, 146, 40000, 64, 226, 149, 145, 48, 32, 80, 146, 14, 64, 146, 197, 32, 146, 1000, 64, 227, 145, 146, 521197, 48, 146, 88123111, 80, 146, 554259, 32, 146, 600000, 64, 146, 99, 32, 146, 100, 64, 228, 145, 146, 45499, 32, 80, 146, 19499, 32, 146, 675, 64, 146, 5519, 32, 146, 10000, 64, 229, 131)
330 LIST_EXTEND 1
332 STORE_FAST 7: all_code //all_code=[145,146,2,...]
334 LOAD_GLOBAL 17: NULL + run
344 LOAD_FAST 7: all_code
346 LOAD_FAST 6: cpu
348 CALL 2 //调用run(cpu,all_code)
356 POP_TOP
358 LOAD_FAST 6: cpu
360 LOAD_CONST 2: 0
362 BINARY_SUBSCR
366 LOAD_CONST 13: 'e1'
368 BINARY_SUBSCR
372 LOAD_CONST 14: 1327
374 COMPARE_OP 40 (==) //判断cpu[0]['e1']是否为1327,下面的判断类似
378 POP_JUMP_IF_FALSE 56 (to 492)
380 LOAD_FAST 6: cpu
382 LOAD_CONST 2: 0
384 BINARY_SUBSCR
388 LOAD_CONST 15: 'e2'
390 BINARY_SUBSCR
394 LOAD_CONST 16: 387
396 COMPARE_OP 40 (==)
400 POP_JUMP_IF_FALSE 45 (to 492)
402 LOAD_FAST 6: cpu
404 LOAD_CONST 2: 0
406 BINARY_SUBSCR
410 LOAD_CONST 17: 'e3'
412 BINARY_SUBSCR
416 LOAD_CONST 18: 521
418 COMPARE_OP 40 (==)
422 POP_JUMP_IF_FALSE 34 (to 492)
424 LOAD_FAST 6: cpu
426 LOAD_CONST 2: 0
428 BINARY_SUBSCR
432 LOAD_CONST 19: 'e4'
434 BINARY_SUBSCR
438 LOAD_CONST 20: 454
440 COMPARE_OP 40 (==)
444 POP_JUMP_IF_FALSE 23 (to 492)
446 LOAD_FAST 6: cpu
448 LOAD_CONST 2: 0
450 BINARY_SUBSCR
454 LOAD_CONST 21: 'e5'
456 BINARY_SUBSCR
460 LOAD_CONST 22: 111
462 COMPARE_OP 40 (==)
466 POP_JUMP_IF_FALSE 12 (to 492)
468 LOAD_GLOBAL 19: NULL + print
478 LOAD_CONST 23: 'correct'
480 CALL 1
488 POP_TOP
490 RETURN_CONST 0: None
492 LOAD_GLOBAL 19: NULL + print
502 LOAD_CONST 24: 'errrrror'
504 CALL 1
512 POP_TOP
514 RETURN_CONST 0: None
516 SWAP 2
518 POP_TOP
520 SWAP 2
522 STORE_FAST 3: i
524 RERAISE 0
526 PUSH_EXC_INFO
528 POP_TOP
530 PUSH_NULL
532 LOAD_FAST 1: exit
534 LOAD_CONST 2: 0
536 CALL 1
544 POP_TOP
546 POP_EXCEPT
548 JUMP_BACKWARD 151 (to 248)
550 COPY 3
552 POP_EXCEPT
554 RERAISE 1
4、结合pycdc反编译的部分源码和观察字节码后手撕的源码如下
class cstack:
def __init__(self):
self.stack = []
self.length = 0
def push(self, value):
self.stack.append(value)
self.length = self.length + 1
def pop(self):
if not self.stack:
return 0
self.length = self.length - 1
cur_value = self.stack.pop()
return cur_value
def parse_code(cpu, code):
cur_step = 0
if code[0] == 16:
cpu[0]['eax'] = cpu[0]['eax'] + cpu[0]['ebx']
elif code[0] == 32:
cpu[0]['eax'] = cpu[0]['eax'] - cpu[0]['ebx']
elif code[0] == 48:
cpu[0]['eax'] = cpu[0]['eax'] * cpu[0]['ebx']
elif code[0] == 64:
cpu[0]['eax'] = cpu[0]['eax'] // cpu[0]['ebx']
elif code[0] == 80:
cpu[0]['eax'] = cpu[0]['eax'] ^ cpu[0]['ebx']
elif code[0] == 96:
cpu[0]['eax'] = cpu[0]['eax'] & cpu[0]['ebx']
elif code[0] == 112:
cpu[0]['eax'] = cpu[0]['eax'] | cpu[0]['ebx']
elif code[0] == 128:
if cpu[0]['eax'] == cpu[0]['ebx']:
cpu[0]['ecx'] = 1
else:
cpu[0]['ecx'] = 0
elif code[0] == 129:
cur_step += 1
if cpu[0]['ecx'] == 1:
cur_step = code[1]
elif code[0] == 130:
cur_step += 1
cur_step = code[1]
elif code[0] == 131:
cur_step = 9999998
elif code[0] == 144:
cpu[0]['eax'] = code[1]
cur_step = 1
elif code[0] == 145:
cpu[0]['eax'] = cpu[1].pop()
elif code[0] == 146:
cpu[0]['ebx'] = code[1]
cur_step = 1
elif code[0] == 147:
cpu[0]['ebx'] = cpu[1].pop()
elif code[0] == 148:
cpu[0]['eax'] = cpu[0]['ebx']
elif code[0] == 149:
cpu[0]['ebx'] = cpu[0]['eax']
elif code[0] == 150:
cpu[0]['ecx'] = code[1]
cur_step = 1
elif code[0] == 151:
cpu[1].push(cpu[0]['eax'])
elif code[0] == 225:
cpu[0]['e1'] = cpu[0]['eax']
elif code[0] == 226:
cpu[0]['e2'] = cpu[0]['eax']
elif code[0] == 227:
cpu[0]['e3'] = cpu[0]['eax']
elif code[0] == 228:
cpu[0]['e4'] = cpu[0]['eax']
elif code[0] == 229:
cpu[0]['e5'] = cpu[0]['eax']
cur_step += 1
return cur_step
def run(all_code, cpu):
cur_step=0
res=0
while 1:
res=parse_code(cpu,all_code[cur_step::])
cur_step=cur_step+res
if res==9999999:
break
#return cpu
def init(cpu, flag):
for i in flag:
cpu[1].push(i)
cpu[0]['eax']=0
cpu[0]['ebx']=0
cpu[0]['ecx']=0
cpu[0]['edx']=0
cpu[0]['eip']=0
cpu[0]['e1']=0
cpu[0]['e2']=0
cpu[0]['e3']=0
cpu[0]['e4']=0
cpu[0]['e5']=0
#return cpu
encry=['1327','387','521','454','111']
stack=cstack()
cpu=[{},stack]
flag=[1,2,3,4] #测试的flag
init(cpu,flag)
all_code=[145, 146, 2, 64, 146, 32768, 32, 146, 792896, 16, 146, 23751, 32, 146, 400000, 64, 146, 297, 32, 146, 200000, 64, 225, 145, 146, 324, 48, 146, 1368, 32, 146, 40000, 64, 226, 149, 145, 48, 32, 80, 146, 14, 64, 146, 197, 32, 146, 1000, 64, 227, 145, 146, 521197, 48, 146, 88123111, 80, 146, 554259, 32, 146, 600000, 64, 146, 99, 32, 146, 100, 64, 228, 145, 146, 45499, 32, 80, 146, 19499, 32, 146, 675, 64, 146, 5519, 32, 146, 10000, 64, 229, 131]
run(all_code,cpu)
5、可以通过调试判断出程序的执行顺序
all_code可以分为5部分,分别为
第一部分:145, 146, 2, 64, 146, 32768, 32, 146, 792896, 16, 146, 23751, 32, 146, 400000, 64, 146, 297, 32, 146, 200000, 64, 225 第二部分:145, 146, 324, 48, 146, 1368, 32, 146, 40000, 64, 226 第三部分:149, 145, 48, 32, 80, 146, 14, 64, 146, 197, 32, 146, 1000, 64, 227 第四部分:145, 146, 521197, 48, 146, 88123111, 80, 146, 554259, 32, 146, 600000, 64, 146, 99, 32, 146, 100, 64, 228 第五部分:145, 146, 45499, 32, 80, 146, 19499, 32, 146, 675, 64, 146, 5519, 32, 146, 10000, 64, 229, 131]
6、这里就解释下第一部分
145:cpu[0]['eax']=cpu[1].pop(),即将flag[-1]赋给cpu[0]['eax'],栈是先进后出的
146, 2 :cpu[0]['ebx']=2
64:cpu[0]['eax']=cpu[0]['eax']//cpu[0]['ebx']
146, 32768:cpu[0]['ebx']=32768
32:cpu[0]['eax']=cpu[0]['eax']-cpu[0]['ebx']
146, 792896 :cpu[0]['ebx']=792896
16:cpu[0]['eax']=cpu[0]['eax']+cpu[0]['ebx']
后面的过程类似(打字太累了)
到225时将cpu[0]['eax']赋值给cpu[0]['e1']
所以第一部分的结果就是cpu[0]['e1']=(((flag[-1]//2-32768+792896-23751)//400000)-297)//200000
要满足cpu[0]['e1']=1327,只需将上述只要解个方程即可,可以求出flag[-1]=212320236127246
7、将其他部分按上述过程可以得到flag
flag=[753020270,52484,18856,47782,212320236127246]
将其转变成16进制后得flag{2ce22d6e-cd04-49a8-baa6-c11aa840c40e}
题目可以在这里下载:https://cloud.189.cn/t/rEF7VnquMfya (访问码:mzt6)