东湖之滨 发表于 2024-5-17 18:47:48

CTF中常见的四种python逆向

说在前面:
什么是pyc文件?
pyc是一种二进制文件,是由py文件经过编译后,生成的文件,是一种byte code,py文件酿成pyc文件后,加载的速度有所进步,pyc 文件是 Python 编译过的字节码文件。它是 Python 程序在运行过程中由源代码(通常是 .py 文件)自动或手动编译产生的二进制文件。
而且pyc是一种跨平台的字节码,是由的虚拟机来执行的,这个是类似于或者.NET的虚拟机的概念。pyc的内容,是跟python的版本相关的,不同版本编译后的pyc文件是不同的,2.5编译的pyc文件,2.4版本的python是无法执行的。
为什么需要pyc文件?
因为py文件是可以直接看到源码的,假如你是开发商业软件的话,不可能把源码也泄漏出去吧?所以就需要编译为pyc后,再发布出去。当然,pyc文件也是可以反编译的,不同版本编译后的pyc文件是不同的,根据python源码中提供的opcode,可以根据pyc文件反编译出py文件源码,网上可以找到一个反编译python2.3版本的pyc文件的工具,不过该工具从python2.4开始就要收费了,假如需要反编译出新版本的pyc文件的话,就需要自己动手了,不过你可以自己修改python的源代码中的opcode文件,重新编译python,从而防止不法分子的破解。
pyc文件

解法:uncompyle6直接反编译
eg.
def check():
    flag=1+1
    if(flag==2):
      return "right"
    return "error"
print(check())这是我们所写的一个简单的python例子
现在我们来生成pyc文件 这里用的是python3
pyhton -m test.pyhttps://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550766.png
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550755.png
pyc文件也是可以运行的
我们在对应的文件夹的搜索框下输入powershell
然后输入
python .\test.pychttps://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550980.png
可以看到即使是运行py文件也是可以运行的
但区别的是我们没法看到pyc文件里面是什么东西,即使拖进IDA里面也无济于事
所以这里我们需要下载一个工具
uncompyle6.exe
在终端打开并输入
pip install uncompyle安装后包罗uncompyle6 但是版本为3.8.0 会导致一些软件的反编译失败发起利用下面命令回到3.7.4版本
pip install uncompyle6==3.7.4安装以后,我们回到我们的tmp目录并打开powershell输入
uncompyle6.exe .\test.pyhttps://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550109.png
这里我们就得到了源码
接下来的操纵就跟windows逆向别无二致了
【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径头脑导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技能电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战本领手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
txt里面是pyc字节码

解法:

