Python打包的可执行文件的逆向工程

作者: Gao 日期: 2024-03-28
Python打包的可执行文件的逆向工程

PyInstaller

如果exe文件是使用PyInstaller打包的。我们可以用PyInstaller Extractor解包exe文件。

python pyinstxtractor.py <文件名>.exe

但是必须要使用相同的Python版本才能解包PYZ文件。解包成功后我们会得到一系列的.pyc文件。

我们可以使用uncompyle6去解压.pyc文件

uncompyle6 xxx.pyc

假如解压PYZ文件的过程中出现下面的这些错误的话, 那这个文件在生成的时候是被加密了

...
[!] Error: Failed to decompress PYZ-00.pyz_extracted\xxx.pyc, probably encrypted. Extracting as is.
...

加密的密钥可以在pyimod00_crypto_key.pyc这个文件找到

uncompyle6 pyimod00_crypto_key.pyc > pyimod00_crypto_key.py

Pyinstaller的加密功能使用tinyaes实现的。既然有了密钥只需要逆向解密成正常的.pyc文件再用uncompyle6转为.py文件。

decompile-python-exe.pyview raw
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
import tinyaes

from pyimod00_crypto_key import key

CIPHER_BLOCK_SIZE = 16
CIPHER_KEY = bytes(key, 'utf-8')
PYC_HEADER = b"\x42\x0D\x0D\x0A\x00\x00\x00\x00\x70\x79\x69\x30\x10\x01\x00\x00"
ENCRYPTED_FILENAME_SUFFIX = ".pyc.encrypted"
PYC_FILENAME_SUFFIX = ".pyc"


def encrypted_to_pyc(input_path, output_path):
with open(input_path, "rb") as encrypted_pyc:
with open(output_path, "wb") as pyc:
cipher_iv = encrypted_pyc.read(CIPHER_BLOCK_SIZE)
encrypted_data = encrypted_pyc.read()
cipher = tinyaes.AES(CIPHER_KEY, cipher_iv)
decrypted_data = cipher.CTR_xcrypt_buffer(encrypted_data)
plaintext = zlib.decompress(decrypted_data)
pyc.write(PYC_HEADER)
pyc.write(plaintext)
print(f"decrypt {input_path} and save result to {output_path}")


def pyc_to_py_with_uncompyle6(input_path):
try:
return subprocess.call(["uncompyle6", "-o", os.path.dirname(input_path), input_path], timeout=30)
except:
return -1


def pyc_to_py_with_decompile3(input_path):
try:
return subprocess.call(["decompyle3", "-o", os.path.dirname(input_path), input_path], timeout=30)
except:
pass
# fallback to uncompyle6
return pyc_to_py_with_uncompyle6(input_path)


def main():
for root, dirs, files in os.walk(".", topdown=False):
for filename in files:
filepath = os.path.join(root, filename)
if filename.endswith(".pyc.encrypted"):
pyc_filepath = filepath[:-len(ENCRYPTED_FILENAME_SUFFIX)] + PYC_FILENAME_SUFFIX
encrypted_to_pyc(filepath, pyc_filepath)
failed_list = []
for root, dirs, files in os.walk(".", topdown=False):
for filename in files:
filepath = os.path.join(root, filename)
if filename.endswith(PYC_FILENAME_SUFFIX):
if pyc_to_py_with_decompile3(filepath) != 0:
failed_list.append(filepath)
print(failed_list)

Pyinstaller最新的版本中已经移除了加密的功能

有些文件用uncompyle6会解析失败 因为对Python 3.7以上的版本支持并不完全 所以还可以用decompyle3去尝试失败的文件

参考