Day17:python逆向
pyc逆向,python可执行文件逆向
pyc文件
- pyc文件是py文件编译过程中产生的中间文件,是一种二进制文件。pyc文件可以由python虚拟机直接执行。不同版本的python编译出来的pyc文件是不同的
pyc文件结构
一个pyc文件由以下几个部分组成:

其中CodeObject是经过序列化处理的python源码的二进制码
pyc字节码
pyc文件是二进制文件,因此也可以进行反汇编。pyc文件有专门的汇编代码,就像java有smali汇编一样。
下面是一个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原代码行号|指令的偏移|指令符号|指令参数(索引值)|参数实际值
0 0 RESUME 0
1 2 LOAD_CONST 0 (<code object add at 0x00000186246D3770, file "test.py", line 1>)
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (add)//把add函数存入函数表,给索引值0
3 8 PUSH_NULL
10 LOAD_NAME 1 (print)//加载函数表索引为1的函数print
12 LOAD_CONST 1 ('hello python')//加载常量表索引为1的"hello python"
14 CALL 1//call索引为1的函数print
22 POP_TOP
4 24 PUSH_NULL
26 LOAD_NAME 1 (print)
28 PUSH_NULL
30 LOAD_NAME 0 (add)
32 LOAD_CONST 2 (1)
34 LOAD_CONST 3 (2)
36 CALL 2
44 CALL 1
52 POP_TOP
54 RETURN_CONST 4 (None)
Disassembly of <code object add at 0x00000186246D3770, file "test.py", line 1>:
1 0 RESUME 0
2 2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 BINARY_OP 0 (+)
10 RETURN_VALUE对上面的一些指令进行解释:
- LOAD_CONST用于加载常量,后面跟着的数字是常量在常量表中的索引
- LOAD_NAME用于加载函数,后面跟着的数字是函数在函数表中的索引
- LOAD_FAST用于加载局部变量(如函数定义时候的参数或者内部变量)
- 可以注意到LOAD_NAME总是先于LOAD_CONST的,即函数比参数先一步加载
- python虚拟机是基于栈的,所以可以看到POP等字眼,LOAD系列操作实际上是入栈操作
pyc文件逆向
- 未经过混淆的pyc文件,可以直接通过在线工具(如:在线Python pyc文件编译与反编译)或者uncompyle6、decompyle3(均可通过pip install安装)转成python代码
- pyc文件的混淆,就类似花指令,通过在汇编层面插入垃圾指令干扰静态分析。解决办法就是修改二进制文件,把垃圾指令的二进制码给删除。可以看看这篇: Python代码保护 | pyc 混淆从入门到工具实现 - 知乎
python可执行文件
python文件可以被打包成可执行文件,默认图标长这样:

对于此类问题,先使用pyinstxtractor解包程序(这里拿一个不知道哪里来的pythonexe文件举例):

pyinstxtractor会把可执行文件解包到带后缀extracted的一个文件夹里,除此之外可以看到,程序会给出可能的入口文件,以及可执行文件的python版本,然后就可以分析这些pyc文件了
python文件被打包的时候还可以指定加密参数,解密的时候根据打包的pyinstaller版本可以分为两种,大于4.0的和小于4.0的,可以使用下面的脚本解密:
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//大于4.0版本
import tinyaes
import zlib
CRYPT_BLOCK_SIZE=16
key = butes('here_be_your_key','utf-8')
inf=open('here_be_your_file_name','rb')
outf=open('here_be_your_output_file','wb')
iv=inf.read(CRYPT_BLOCK_SIZE)
cipher=tinyaes.AES(key,iv)
plaintext=zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')#here be your magic number from pyfile in the same dir,16bytes
outf.write(plaintext)
inf.close()
outf.close()
//小于4.0版本
from Crypto.Cipher import AES
import zlib
CRYPT_BLOCK_SIZE=16
key=b'here_be_your_key'
inf=open('here_be_your_cryptedfile_name','rb')
outf=open('here_be_your_output_file_name','wb')
iv=inf.read(CRYPT_BLOCK_SIZE)
cipher-AES.new(key,AES.MODE_CFB,iv)
plaintext=zlib.decompress(cipher,hecrypt(inf.read()))
outf.write('here_be_your_magicnumber')#8bytes
outf.write(plaintext)
inf.close()
outf.close()代码里面的key可以找可疑的pyc文件转成python代码查看,而版本可以通过查看pyimod01_archive.pyc导入的加密库来识别(上面两个版本的脚本导入的加密库分别是tinyaes和AES,是不一样的)
Day17:python逆向