逆向工程实战:从PyInstaller打包的EXE中还原Python源码全流程
当你拿到一个用PyInstaller打包的Python程序,却发现没有原始代码时该怎么办?本文将带你完整走通从识别打包工具到最终还原.py文件的每个关键步骤。不同于简单的工具介绍,我们会深入每个操作背后的原理,并提供可直接复用的解密脚本。
1. 逆向工程前的准备工作
逆向PyInstaller打包的程序需要一套特定的工具链。以下是必备工具及其作用:
- Detect It Easy (DIE):用于快速识别文件类型和打包工具
- pyinstxtractor:PyInstaller专用解包工具
- uncompyle6:将.pyc文件反编译为.py源码
- 010 Editor:十六进制编辑器,用于分析文件头
- Python环境:需与打包时使用的Python版本一致
重要提示:Python版本必须严格匹配。用Python 3.8打包的exe,必须用Python 3.8环境进行解包,否则会出现magic number不匹配的错误。
安装核心工具的命令如下:
pip install uncompyle6 git clone https://github.com/extremecoders-re/pyinstxtractor2. 解包PyInstaller生成的EXE文件
使用pyinstxtractor进行解包是逆向的第一步。这个工具能解析PyInstaller的特殊打包格式,提取出内部的.pyc字节码文件。
典型解包操作:
python pyinstxtractor.py target.exe解包后会生成target.exe_extracted目录,其中包含这些关键文件:
| 文件/目录 | 作用 |
|---|---|
| PYZ-00.pyz_extracted | 存放所有依赖库的.pyc文件 |
| pyimod00_crypto_key.pyc | 存放加密密钥(如果使用了加密) |
| main.pyc | 主程序的字节码文件 |
常见问题排查:
- 如果解包失败,首先检查Python版本是否匹配
- 加密过的exe可能需要先提取密钥才能完整解包
- 某些加固过的exe可能需要手动修复文件头
3. 处理加密的PyInstaller打包文件
PyInstaller支持使用--key参数进行加密打包。识别加密的方法很简单:
- 检查PYZ-00.pyz_extracted目录中的文件
- 如果看到
.pyc.encrypted后缀的文件,说明使用了加密
解密需要两个关键信息:
- 加密密钥(存储在pyimod00_crypto_key.pyc中)
- PyInstaller的加密算法版本(区分4.0前后)
提取密钥的步骤:
uncompyle6 -o key.py pyimod00_crypto_key.pyc然后根据PyInstaller版本选择对应的解密脚本:
PyInstaller < 4.0 解密脚本:
from Crypto.Cipher import AES import zlib def decrypt_file(encrypted_path, output_path, key): with open(encrypted_path, 'rb') as inf: iv = inf.read(16) cipher = AES.new(key, AES.MODE_CFB, iv) plaintext = zlib.decompress(cipher.decrypt(inf.read())) with open(output_path, 'wb') as outf: outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0') # Python 3.8头 outf.write(plaintext)PyInstaller ≥ 4.0 解密脚本:
import tinyaes import zlib def decrypt_file(encrypted_path, output_path, key): with open(encrypted_path, 'rb') as inf: iv = inf.read(16) cipher = tinyaes.AES(key, iv) plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read())) with open(output_path, 'wb') as outf: outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0') # Python 3.8头 outf.write(plaintext)4. 反编译.pyc字节码文件
获得正常的.pyc文件后,使用uncompyle6进行反编译:
uncompyle6 -o output.py input.pyc对于批量处理,可以使用这个脚本:
import os from pathlib import Path def batch_decompile(input_dir, output_dir): os.makedirs(output_dir, exist_ok=True) for pyc in Path(input_dir).rglob("*.pyc"): cmd = f"uncompyle6 -o {output_dir}/{pyc.stem}.py {pyc}" os.system(cmd)反编译常见问题及解决方案:
Magic number不匹配:
- 症状:
ValueError: Bad magic number in .pyc file - 解决:手动修正.pyc文件头,或使用正确Python版本
- 症状:
反编译失败:
- 症状:输出无意义代码或报错
- 可能原因:代码被混淆或优化过
- 解决:尝试其他反编译工具如pycdc
部分代码缺失:
- 可能原因:使用了Cython扩展
- 解决:需要更高级的逆向技术
5. 高级技巧与实战经验分享
在实际逆向过程中,会遇到各种特殊情况。以下是几个实用技巧:
技巧1:修复损坏的.pyc文件头
有时解包得到的.pyc文件头可能损坏,可以手动修复:
- 用010 Editor打开.pyc文件
- 根据Python版本写入正确的magic number
- 保存后重新尝试反编译
各Python版本的magic number对应表:
| Python版本 | Magic Number (十六进制) |
|---|---|
| 3.7 | 42 0D 0D 0A |
| 3.8 | 55 0D 0D 0A |
| 3.9 | 61 0D 0D 0A |
| 3.10 | 6F 0D 0D 0A |
技巧2:处理混淆过的代码
遇到混淆代码时,可以尝试:
- 使用astor库重构AST
- 手动分析控制流
- 使用调试器动态跟踪执行
技巧3:提取嵌入的资源文件
PyInstaller打包的资源文件可以通过这些步骤提取:
from PyInstaller.utils.win32.resource import GetResources resources = GetResources('target.exe') for res in resources: with open(res.name, 'wb') as f: f.write(res.data)逆向工程既是技术也是艺术。每个PyInstaller打包的程序都可能带来独特的挑战,但掌握了这套方法论后,你就能系统性地解决大多数逆向问题。