如何从 PyInstaller 打包的可执行文件中提取和反编译源码

背景

在开发 Python 应用时,使用 PyInstaller 打包为可执行文件(如 .exe)是常见的做法。PyInstaller 将 Python 源码转换成可执行文件,但有时候我们可能需要从这些打包的文件中恢复源代码。例如,处理遗失的源码或研究他人的代码时,这一过程非常有用。

在本文中,我们将详细介绍如何从 PyInstaller 打包的文件中提取源码,并通过反编译恢复成可读的 Python 代码。


步骤 1:解包 PyInstaller 可执行文件

当我们通过 PyInstaller 打包 Python 程序时,所有的源代码(.py 文件)会被编译成字节码 .pyc 文件,并和 Python 解释器及其依赖打包成一个可执行文件。我们的目标是从中提取出 .pyc 文件。

使用 pyinstxtractor 解包 PyInstaller 可执行文件

首先,我们需要使用 pyinstxtractor.py 这个开源脚本来解包可执行文件。它会将打包文件中的内容提取出来,包括 .pyc 文件。

  1. 下载 pyinstxtractor.py 脚本,可以从 pyinstxtractor GitHub 仓库 获取。
  2. 运行脚本: 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 提取文件

  1. 使用 pyi-archive_viewer(PyInstaller 自带的工具)来查看归档内容。首先,运行以下命令来查看归档内容: pyi-archive_viewer your_app.exe > toc
  2. 在查看到的 TOC(目录)中,找到 .pyc 文件(例如 api.pycmain.pyc 等)。
  3. 使用 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 错误

  1. 确认 Python 版本: 你需要确认可执行文件使用的 Python 版本。可以通过查看解包后的 python310.dll(或类似文件)确认 Python 版本。如果版本是 3.7,请确保用 3.7 版本的 Python 进行反编译。
  2. 反编译 .pyc 文件: 使用 decompyle3uncompyle6 反编译 .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 压缩

  1. 在提取 .pyc 文件后,首先检查文件内容是否压缩。如果文件是压缩过的,你需要先解压。
  2. 使用 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 文件都能恢复成源代码。

Leave a Reply

Your email address will not be published. Required fields are marked *

*