前言

上一篇介绍了如何用Pyinstaller打包python文件成bin文件,那问题来了,我们拿到一个Pyinstaller打包的bin文件,想看代码怎么办,这就要用到Pyinstaller反编译了。

方法

一、获取pyc文件

我们第一步采用的工具是pyinstxtractor.py,可以将Pyinstaller生成的bin文件解包成pyc文件。
之后把这个文件复制到bin文件同级目录下,运行如下命令:

1
python pyinstxtractor.py xxbin

运行后生成xxbin.extracted文件夹 ,里面有一堆so ,pyc等文件;

我们还注意到此目录下还有一个PYZ-00.pyz_extracted文件夹,里面都是引入的依赖库,但是注意,里面文件都是.pyc.extracted格式,是加密的,需要进一步反编译

二、pyc文件解密

在反编译python生成可执行文件bin时,引用的类库文件经常遇到使用Crypto 模块AES算法加密,解包生成的并不是pyc文件,而是加密的pyc. encrypted文件,当然它也无法查看编译。当然,它也是可以解密的。

第一步,获取Crypto的key,这是打包时由开发者指定的。解包完成后将在根目录形成名为"pyimod00_crypto_key.pyc"的文件,将它转为py文件即可查看key文件。key是必须文件,否则无法进行解密;

第二步,对于不同pyton版本头文件(header)也不相同,2.7~3.10如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
Python 2.7: \x03\xf3\x0d\x0a\0\0\0\0
Python 3.0: \x3b\x0c\x0d\x0a\0\0\0\0
Python 3.1: \x4f\x0c\x0d\x0a\0\0\0\0
Python 3.2: \x6c\x0c\x0d\x0a\0\0\0\0
Python 3.3: \x9e\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.4: \xee\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.5: \x17\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.6: \x33\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.7: \x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.8: \x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.9: \x61\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.10: \x6f\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0

第三步,编写解密处理的脚本代码,将加密的pyc. encrypted文件转成不加密的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
33
34
import glob
import zlib
import tinyaes
from pathlib import Path

CRYPT_BLOCK_SIZE = 16

# key obtained from pyimod00_crypto_key
key = bytes('0000000000013983', 'utf-8')

for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"):
inf = open(p, 'rb') # encrypted file input
outf = open(p.with_name(p.stem), 'wb') # output file

# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)

cipher = tinyaes.AES(key, iv)

# Decrypt and decompress
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))

# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')

# Write decrypted data
outf.write(plaintext)

inf.close()
outf.close()

# Delete .pyc.encrypted file
p.unlink()

三、pyc反编译为py

Linux下可以使用uncompyle6或者decompyle3来将pyc文件转成py文件,这里使用decompyle3,直接decompyle3 xx.pyc > xx.py即可。

当然,有个在线工具可以直接pyc转py。

四、Code

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
#!/usr/bin/env python
# coding: utf-8

import os
import sys
import pyinstxtractor
import glob
import zlib
import tinyaes
from pathlib import Path
import re


class Main():
def __init__(self, parent=None):
self.exe_file = sys.argv[1]
self.pyc_dir = ""
self.key = ""
self.files = []

def exe2pyc(self):
# 将exe转化为pyc文件
sys.argv = ['pyinstxtractor.py', self.exe_file]
pyinstxtractor.main()

def get_pyc_dir(self):
self.pyc_dir = os.path.basename(self.exe_file) + "_extracted"

def encrypted2pyc(self, root):
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = bytes(self.key[0], 'utf-8')
# key = bytes('0000000000013983', 'utf-8')
for p in Path(root).glob("**/*.pyc.encrypted"):
inf = open(p, 'rb') # encrypted file input
outf = open(p.with_name(p.stem), 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
cipher = tinyaes.AES(key, iv)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))
# Write pyc header
# The header below is for Python 3.7
outf.write(b'\x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
# Delete .pyc.encrypted file
# p.unlink()
print("change %s ---> %s"%(p, p.with_name(p.stem)))

def pyc2py(self, root, files):
for file_name in files:
if file_name.endswith('.pyc'):
if not self.check_if_file_exists(file_name):
if file_name.endswith('.pyc'):
part_name = file_name[0:-4]
part_file_name = os.path.join(root, part_name).replace("\\","/")
os.system("decompyle3 %s.pyc > %s.py"%(part_file_name, part_file_name))
print("change %s.pyc ---> %s.py"%(part_file_name, part_file_name))
else:
print("%s already exist, skip"%(file_name[0:-4]+'.py'))
continue


def get_key_from_crypto_key(self):
for root, dirs, files in os.walk(self.pyc_dir, True):
for file_name in files:
if file_name == "pyimod00_crypto_key.pyc":
part_name = file_name[0:-4]
part_file_name = os.path.join(root, part_name).replace("\\","/")
os.system("decompyle3 %s.pyc > %s.py"%(part_file_name, part_file_name))
print("change %s.pyc ---> %s.py"%(part_file_name, part_file_name))
with open(part_file_name+'.py', 'r') as f:
for line in f.readlines():
if "key = " in line:
self.key = re.findall(r"'([^']*)'", line)

def check_if_file_exists(self, file_name):
part_name = file_name[0:-4] + '.py'
if part_name in self.files:
return True
return False

def exe2py(self):
self.exe2pyc()
# 恢复当前目录位置
os.chdir("..")
self.get_pyc_dir()
self.get_key_from_crypto_key()
pyz_file = self.pyc_dir+"/PYZ-00.pyz_extracted"
self.encrypted2pyc(pyz_file)
for root, dirs, files in os.walk(self.pyc_dir, True):
self.files = files
self.pyc2py(root, files)


if __name__=="__main__":
if len(sys.argv) < 2:
print('[+] Usage: exe2py.py <filename>')
mainFunc = Main()
magic = mainFunc.exe2py()


©2018 - Felicx 使用 Stellar 创建
总访问 113701 次 | 本页访问 326
共发表 83 篇Blog · 总计 127.5k 字