背景
在开发 Python 应用时,使用 PyInstaller 打包为可执行文件(如 .exe
)是常见的做法。PyInstaller 将 Python 源码转换成可执行文件,但有时候我们可能需要从这些打包的文件中恢复源代码。例如,处理遗失的源码或研究他人的代码时,这一过程非常有用。
在本文中,我们将详细介绍如何从 PyInstaller 打包的文件中提取源码,并通过反编译恢复成可读的 Python 代码。
步骤 1:解包 PyInstaller 可执行文件
当我们通过 PyInstaller 打包 Python 程序时,所有的源代码(.py
文件)会被编译成字节码 .pyc
文件,并和 Python 解释器及其依赖打包成一个可执行文件。我们的目标是从中提取出 .pyc
文件。
使用 pyinstxtractor 解包 PyInstaller 可执行文件
首先,我们需要使用 pyinstxtractor.py
这个开源脚本来解包可执行文件。它会将打包文件中的内容提取出来,包括 .pyc
文件。
- 下载
pyinstxtractor.py
脚本,可以从 pyinstxtractor GitHub 仓库 获取。 - 运行脚本:
python pyinstxtractor.py your_app.exe
运行后,脚本会生成一个新的文件夹,其中包含解压出来的文件。例如:
main.pyc
(入口脚本)PYZ-00.pyz
(其他模块的字节码)base_library.zip
(标准库)
步骤 2:提取 PYZ-00.pyz
中的字节码
PYZ-00.pyz
是 PyInstaller 用来存放所有 .pyc
字节码文件的归档文件。我们需要从中提取出 .pyc
文件。
使用 pyi-archive_viewer
提取文件
- 使用
pyi-archive_viewer
(PyInstaller 自带的工具)来查看归档内容。首先,运行以下命令来查看归档内容:pyi-archive_viewer your_app.exe > toc
- 在查看到的 TOC(目录)中,找到
.pyc
文件(例如api.pyc
,main.pyc
等)。 - 使用
X
命令来提取这些.pyc
文件:X api.pyc
提取后,你将得到 .pyc
文件。
步骤 3:解决反编译问题
从 PyInstaller 打包的文件中提取出的 .pyc
文件并不直接可以反编译,原因是 PyInstaller 可能使用了不同的 Python 版本(例如 3.7、3.8)。如果直接使用反编译工具反编译 .pyc
,你可能会遇到 bad magic number
错误。这是因为 .pyc
文件是为特定版本的 Python 编译的,反编译时需要使用相同版本的 Python 解释器。
解决 bad magic number
错误
- 确认 Python 版本: 你需要确认可执行文件使用的 Python 版本。可以通过查看解包后的
python310.dll
(或类似文件)确认 Python 版本。如果版本是 3.7,请确保用 3.7 版本的 Python 进行反编译。 - 反编译
.pyc
文件: 使用decompyle3
或uncompyle6
反编译.pyc
文件。如果.pyc
是为 Python 3.7 编译的,请使用以下命令:decompyle3 -p 3.7 api.pyc > api.py # 或 uncompyle6 --py=3.7 api.pyc > api.py
这将把字节码转换回.py
源代码。
步骤 4:处理 zlib 压缩的字节码
有时,.pyc
文件可能会被 zlib 压缩过。在这种情况下,反编译工具会报错,如 bad marshal data
。
处理 zlib 压缩
- 在提取
.pyc
文件后,首先检查文件内容是否压缩。如果文件是压缩过的,你需要先解压。 - 使用 Python 代码解压和加载
marshal
字节码:
import marshal
import zlib
def maybe_decompress(data):
try:
return zlib.decompress(data)
except Exception:
return data
def load_marshal_code(file_path):
with open(file_path, 'rb') as f:
raw_data = f.read()
data = maybe_decompress(raw_data)
return marshal.loads(data)
通过这种方式,你可以从 zlib 压缩的字节码中提取代码对象并进行反编译。
步骤 5:生成 .pyc
并反编译
一旦你成功提取并解压了 .pyc
文件,接下来就是给文件加上 .pyc
头部并反编译。
为 .pyc
文件添加头部
使用 Python 3.7(与目标 Python 版本一致)将解压出来的字节码文件加上 .pyc
头部:
import marshal
import struct
import time
def create_pyc_header(raw_code):
magic = b'\x03\xf3\r\n' # Python 3.7 magic number
timestamp = struct.pack("<Q", int(time.time()))
return magic + struct.pack("<I", 0) + timestamp + raw_code
然后,使用反编译工具将 .pyc
文件转换为 Python 源代码:
decompyle3 -p 3.7 api_fixed.pyc > api.py
# 或
uncompyle6 --py=3.7 api_fixed.pyc > api.py
总结
通过以上步骤,你可以从 PyInstaller 打包的可执行文件中提取和恢复 Python 源代码。这包括:
- 使用
pyinstxtractor
解包.exe
文件。 - 提取
.pyc
文件,并处理压缩或加密的字节码。 - 反编译
.pyc
文件,并恢复源代码。
需要注意的是,反编译过程可能会遇到一些挑战,特别是当源代码被加密或压缩时。不过,通过逐步解压、恢复字节码并正确使用反编译工具,几乎所有的 .pyc
文件都能恢复成源代码。