[*]读py字节码
[*]根据opcode文件查询意思
我们先来得到我们test.py例子的字节码
在powershell下先输入python 然后输入
import dis,marshal
f=open("test.pyc", "rb").read()
fdis和marshal库 一个是装载库 一个是反编译字节码的库
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550579.png
我们可以将我们的test.pyc导入010Editor中共同着一起看
python2的前八个字节是python2的魔术字
python3的前十六个字节是python3的魔术字
所以我们可以不用读前十六位 我们只需要读后十六位的东西
code=marshal.loads(f)
code现在我们读进来的是二进制数据,我们可以用dis来进行反编译 就会得到python的字节码 可以明白成python的汇编读出来了
dis.dis(code)https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550873.png
这就是python的汇编
我们这里是直接将pyc文件uncompyle6回去的,但是在CTF中一般都是把python的汇编直接复制粘贴出来丢给我们...
所以我们只能硬看...或者共同点东西 比如python的opcode一起看
增补一点:python2是3个字节为1个指令 python3是2个字节为1个指令
比如test.pyc中LOAD_CONST 指令就占用了0,1两个字节
所以下条指令就从3开始了
1           0 LOAD_CONST               0 ()
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

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

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内

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           #建立容量为32的列表
98 STORE_NAME               5 (text)    #以上32个数据为text数组的数值

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           #判断str即输入字符串的长度是否为32,不是则跳转到140
112 LOAD_NAME                4 (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           #判断str字符串的前七位是否为'DASCTF{',不是则跳转到140
128 LOAD_NAME                4 (str)
130 LOAD_CONST              39 (31)
132 BINARY_SUBSCR
134 LOAD_CONST              40 ('}')
136 COMPARE_OP               3 (!=)
138 POP_JUMP_IF_FALSE      154           #判断str字符串的最后一位也就是31位是否为'}',不是则跳转到154
#因为如果不跳转继续执行的话就会执行到输入字符串符合的一段代码使程序                                              #退出

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

15         148 LOAD_NAME                7 (exit)
150 CALL_FUNCTION            0
152 POP_TOP                              #退出程序

16     >>  154 LOAD_NAME                8 (list)    
156 LOAD_NAME                4 (str)
158 CALL_FUNCTION            1
160 STORE_NAME               9 (st)      #创建列表st

17         162 BUILD_LIST               0
164 STORE_NAME              10 (key)

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

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(0,32)

20         188 LOAD_NAME               13 (ord)
190 LOAD_NAME                4 (str)
192 LOAD_NAME               12 (i)
194 BINARY_SUBSCR
196 CALL_FUNCTION            1
198 LOAD_NAME               10 (key)        
200 LOAD_NAME               12 (i)
202 LOAD_NAME                6 (len)
204 LOAD_NAME               10 (key)
206 CALL_FUNCTION            1            
208 BINARY_MODULO                          #key元素少于str元素,所以要把i和key的长度取余避免越界
210 BINARY_SUBSCR
212 BINARY_XOR
214 LOAD_NAME                9 (st)
216 LOAD_NAME               12 (i)
218 STORE_SUBSCR                           #此处代码将str和key中的元素进行异或处理后存入st
220 JUMP_ABSOLUTE          184             #相当于st = ord(str) ^ key
>>  222 POP_BLOCK                            

21     >>  224 LOAD_NAME                9 (st)        
226 LOAD_NAME                5 (text)
228 COMPARE_OP               2 (==)
230 POP_JUMP_IF_FALSE      242             #对比st数组和text数组,不相等则跳转到地址242处

22         232 LOAD_NAME                2 (print)
234 LOAD_CONST              42 ('Congratulations and you are good at PYC!')
236 CALL_FUNCTION            1
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
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_FUNCTION            1
12 GET_ITER
>>   14 FOR_ITER                30 (to 46)
16 STORE_FAST               2 (i)         #相当于for i in range(0,8)
#从这里我们可以知道key的长度为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)

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赋值
42 POP_TOP
44 JUMP_ABSOLUTE           14
>>   46 POP_BLOCK
>>   48 LOAD_CONST               0 (None)

50 RETURN_VALUE这段代码的总体意思就是将输入的str字符串与key数组进行异或加密后存入st数组并于text数组进行对比我们可以从代码中得之text数组的元素值也可以知道str的前七位必为’DASCTF{’,末了一位必为’}’,而key数组只有8位,所以对str的加密是8位8位的进行的又因为异或具有自反性,所以可以据’DASCTF{‘字符串与text前7个元素做异或处理得出前7位,再将’}'与text末了一位进行异或 处理得出第8位,就可以得到key的整个数组
这段汇编最关键的部门如下
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_FUNCTION            1
12 GET_ITER
>>   14 FOR_ITER                30 (to 46)
16 STORE_FAST               2 (i)         #相当于for i in range(0,8)
#从这里我们可以知道key的长度为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)

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赋值
42 POP_TOP
44 JUMP_ABSOLUTE           14
>>   46 POP_BLOCK
>>   48 LOAD_CONST               0 (None)

50 RETURN_VALUE首先就是一个num的初始化 因为LOAD_CONST推送到堆栈
然后STORE_FAST将TOS(python的栈)存储到当地中
这两条结合起来 其实意思就是
num=0接下来 SETUP_LOOP(delta)
将一个循环的块推送到块堆栈。该块超过当前指令,巨细为delta字节。
LOAD_GLOBAL 定义一个全局变量 range LOAD_CONST定义一个常量 8
共同着utools里面程序员手册里面的Python库硬看
for i in range(8):然后关键的这一步
首先LOAD_FAST 将num压入栈堆,然后又把一个常量(7508399208111569251)推送到堆栈中
然后又执行BINARY_SUBTRACT 也就是减操纵 即栈的后一位减去栈顶,对应到代码中也就是num减去这个常量
然后又推了个值4294967295 然后进行BINARY_MODULO操纵 这是栈顶后一位取余栈顶的值
末了STORE_FAST 存储到num这个变量
所以这关键的一步python代码应该是
num=(num-7508399208111569251)%4294967295后面干的操纵大体就是 LOAD_FAST num 然后LOAD_CONST 24 然后BINARY_RSHIFT 主要是就是栈顶后一位右移栈顶数据的值 然后存储到key里面 大概就是这么个意思
print(num>>24)结合起来就是这样的
num=0
for i in range(8):
   num=(num-7508399208111569251)%4294967295
   print(num>>24)这样我们就得到这道题的密钥
40
80
121
161
202
242
27
67然后采用每八个字节都去异或一下这个密钥,flag就出来了
s=[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]
key=[]
flag=''
num=0
for i in range(8):
   num=(num-7508399208111569251)%4294967295
   key.append(num>>24)
for i in range(32):
   flag += chr(key ^ s)               
print(flag)打包成exe的pyc文件

解法:

[*]通过脚本酿成布局体和一个文件
[*]重点:再把时间属性和版本的魔术字放回去保存
[*]uncompyle6即可
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550825.png
下载完题目发现这是个exe文件 但是图标又是很显着的pyc文件
所以这是个打包成exe的py文件
这里我们需要用到一个工具pyinstxtractor.py
把这个py文件复制到我们的题目文件夹里面
在搜索框中输入powershell 在打开的终端中输入
python .\pyinstxtractor.py .\attachment.exehttps://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550210.png
运行后生成attachment.exe_extracted文件夹,进入之后看到一些源文件,由于我电脑上的python是3.8版本,解包要3.6版本,所以生成了不正常的入口文件login而不是login.pyc,想要酿成正常的可反编译的pyc文件就要对生成文件进行修改。(假如不嫌麻烦可以换一下python3.6的情况)
现在开始修改login入口文件,这里用的是winhex。
修改之前需要了解一点,在将python文件打包成exe文件的过程中,会抹去pyc文件前面的部门信息,所以在反编译之前需要查抄并添加上这部门信息,这部门信息可以通过struct文件获取。
windex中打开struct文件后,把struct文件前几个字节插入login开头。(具体要插入几个字节还是要看解包后的文件,我的文件是E3字节码前面的丢失,那么就只需要看struct中E3之前的字节码有哪些,ctrl + c复制,然后在login开头ctrl + v 粘贴即可。)
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550850.png
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550281.png
修改后如下
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550790.png
保存后 将login后缀名修改为.pyc即可
将login.pyc复制粘贴到题目的文件夹后打开powershell终端
并且调用uncompyle6.exe
uncompyle6.exe .\login.pyc就可以看到源码了
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404181550072.png
import sysinput1 = input('input something:')if len(input1) != 14:    print('Wrong length!')    sys.exit()else:    code = []    for i in range(13):        code.append(ord(input1) ^ ord(input1[(i + 1)]))​    code.append(ord(input1))    a1 = code    a2 = code    a3 = code    a4 = code    a5 = code    a6 = code    a7 = code    a8 = code    a9 = code    a10 = code    a11 = code    a12 = code    a13 = code    a14 = code    if (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) & (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) & (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) & (a1 * 23 + a2 * 34 + a3 * 35 - a4 * 59 + a5 * 49 + a6 * 81 + a7 * 25 + (a8 ></strong></p>  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: CTF中常见的四种python逆向