python解包exe_PyInstaller打包程序的逆向分析与资源提取

3次阅读

PyInstaller打包的exe中资源存于嵌入的PYZ归档内,位于PE资源段(如PYINST010)或文件末尾,运行时由bootloader动态加载而非解压到磁盘。

python解包exe_PyInstaller打包程序的逆向分析与资源提取

PyInstaller 打包的 exe 里资源在哪?

PyInstaller 默认把 python 字节码(.pyc)、依赖库、数据文件全塞进一个归档中,不是直接放在文件系统里。这个归档叫 PYZ,通常嵌在 exe 的资源段(windows)或末尾(跨平台),也可能被加密或压缩。你双击打开 exe 看不到任何 .py 或 .pyc 文件,是因为它们根本没“解压到磁盘”——运行时由 PyInstaller 的 bootloader 动态加载。

pyinstxtractor.py 提取基础结构

这是目前最可靠的静态提取工具,能识别 PyInstaller 的打包格式并分离出 PYZ 归档、exe 资源、外部依赖等。注意它不处理加壳(如 UPX)或自定义加密,得先脱壳再跑:

  • 确保目标 exe 没被 UPX 压缩,否则先执行 upx -d target.exe
  • 运行 python pyinstxtractor.py target.exe,会生成 target.exe_extracted/ 目录
  • 关键产出是 out00-PYZ.pyz(或类似命名的 PYZ 文件),它本质是 ZIP,可直接用 unzip out00-PYZ.pyz -d pyc_out 解开
  • 解出来的多是 pyc 文件,但头部带 PyInstaller 特定 magic(如 x00x00x00x00),需用 uncompyle6decompyle3 反编译,不能直接用 dis 或标准 compile

Struct.unpackpefile 定位 PYZ 起始偏移

pyinstxtractor 失败(比如定制 bootloader 或混淆了资源标识),就得手动找 PYZ 数据块。PyInstaller 在 windows 上常把 PYZ 存在名为 PYINST010 的资源类型下,或直接追加在 PE 文件末尾:

  • pefile 检查资源表:pe = pefile.PE("target.exe"); [r for r in pe.DIRECTORY_ENTRY_RESOURCE.entries if hasattr(r, 'name') and r.name and b'PYINST' in r.name.name]
  • 若没资源项,尝试从文件末尾倒查:读取最后 1MB,搜索字节序列 b'PYZx00'(PYZ header),再用 struct.unpack(' 读取后续 4 字节得到实际长度
  • 提取出的原始 PYZ 数据可能含 AES 加密头(如果打包时用了 --key),此时必须知道密钥才能继续,否则反编译会失败并报错 ValueError: bad marshal data

反编译 pyc 时 uncompyle6 报错怎么办?

常见错误包括 RuntimeError: don't know how to handle op 172(新字节码指令)或 marshal.loads() failed(magic 不匹配)。本质是 pyc 版本与反编译器不一致:

立即学习Python免费学习笔记(深入)”;

  • 先用 file_header = open("xxx.pyc", "rb").read(8) 查看前 4 字节 magic,对照 Python 版本表(如 x33xf3x0dx0a 对应 Python 3.9)
  • 强制指定版本运行:uncompyle6 --pyversion 3.9 xxx.pyc,别依赖自动探测
  • 若仍失败,换 decompyle3(对较新版本支持更好),或用 pycdc(C++ 实现,更底层)
  • 注意:PyInstaller 有时会 patch pyc header 中的 timestamp 和 size 字段,导致校验失败,可临时删掉前 12 字节再试(仅调试用)

真正卡住的地方往往不是提取,而是 bootloader 自定义修改或运行时解密逻辑——这些没法靠静态分析搞定,得结合 Process Monitor 抓文件行为,或用 x64dbg 下断在 PyImport_ExecCodeModule 看实际加载的字节流。

text=ZqhQzanResources