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

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(如\x00\x00\x00\x00),需用uncompyle6或decompyle3反编译,不能直接用dis或标准compile
struct.unpack 和 pefile 定位 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'PYZ\x00'(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 版本表(如\x33\xf3\x0d\x0a对应 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 看实际加载的字节流。











