python类题目在re题目占比快速上升,为了跟上时代,我们不得不系统学习一下python逆向,目前出现的python类题目,按照给出附件种类主要分为四种:pyc文件/给出pyc字节码txt/加花pyc/打包成exe的py文件
近期的CTF比赛中,python类题目在re题目占比快速上升,为了跟上时代,我们不得不系统学习一下python逆向,目前出现的python类题目,按照给出附件种类主要分为四种:pyc文件/给出pyc字节码txt/加花pyc/打包成exe的py文件
0x00 前言
这篇文章开始之前要特别鸣谢ppppz师傅,做出了这么好的一期python逆向入门总结视频。俗话说的好,前人栽树,后人乘凉,让我们站在大佬的肩膀上继续学习总结。
觉得文章比较难以理解的可以去看看大佬的视频,人帅声甜吾辈楷模:
https://www.bilibili.com/video/BV1JL4y1p7Tt?spm_id_from=333.999.0.0
0x01 pyc文件类
前置知识:pyc文件及pyc文件结构
pyc文件是python在编译过程中出现的主要中间过程文件,是一种二进制文件,是由py文件经过编译后,生成的文件,是一种byte code。pyc文件是可以由python虚拟机直接执行的程序。因此分析pyc文件的文件结构对于实现python编译与反编译就显得十分重要。pyc py文件变成pyc文件后,加载的速度有所提高,而且pyc是一种跨 平台的字节码,这个是类似于JAVA或者.NET的虚拟机的概念。pyc的内容,是跟python的版本相关的,不同 版本编译后的pyc文件是不同的,2.5编译的pyc文件,2.4版本的python是无法执行的。
Python代码的编译结果就是PyCodeObject对象。PyCodeObject对象可以由虚拟机加载后直接运行,而pyc文件就是PyCodeObject对象在硬盘上的保存形式。因此我们先分析PyCodeObject对象的结构,随后再涉及pyc文件的二进制结构。
下图展示了pyc文件的完整格式
这里不再赘述,一个简单的实例分析可以看如下这篇大佬文章
https://blog.csdn.net/weixin_35967330/article/details/114390031?spm=1001.2014.3001.5501
pyc类题目处理
uncompyle6直接反编译即可。不过现在裸的pyc越来越少了,一般需要加花
的安装,直接上pip
pip install uncompyle6
uncompyle6反编译实例:
0x02 给出pyc字节码类
前置知识:pyc字节码是什么
Python实际上将源代码编译为一组虚拟机指令,Python的解释器就是该虚拟机的一个具体实现。这种跑在虚拟机内部的中间格式被称为“字节码”。
python字节码个人感觉有一点类似于汇编代码,但是比汇编代码易读,所以大家不必太过担心。
pyc字节码类题目处理
使用库对pyc文件逆向,代码如下
import dis
import marshal
f=open("printname.pyc","rb")
b_data=f.read()
f.close()
PyCodeObjectData=b_data[8:]
Pyobj=marshal.loads(PyCodeObjectData)
dis.dis(Pyobj)
运行以上代码可以将pyc转换为字节码
1.读py字节码
2.根据opcode文件查询意思
pyc类题目让我想起了vm虚拟机题目,都是需要将类汇编语言转化为高级语言~如果感觉pyc字节码类题目难理解的话可以联想理解一下。
例题1:浙江省赛某题目(感谢pz师傅)
0 LOAD_CONST 0 ()
2 LOAD_CONST 1 ('keyinit')
4 MAKE_FUNCTION0
6 STORE_NAME 0 (keyinit)
8 8 LOAD_NAME1 (__name__)
10 LOAD_CONST 2 ('__main__')
12 COMPARE_OP 2 (==)
14 POP_JUMP_IF_FALSE 250
9 16 LOAD_NAME2 (print)
18 LOAD_CONST 3 ('Can you crack pyc?')
20 CALL_FUNCTION1
22 POP_TOP
10 24 LOAD_NAME3 (input)
26 LOAD_CONST 4 ('Plz give me your flag:')
28 CALL_FUNCTION1
30 STORE_NAME 4 (str)
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)
13 100 LOAD_NAME6 (len)
102 LOAD_NAME4 (str)
104 CALL_FUNCTION1
106 LOAD_CONST 36 (32)
108 COMPARE_OP 3 (!=)
110 POP_JUMP_IF_TRUE 140
112 LOAD_NAME4 (str)
114 LOAD_CONST 37 (0)
116 LOAD_CONST 27 (7)
118 BUILD_SLICE 2
120 BINARY_SUBSCR
122 LOAD_CONST 38 ('DASCTF{')
124 COMPARE_OP 3 (!=)
126 POP_JUMP_IF_TRUE 140
128 LOAD_NAME4 (str)
130 LOAD_CONST 39 (31)
132 BINARY_SUBSCR
134 LOAD_CONST 40 ('}')
136 COMPARE_OP 3 (!=)
138 POP_JUMP_IF_FALSE 154
14 >> 140 LOAD_NAME2 (print)
142 LOAD_CONST 41 ('Bye bye~~')
144 CALL_FUNCTION1
146 POP_TOP
15 148 LOAD_NAME7 (exit)
150 CALL_FUNCTION0
152 POP_TOP
16 >> 154 LOAD_NAME8 (list)
156 LOAD_NAME4 (str)
158 CALL_FUNCTION1
160 STORE_NAME 9 (st)
17 162 BUILD_LIST 0
164 STORE_NAME 10 (key)
18 166 LOAD_NAME0 (keyinit)
168 LOAD_NAME 10 (key)
170 CALL_FUNCTION1
172 POP_TOP
19 174 SETUP_LOOP 48 (to 224)
176 LOAD_NAME 11 (range)
178 LOAD_CONST 36 (32)
180 CALL_FUNCTION1
182 GET_ITER
>> 184 FOR_ITER36 (to 222)
186 STORE_NAME 12 (i)
20 188 LOAD_NAME 13 (ord)
190 LOAD_NAME4 (str)
192 LOAD_NAME 12 (i)
194 BINARY_SUBSCR
196 CALL_FUNCTION1
198 LOAD_NAME 10 (key)
200 LOAD_NAME 12 (i)
202 LOAD_NAME6 (len)
204 LOAD_NAME 10 (key)
206 CALL_FUNCTION1
208 BINARY_MODULO
210 BINARY_SUBSCR
212 BINARY_XOR
214 LOAD_NAME9 (st)
216 LOAD_NAME 12 (i)
218 STORE_SUBSCR
220 JUMP_ABSOLUTE 184
>> 222 POP_BLOCK
21 >> 224 LOAD_NAME9 (st)
226 LOAD_NAME5 (text)
228 COMPARE_OP 2 (==)
230 POP_JUMP_IF_FALSE 242
22 232 LOAD_NAME2 (print)
234 LOAD_CONST 42 ('Congratulations and you are good at PYC!')
236 CALL_FUNCTION1
238 POP_TOP
240 JUMP_FORWARD 8 (to 250)
24 >> 242 LOAD_NAME2 (print)
244 LOAD_CONST 43 ('Sorry,plz learn more about pyc.')
246 CALL_FUNCTION1
248 POP_TOP
>> 250 LOAD_CONST 44 (None)
252 RETURN_VALUE
Disassembly of :
2 0 LOAD_CONST 1 (0)
2 STORE_FAST 1 (num)
3 4 SETUP_LOOP 42 (to 48)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 2 (8)
10 CALL_FUNCTION1
12 GET_ITER
>> 14 FOR_ITER30 (to 46)
16 STORE_FAST 2 (i)
4 18 LOAD_FAST1 (num)
20 LOAD_CONST 3 (7508399208111569251)
22 BINARY_SUBTRACT
24 LOAD_CONST 4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST 1 (num)
5 30 LOAD_FAST0 (key)
32 LOAD_METHOD 1 (append)
34 LOAD_FAST1 (num)
36 LOAD_CONST 5 (24)
38 BINARY_RSHIFT
40 CALL_METHOD 1
42 POP_TOP
44 JUMP_ABSOLUTE 14
>> 46 POP_BLOCK
>> 48 LOAD_CONST 0 (None)
50 RETURN_VALUE
获取到字节码之后,其实没什么好说的,和虚拟机逆向有opcode并且有对应汇编表一个道理,纯纯苦力活。 题目逻辑很简单,将给出加密flag 和特定密码表每8位一异或得到flag。下面详细讲解一下获取密钥部分的翻译。
密钥函数字节码
Disassembly of :
2 0 LOAD_CONST 1 (0)
2 STORE_FAST 1 (num)
3 4 SETUP_LOOP 42 (to 48)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 2 (8)
10 CALL_FUNCTION1
12 GET_ITER
>> 14 FOR_ITER30 (to 46)
16 STORE_FAST 2 (i)
4 18 LOAD_FAST1 (num)
20 LOAD_CONST 3 (7508399208111569251)
22 BINARY_SUBTRACT
24 LOAD_CONST 4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST 1 (num)
5 30 LOAD_FAST0 (key)
32 LOAD_METHOD 1 (append)
34 LOAD_FAST1 (num)
36 LOAD_CONST 5 (24)
38 BINARY_RSHIFT
40 CALL_METHOD 1
42 POP_TOP
44 JUMP_ABSOLUTE 14
>> 46 POP_BLOCK
>> 48 LOAD_CONST 0 (None)
50 RETURN_VALUE
首先是一个循环体
4 SETUP_LOOP 42 (to 48)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 2 (8)
10 CALL_FUNCTION1
12 GET_ITER
>> 14 FOR_ITER30 (to 46)
16 STORE_FAST 2 (i)
翻译一下就是
for i in range(8):
接下来将num 以及7508399208111569251入栈
18 LOAD_FAST1 (num)
20 LOAD_CONST 3(7508399208111569251)
22 BINARY_SUBTRACT
查表了解到BINARY_SUBTRACT是指减法操作
接着与4294967295取模
24 LOAD_CONST 4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST 1 (num)
5 30 LOAD_FAST0 (key)
32 LOAD_METHOD 1 (append)
34 LOAD_FAST1 (num)
组合一下就是
num=(num-7508399208111569251)%4294967295
30 LOAD_FAST0 (key)
32 LOAD_METHOD 1 (append)
34 LOAD_FAST1 (num)
36 LOAD_CONST 5 (24)
38 BINARY_RSHIFT
40 CALL_METHOD 1
42 POP_TOP
44 JUMP_ABSOLUTE 14
>> 46 POP_BLOCK
>> 48 LOAD_CONST 0 (None)
50 RETURN_VALUE
BINARY_RSHIFT查表是位移右运算,整体组合下来就是
num=0
for i in range(8):
num=(num-7508399208111569251)%4294967295
print(num>>24)
运行得到密钥
就如这般分析上文代码,得到是一个循环key异或
188 LOAD_NAME 13 (ord)
190 LOAD_NAME4 (str)
192 LOAD_NAME 12 (i)
194 BINARY_SUBSCR
196 CALL_FUNCTION1
198 LOAD_NAME 10 (key)
200 LOAD_NAME 12 (i)
202 LOAD_NAME6 (len)
204 LOAD_NAME 10 (key)
206 CALL_FUNCTION1
208 BINARY_MODULO
210 BINARY_SUBSCR
212 BINARY_XOR
214 LOAD_NAME9 (st)
216 LOAD_NAME 12 (i)
218 STORE_SUBSCR
220 JUMP_ABSOLUTE 184
>> 222 POP_BLOCK
翻译出来
for i in range(32):
flag+=chr(ord(str[i])^key(i%len(key)))
这样组合得到最终脚本。
a=[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]
b=[40,80,121,161,202,242,27,67]
for i in range(32):
print(chr(a[i]^b[i%8]),end="")
flag:DASCTF{0hH_My_9Uy!_vou_D_1T_0^0}
例题2:不知出处
趁热打铁,再做一道。
Disassembly of a:
3 0 LOAD_CONST 1 (0)
2 BUILD_LIST 1
4 LOAD_GLOBAL 0 (len)
6 LOAD_FAST0 (s)
8 CALL_FUNCTION1
10 BINARY_MULTIPLY
12 STORE_FAST 1 (o)
4 14 LOAD_GLOBAL 1 (enumerate)
16 LOAD_FAST0 (s)
18 CALL_FUNCTION1
20 GET_ITER
>> 22 FOR_ITER24 (to 48)
24 UNPACK_SEQUENCE 2
26 STORE_FAST 2 (i)
28 STORE_FAST 3 (c)
5 30 LOAD_FAST3 (c)
32 LOAD_CONST 2 (2)
34 BINARY_MULTIPLY
36 LOAD_CONST 3 (60)
38 BINARY_SUBTRACT
40 LOAD_FAST1 (o)
42 LOAD_FAST2 (i)
44 STORE_SUBSCR
46 JUMP_ABSOLUTE 22
6 >> 48 LOAD_FAST1 (o)
50 RETURN_VALUE
Disassembly of b:
9 0 LOAD_GLOBAL 0 (zip)
2 LOAD_FAST0 (s)
4 LOAD_FAST1 (t)
6 CALL_FUNCTION2
8 GET_ITER
>> 10 FOR_ITER22 (to 34)
12 UNPACK_SEQUENCE 2
14 STORE_FAST 2 (x)
16 STORE_FAST 3 (y)
10 18 LOAD_FAST2 (x)
20 LOAD_FAST3 (y)
22 BINARY_ADD
24 LOAD_CONST 1 (50)
26 BINARY_SUBTRACT
28 YIELD_VALUE
30 POP_TOP
32 JUMP_ABSOLUTE 10
>> 34 LOAD_CONST 0 (None)
36 RETURN_VALUE
Disassembly of c:
13 0 LOAD_CONST 1 ( at 0x7ff31a16f0e0, file "vuln.py", line 13>)
2 LOAD_CONST 2 ('c.<locals>.<listcomp>')
4 MAKE_FUNCTION0
6 LOAD_FAST0 (s)
8 GET_ITER
10 CALL_FUNCTION1
12 RETURN_VALUE
Disassembly of at 0x7ff31a16f0e0, file "vuln.py", line 13>:
13 0 BUILD_LIST 0
2 LOAD_FAST0 (.0)
>>4 FOR_ITER12 (to 18)
6 STORE_FAST 1 (c)
8 LOAD_FAST1 (c)
10 LOAD_CONST 0 (5)
12 BINARY_ADD
14 LIST_APPEND 2
16 JUMP_ABSOLUTE4
>> 18 RETURN_VALUE
Disassembly of e:
16 0 LOAD_CONST 1 ( at 0x7ff31a16f240, file "vuln.py", line 16>)
2 LOAD_CONST 2 ('e.<locals>.<listcomp>')
4 MAKE_FUNCTION0
6 LOAD_FAST0 (s)
8 GET_ITER
10 CALL_FUNCTION1
12 STORE_FAST 0 (s)
17 14 LOAD_CONST 3 ( at 0x7ff31a16f2f0, file "vuln.py", line 17>)
16 LOAD_CONST 2 ('e.<locals>.<listcomp>')
18 MAKE_FUNCTION0
20 LOAD_GLOBAL 0 (b)
22 LOAD_GLOBAL 1 (a)
24 LOAD_FAST0 (s)
26 CALL_FUNCTION1
28 LOAD_GLOBAL 2 (c)
30 LOAD_FAST0 (s)
32 CALL_FUNCTION1
34 CALL_FUNCTION2
36 GET_ITER
38 CALL_FUNCTION1
40 STORE_FAST 1 (o)
18 42 LOAD_GLOBAL 3 (bytes)
44 LOAD_FAST1 (o)
46 CALL_FUNCTION1
48 RETURN_VALUE
Disassembly of at 0x7ff31a16f240, file "vuln.py", line 16>:
16 0 BUILD_LIST 0
2 LOAD_FAST0 (.0)
>>4 FOR_ITER12 (to 18)
6 STORE_FAST 1 (c)
8 LOAD_GLOBAL 0 (ord)
10 LOAD_FAST1 (c)
12 CALL_FUNCTION1
14 LIST_APPEND 2
16 JUMP_ABSOLUTE4
>> 18 RETURN_VALUE
Disassembly of at 0x7ff31a16f2f0, file "vuln.py", line 17>:
17 0 BUILD_LIST 0
2 LOAD_FAST0 (.0)
>>4 FOR_ITER16 (to 22)
6 STORE_FAST 1 (c)
8 LOAD_FAST1 (c)
10 LOAD_CONST 0 (5)
12 BINARY_XOR
14 LOAD_CONST 1 (30)
16 BINARY_SUBTRACT
18 LIST_APPEND 2
20 JUMP_ABSOLUTE4
>> 22 RETURN_VALUE
Disassembly of main:
21 0 LOAD_GLOBAL 0 (input)
2 LOAD_CONST 1 ('Guess?')
4 CALL_FUNCTION1
6 STORE_FAST 0 (s)
22 8 LOAD_CONST 2 (b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed')
10 STORE_FAST 1 (o)
23 12 LOAD_GLOBAL 1 (e)
14 LOAD_FAST0 (s)
16 CALL_FUNCTION1
18 LOAD_FAST1 (o)
20 COMPARE_OP 2 (==)
22 POP_JUMP_IF_FALSE 34
24 24 LOAD_GLOBAL 2 (print)
26 LOAD_CONST 3 ('Correct!')
28 CALL_FUNCTION1
30 POP_TOP
32 JUMP_FORWARD 8 (to 42)
26 >> 34 LOAD_GLOBAL 2 (print)
36 LOAD_CONST 4 ('Wrong...')
38 CALL_FUNCTION1
40 POP_TOP
>> 42 LOAD_CONST 0 (None)
44 RETURN_VALUE
本题的结构为main函数+多函数组合。这样的话,我们采取先看main函数,再逐个分析函数的策略。
main函数,可说的就if函数这一部分,最后一行的 POP_JUMP_IF_FALSE 表明这是个if判断语句
12 LOAD_GLOBAL 1 (e)
14 LOAD_FAST0 (s)
16 CALL_FUNCTION1
18 LOAD_FAST1 (o)
20 COMPARE_OP 2 (==)
22 POP_JUMP_IF_FALSE 34
翻译一下就是
if(e[s]==o):
这样我们就可以毫不费力地翻译出main函数
s=input("Guess!")
o=b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed'
if e(s)==o:
print('Correct!')
else:
print('Wrong...')
其他abcde几个函数 的翻译难点,主要在于 对字节码 函数来回之间调用涉及的语句不明白。我们来看一下最复杂的e函数
20 LOAD_GLOBAL 0 (b)
#22~26行 调用了a(s),28~32 调用了b(s)
22 LOAD_GLOBAL 1 (a)
24 LOAD_FAST0 (s)
26 CALL_FUNCTION1
28 LOAD_GLOBAL 2 (c)
30 LOAD_FAST0 (s)
32 CALL_FUNCTION1
34 CALL_FUNCTION2
#第20行+34行 call function后面的参数为2 包裹了整体调用 a(s),c(s)成了 a(),c()中的s
这几行的翻译非常有意思,看我的注释:
翻译出来很简单
b(a(s),c(s))
接着倒上去看函数e的第16行,首先我们发现结果存在s中,然后将一个函数带着参数s返回。
4 FOR_ITER12 (to 18)
6 STORE_FAST 1 (c)
8 LOAD_GLOBAL 0 (ord)
10 LOAD_FAST1 (c)
12 CALL_FUNCTION1
14 LIST_APPEND 2
16 JUMP_ABSOLUTE4
>> 18 RETURN_VALUE
结合看函数,发现这是做一个循环,将每一个元素c转换为ord(c)返回。所以我们猜测这里应该是s=[ord(c) for c in s]。
4 FOR_ITER16 (to 22)
6 STORE_FAST 1 (c)
8 LOAD_FAST1 (c)
10 LOAD_CONST 0 (5)
12 BINARY_XOR
14 LOAD_CONST 1 (30)
16 BINARY_SUBTRACT
18 LIST_APPEND 2
20 JUMP_ABSOLUTE4
>> 22 RETURN_VALUE
第17行类似于上一行,不同的是函数里的参数,首先第一个函数b有两个参数,每个参数又在调用一个有一个参数s的函数a,c。所以这里应该是
o=[(c^5)-30 for c in b(a(s),c(s))]。
按照这样的逻辑再去翻译其他函数即可
完整代码
def a(s):
o=[0]*len(s)
for i,c in enumerate(s):
o[i]=c*2-60
return o
def b(s,t):
for (x,y) in zip(s,t):
yield x+y-50
def c(s):
return [(c+5) for c in s]
def e(s):
s=[ord(c) for c in s]
o=[(c^5)-30 for c in b(a(s),c(s))]
return bytes(o)
s=input("Guess!")
o=b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed'
if e(s)==o:
print('Correct!')
else:
print('Wrong...')
逆向解题:
o=b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed'
ll=[]
for i in o:
ll.append((((int(i.encode("hex"),16)+30)^5)+50+55)//3)
m=""
for ii in ll:
m=m+chr(ii)
print(m)
例题3:[羊城杯 2020]Bytecode
没什么要多说的,这种题就是硬翻译
注意这种load+store形式的翻译
125 LOAD_NAME3 (flag)
128 STORE_NAME 4 (str)
翻译:str=flag
得到源码
en = [3, 37, 72, 9, 6, 132]
output = [101, 96, 23, 68, 112, 42, 107, 62, 96, 53, 176, 179,
98, 53, 67, 29, 41, 120, 60, 106, 51, 101, 178, 189, 101, 48]
print('welcome to GWHT2020')
flag = input('please input your flag:')
str = flag
a = len(str)
if a < 38:
print('lenth wrong!')
exit(0)
if ord(str[0]) + 2020 * ord(str[1]) + 2020 * ord(str[3]) + 2020 * ord(str[4]) == 1182843538814603:
print('good!continue\xe2\x80\xa6\xe2\x80\xa6')
else:
print('bye~')
exit(0)
x = []
k = 5
for i in range(13):
b = ord(str[k])
c = ord(str[k + 1])
a11 = c ^ en[i % 6]
a22 = b ^ en[i % 6]
x.append(a11)
x.append(a22)
k += 2
if x == output:
print('good!continue\xe2\x80\xa6\xe2\x80\xa6')
else:
print('oh,you are wrong!')
exit(0)
l = len(str)
a1 = ord(str[l - 7])
a2 = ord(str[l - 6])
a3 = ord(str[l - 5])
a4 = ord(str[l - 4])
a5 = ord(str[l - 3])
a6 = ord(str[l - 2])
if a1 * 3 + a2 * 2 + a3 * 5 == 1003:
if a1 * 4 + a2 * 7 + a3 * 9 == 2013:
if a1 + a2 * 8 + a3 * 2 == 1109:
if a1 * 3 + a5 * 2 + a6 * 5 == 671:
if a4 * 4 + a5 * 7 + a6 * 9 == 1252:
if a4 + a5 * 8 + a6 * 2 == 644:
print('congraduation!you get the right flag!')
构造相应exp:
# EXP
from z3 import *
en = [3, 37, 72, 9, 6, 132]
output = [101, 96, 23, 68, 112, 42, 107, 62, 96, 53, 176, 179, 98, 53, 67, 29, 41, 120, 60, 106, 51, 101, 178, 189, 101,
48]
flag = ''
k = 0
x = []
for i in range(13):
c = chr(output[k] ^ en[i % 6])
b = chr(output[k + 1] ^ en[i % 6])
x.append(b)
x.append(c)
k += 2
flag = ''.join(x)
# print(flag)
a1 = Int('a1')
a2 = Int('a2')
a3 = Int('a3')
a4 = Int('a4')
a5 = Int('a5')
a6 = Int('a6')
s = Solver()
s.add(a1 * 3 + a2 * 2 + a3 * 5 == 1003)
s.add(a1 * 4 + a2 * 7 + a3 * 9 == 2013)
s.add(a1 + a2 * 8 + a3 * 2 == 1109)
s.add(a4 * 3 + a5 * 2 + a6 * 5 == 671)
s.add(a4 * 4 + a5 * 7 + a6 * 9 == 1252)
s.add(a4 + a5 * 8 + a6 * 2 == 644)
if s.check() == sat:
result = s.model()
print(result)
s = [97, 101, 102, 102, 55, 51]
for i in range(6):
flag += chr(s[i])
print(flag)
0x03 加花的pyc类
根据uncompyle6和字节码判断花
可以看到在第七行停住了,说明此处存在阻碍反编译的花指令
读取co_code长度
何谓co_code?
co_code :字节码指令序列,字节码都由操作码 opcode 和参数 opatg 组成的序列。记录着指令数量,指令的增加和减少都会影响该值。
获取该值的方式很简单,键入len(code.co_code)即可获取
去花并修改co_code 长度
接下来我们就要尝试去花了,使用010editor打开pyc文件,然鹅我们面临一个新的问题,怎么在二进制格式中定位花指令的位置呢
首先找到python2 文件目录下的opcode.h文件,其中存储着所有opcode以及其对应的值。
记得转换16进制113 转换16进制为71,100转换为64.
在010editor中搜索71和64,发现只存在一个71,且我们知道python2 中一条指令占3位,就能确定该六位是我们需要的花指令,选中后直接按退格键删掉。
然而光删除花指令并不能完成去花,我们保存文件后再次获取co_code的值 仍为27 。说明我们的去花工作还没结束,接下来还需要修改co_code的值为21。
仍然是在010editor中搜索27的十六进制(1B),找到后修改为hex(21)=15 之后保存文件,这样去花工作就完成了。
保存 uncompyle6反编译
保存好去花的文件就可以愉快的进行反编译了
0x04 打包成exe的py文件类
例题:[SangFor2020]login
通过脚本变成结构体+一个文件
把时间属性和版本的魔术字放回去保存
具体到这个题将struct.pyc中的前12位复制粘贴到login.pyc中
uncompyle6即可
不同版本的修复方法:
在Python3.7及以上版本的编译后二进制文件中,头部除了四字节Magic Number,还有四个字节的空位和八个字节的时间戳+大小信息,后者对文件反编译没有影响,全部填充0即可;
Python3.3 - Python3.7(包含3.3)版本中,只需要Magic Number和八位时间戳+大小信息
Python3.3 以下的版本中,只有Magic Number和四位时间戳
用Winhex修复文件,在头部写入(非覆盖)上述格式的内容,就可以进行反编译了
继续回到解题:
看这个源码很明显的z3约束
from z3 import *
def main():
s=Solver()
a1=Int('a1')
a2=Int('a2')
a3=Int('a3')
a4=Int('a4')
a5=Int('a5')
a6=Int('a6')
a7=Int('a7')
a8=Int('a8')
a9=Int('a9')
a10=Int('a10')
a11=Int('a11')
a12=Int('a12')
a13=Int('a13')
a14=Int('a14')
s.add(a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5 + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36 + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60 + a14 * 29 == 22748)
s.add(a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25 + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66 + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39 + a14 * 17 == 7258)
s.add(a1 * 28 + a2 * 35 + a3 * 16 - a4 * 65 + a5 * 53 + a6 * 39 + a7 * 27 + a8 * 15 - a9 * 33 + a10 * 13 + a11 * 101 + a12 * 90 - a13 * 34 + a14 * 23 == 26190)
s.add(a1 * 23 + a2 * 34 + a3 * 35 - a4 * 59 + a5 * 49 + a6 * 81 + a7 * 25 + a8*128 - a9 * 32 + a10 * 75 + a11 * 81 + a12 * 47 - a13 * 60 + a14 * 29 == 37136)
s.add(a1 * 38 + a2 * 97 + a3 * 35 - a4 * 52 + a5 * 42 + a6 * 79 + a7 * 90 + a8 * 23 - a9 * 36 + a10 * 57 + a11 * 81 + a12 * 42 - a13 * 62 - a14 * 11 == 27915)
s.add(a1 * 22 + a2 * 27 + a3 * 35 - a4 * 45 + a5 * 47 + a6 * 49 + a7 * 29 + a8 * 18 - a9 * 26 + a10 * 35 + a11 * 41 + a12 * 40 - a13 * 61 + a14 * 28 == 17298)
s.add(a1 * 12 + a2 * 45 + a3 * 35 - a4 * 9 - a5 * 42 + a6 * 86 + a7 * 23 + a8 * 85 - a9 * 47 + a10 * 34 + a11 * 76 + a12 * 43 - a13 * 44 + a14 * 65 == 19875)
s.add(a1 * 79 + a2 * 62 + a3 * 35 - a4 * 85 + a5 * 33 + a6 * 79 + a7 * 86 + a8 * 14 - a9 * 30 + a10 * 25 + a11 * 11 + a12 * 57 - a13 * 50 - a14 * 9 == 22784)
s.add(a1 * 8 + a2 * 6 + a3 * 64 - a4 * 85 + a5 * 73 + a6 * 29 + a7 * 2 + a8 * 23 - a9 * 36 + a10 * 5 + a11 * 2 + a12 * 47 - a13 * 64 + a14 * 27 == 9710)
s.add(a1 * 67 - a2 * 68 + a3 * 68 - a4 * 51 - a5 * 43 + a6 * 81 + a7 * 22 - a8 * 12 - a9 * 38 + a10 * 75 + a11 * 41 + a12 * 27 - a13 * 52 + a14 * 31 == 13376)
s.add(a1 * 85 + a2 * 63 + a3 * 5 - a4 * 51 + a5 * 44 + a6 * 36 + a7 * 28 + a8 * 15 - a9 * 6 + a10 * 45 + a11 * 31 + a12 * 7 - a13 * 67 + a14 * 78 == 24065)
s.add(a1 * 47 + a2 * 64 + a3 * 66 - a4 * 5 + a5 * 43 + a6 * 112 + a7 * 25 + a8 * 13 - a9 * 35 + a10 * 95 + a11 * 21 + a12 * 43 - a13 * 61 + a14 * 20 == 27687)
s.add(a1 * 89 + a2 * 67 + a3 * 85 - a4 * 25 + a5 * 49 + a6 * 89 + a7 * 23 + a8 * 56 - a9 * 92 + a10 * 14 + a11 * 89 + a12 * 47 - a13 * 61 - a14 * 29 == 29250)
s.add(a1 * 95 + a2 * 34 + a3 * 62 - a4 * 9 - a5 * 43 + a6 * 83 + a7 * 25 + a8 * 12 - a9 * 36 + a10 * 16 + a11 * 51 + a12 * 47 - a13 * 60 - a14 * 24 == 15317)
if s.check()==sat:
print(s.model())
先利用脚本求出code数组
注意顺序不是一 一对应的,code数组赋值正确后倒序异或求解即可即可。
code=[None]*14
code[0]=10
code[1]=24
code[2]=119
code[3]=7
code[4]=104
code[5]=43
code[6]=28
code[7]=91
code[8]=108
code[9]=52
code[10]=88
code[11]=74
code[12]=88
code[13]=33
inputs=[None]*14
inputs[13]=code[13]
flag=""
for i in range(12,-1,-1):
inputs[i]=(code[i]^inputs[i+1])
for i in range(14):
flag+=chr(inputs[i])
print(flag)
if __name__=='__main__':
main()
0x05 后记
花了两天将ctf中目前常见的py逆向题型梳理了一遍,感觉收获还是蛮大的。未来在比赛中遇到python类题目,就不会像之前那样如此慌张,可以按照类型准确定位,条理分析。怒拿flag。
0x06 参考文章
https://blog.csdn.net/Zheng__Huang/article/details/112380221
https://blog.csdn.net/qq_27825451/article/details/80283737
https://www.cnblogs.com/serendipity-my/p/13735229.html
https://www.bilibili.com/video/av849399494/
https://blog.csdn.net/m0_37157335/article/details/124121928
https://developer.51cto.com/article/664357.html
- 本文作者: 绿冰壶
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1618
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!