使用Python标准库对.pyc进行反编译的全过程

 更新时间:2026年03月09日 10:05:43   作者:feifeiyechuan  
在生产环境中,我们经常只能拿到 Python 的编译文件(.pyc),而没有原始 .py 源码,本文记录一次完整的从 .pyc 到可读源码的实践过程,需要的朋友可以参考下

在生产环境中,我们经常只能拿到 Python 的编译文件(.pyc),而没有原始 .py 源码。
本文记录一次完整的.pyc 到可读源码的实践过程,并整理成可复用的“知识库条目”,后续你可以对其他 .pyc 复用同样的方法。

环境说明(模拟场景):

  • 解释器:CPython 3.x
  • 目标文件:module_a.pyc(某业务模块的编译文件)
  • 工具:只使用 Python 标准库 dis、marshal(无第三方反编译器依赖)

一、整体思路概览

面对一个 .pyc 文件,我们的目标是:

  1. .pyc 里拿到 Code 对象(Python 的内部字节码表示);
  2. dis 把字节码反汇编成可读的文本指令(生成 your_module_dis.txt 之类的文本);
  3. 基于该文本里的指令列表,手工/半自动还原出逻辑等价的 Python 源码(得到 recovered_module.py 这样的还原版源码);
  4. 通过对比运行效果,验证还原代码与原模块行为一致

本文重点记录第 ①~③ 步的细节和踩坑点,方便后续你作为“知识库”查阅。

二、CPython.pyc文件的基本结构

了解一点 .pyc 结构有助于理解为什么要先 f.read(16)

  • 前 16 字节是头部(header),包含:
    • 魔数(magic number)
    • 标志位(flags)
    • 时间戳 / 源文件哈希
    • 源码大小等
  • 后面才是真正的 code object 数据,通过 marshal 模块进行序列化。

所以大致结构是:

[ 16 字节头部 ] + [ marshal.dump(code_object) 的二进制数据 ]

只要我们跳过前 16 字节,再用 marshal.load 读取,就能拿回一个可以被 dis 反汇编的 code 对象。

三、用dis + marshal导出字节码到文本(示例脚本)

核心导出脚本如下(示例脚本 disassemble_example.py):

import dis, marshal, types

# 示例:从业务模块 module_a.pyc 中读取字节码
with open(r"D:\demo_project\bin\module_a.pyc", "rb") as f:
    f.read(16)  # 跳过头部
    code = marshal.load(f)

with open("your_module_dis.txt", "w", encoding="utf-8") as out:
    dis.dis(code, file=out)

关键点说明:

  • f.read(16):跳过 .pyc 头 16 字节,只保留真正的字节码部分;
  • marshal.load(f):从剩余二进制流中反序列化出一个 code 对象;
  • dis.dis(code, file=out):对这个顶层 code 对象做反汇编,结果写入 your_module_dis.txt

执行完成后,会在同目录下生成一个比较长的 your_module_dis.txt,里面是类似这样的内容(下面是完全虚构的模拟输出片段,用于说明格式与思路):

  0           0 RESUME                   0

  2           2 LOAD_CONST               0 (0)
              4 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (json)
              8 STORE_NAME               0 (json)

  3          10 LOAD_CONST               0 (0)
             12 LOAD_CONST               1 (None)
             14 IMPORT_NAME              1 (re)
             16 STORE_NAME               1 (re)

  ...

  66          74 LOAD_CONST               6 (<code object extract_main_syms at 0x..., file "/app/demo_project/module_a.py", line 19>)
             104 MAKE_FUNCTION            0
             106 STORE_NAME              15 (extract_main_syms)

从这种输出格式中,可以看出:

  • 模块会先导入若干依赖模块;
  • 然后依次把若干 code object 绑定为函数。

这些信息就是后续还原源码时用于搭建结构的“骨架”,这里只是虚构的示例格式,不对应任何真实业务代码。

四、分析导出的文本:从字节码骨架还原模块结构

在导出的文本一开始,你一般会看到类似这样的模式(同样是虚构的模拟片段):

  11          74 LOAD_CONST               0 (0)
             76 LOAD_CONST               2 (('log_info', 'log_error'))
             78 IMPORT_NAME              9 (core)
             80 IMPORT_FROM             10 (log_info)
             82 STORE_NAME              10 (log_info)
             84 IMPORT_FROM             11 (log_error)
             86 STORE_NAME              11 (log_error)
             88 POP_TOP

  13          90 LOAD_CONST               3 ('DEMO_CODE')
             92 STORE_GLOBAL            12 (code_str)

  15          94 LOAD_CONST               4 ('https://api.example.com/i')
             96 STORE_NAME              13 (service_url)

  16          98 LOAD_CONST               5 (False)
            100 STORE_NAME              14 (debug_mode)

结合经验,可以直接还原为类似下面的伪代码形式(示例依然是虚构的,注意其中的配置项和值都只是演示用):

from core import log_error, log_info

code_str: str = "DEMO_CODE"
ICD_service_url: str = "https://api.example.com"
debug_mode: bool = False

还原模块级结构的一般步骤:

  1. 先关注所有 IMPORT_NAME / IMPORT_FROM:恢复顶层 import 语句;
  2. 找所有 STORE_NAME + 常量字符串:通常是模块级常量配置,如 code_str / URL / debug 开关;
  3. LOAD_CONST (<code object ...>) + MAKE_FUNCTION + STORE_NAME
    • 可以直接得到函数名:STORE_NAME 15 (extract_main_syms)def extract_main_syms(...):
    • 然后在 Disassembly of <code object extract_main_syms ...> 下面继续分析函数内部逻辑。

到这里为止,我们已经能搭出一个大致的模块轮廓。

五、函数内部:从字节码反推高层逻辑(虚构示例)

拿某个函数(例如 extract_main_syms)举例,在导出文本中它的反汇编开头可能类似(示意片段,完全虚构):

Disassembly of <code object extract_main_syms at 0x..., file "/app/demo_project/module_a.py", line 19>:
 19           0 RESUME                   0

 20           2 BUILD_LIST               0
              4 STORE_FAST               2 (result_syms)

 21           6 LOAD_FAST                0 (input_data)
              8 LOAD_CONST               1 ('record')
             10 BINARY_SUBSCR
             20 LOAD_CONST               2 ('main_field')
             22 BINARY_SUBSCR
             32 STORE_FAST               3 (main_text)

 22          34 LOAD_FAST                0 (input_data)
             36 LOAD_CONST               1 ('record')
             38 BINARY_SUBSCR
             48 LOAD_CONST               3 ('history_field')
             50 BINARY_SUBSCR
             60 STORE_FAST               4 (history_text)

根据变量名,可以还原出类似这样的伪代码(同样是虚构示例):

def extract_main_syms(input_data: Dict[str, Any]) -> List[str]:
    result_syms: List[str] = []

    record = input_data.get("record", {}) or {}
    history_text: str = record.get("history_field", "") or ""
    ...

通用还原技巧:

  • BUILD_LIST 0 + STORE_FAST (xxx)xxx = []
  • LOAD_FAST ... BINARY_SUBSCR 多连串 → 多级下标 / dict 取值;
  • 频繁出现的字符串常量(中文问诊要点、病史名称)→ 可以直接在源码里用同样的文本补回去;
  • CALL_FUNCTION / `CALL_METHOD`` 结合函数名、参数个数,大多数时候可以明确调用意图。

通过这一套操作,可以逐步把若干核心函数(例如 extract_main_syms 等)都还原为结构清晰、类型标注完善的 Python 源码(在你自己的还原文件中,例如 recovered_module.py)。

六、把反汇编结果固化为“知识库”:还原源码的实现策略

在一次完整的实践中,我们可以在一个还原文件(例如 recovered_module.py)中遵循下面几个原则:

  • 保持对外行为一致:函数名、参数、返回结构尽量和原模块保持兼容;
  • 逻辑等价优先,而非逐指令对齐:我们关注的是“相同输入 → 相同输出”,不追求一模一样的实现写法;
  • 适当增加类型标注和注释:方便后续维护和阅读,特别是复杂的业务流程;
  • 抽出可配置项:如 code_strICD_service_url、日期限制 date_limite 等统一放在模块顶部。

例如,顶层配置在还原后被集中到了模块开头(这里用模拟数据举例):

code_str: str = "DEMO_CODE"
ICD_service_url: str = "https://api.example.com/icd"
debug_mode: bool = False

对于某些主流程函数,可以按照原字节码中调用顺序,清晰划出若干步骤,例如:

  • 权限或授权有效期检查;
  • 输入参数解析及预处理;
  • 关键信息抽取、实体标注或特征工程;
  • 业务规则判断、打分与过滤;
  • 结果归组、加权及 Top-N 输出。

这些结构在源码层面被“语义化”之后,不再只是 LOAD_CONST / JUMP_FORWARD 这样的指令堆,而是清晰的业务流程。

七、实践经验与踩坑记录

反编译 .pyc 到可维护源码,过程中有一些值得记录的点(下面都以虚构示例来说明思路):

1)优先搞清楚“输入/输出”约定

  • 先从接口调用方(如外部服务或其它模块)入手,反向推函数参数含义,比直接看字节码更有效。

2)对复杂 if/for 结构,不要硬记指令,先画流程

  • 可以根据 JUMP_IF_FALSE_OR_POPPOP_JUMP_FORWARD_IF_FALSE 之类的跳转位置,画一张小流程图,再还原成 if/elif/else

3)保持一个“对照文件”

  • 建议始终保留一对文件:例如 your_module_dis.txt 作为原始反汇编结果,recovered_module.py 是还原后的源码,两边对照非常重要。

4)不要迷信自动反编译工具

  • 对于逻辑复杂、包含大量中文业务规则的代码,自动反编译经常给出难以阅读的结果,
    手工+半自动(基于 dis 的文本)更适合做“可维护的重构”。

八、如何复用这套方法处理其他.pyc

当你以后再遇到一个新的 .pyc,可以按下面的模板操作(完全和具体业务无关,只关注技术流程):

准备一个类似前文的 disassemble_example.py,只改文件路径

import dis, marshal

with open(r"你的目标文件.pyc", "rb") as f:
    f.read(16)
    code = marshal.load(f)

with open("your_module_dis.txt", "w", encoding="utf-8") as out:
    dis.dis(code, file=out)

打开生成的 your_module_dis.txt

  • 先定位所有 IMPORT_NAME / MAKE_FUNCTION + STORE_NAME,搭好模块骨架;
  • 再逐个函数分析 Disassembly of <code object ...> 部分。

在新建的 .py 文件中,还原可读源码

  • 先实现外部接口签名;
  • 再根据字节码实现内部逻辑;
  • 最后写一些测试用例,对比行为。

把还原心得也整理到类似本篇这样的 markdown 中

  • 方便你自己未来查阅,也方便团队里其他同事快速接手。

九、小结

  • 使用 Python 标准库的 marshal + dis,可以在不依赖第三方库的前提下,从 .pyc 中获取字节码并反汇编到文本;
  • 基于反汇编文本(如 your_module_dis.txt),可以逐步还原出结构清晰的源码文件(例如 recovered_module.py),作为长期维护版本;
  • 将整个流程和经验沉淀成 markdown 知识库(本文)后,可以非常方便地复用于今后所有 .pyc 分析与还原工作。

只要掌握了这一套流程,你在面对“只有 .pyc、没有源码”的场景时,就不会再那么无助,而是有一套稳定可复用的反编译+重构方法 论。

以上就是使用Python标准库对.pyc进行反编译的全过程的详细内容,更多关于Python标准库对.pyc反编译的资料请关注脚本之家其它相关文章!

相关文章

  • Python Tornado 框架使用终极指南

    Python Tornado 框架使用终极指南

    这篇文章主要为大家介绍了Python Tornado 框架使用终极指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Python解析m3u8拼接下载mp4视频文件的示例代码

    Python解析m3u8拼接下载mp4视频文件的示例代码

    这篇文章主要介绍了Python解析m3u8拼接下载mp4视频文件的示例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • python 拼接文件路径的方法

    python 拼接文件路径的方法

    今天小编就为大家分享一篇python 拼接文件路径的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • 听歌识曲--用python实现一个音乐检索器的功能

    听歌识曲--用python实现一个音乐检索器的功能

    本篇文章中主要介绍了用python实现一个音乐检索器,类似于QQ音乐的摇一摇识曲,有兴趣的同学可以了解一下。
    2016-11-11
  • Python中使用socket发送HTTP请求数据接收不完整问题解决方法

    Python中使用socket发送HTTP请求数据接收不完整问题解决方法

    这篇文章主要介绍了Python中使用socket发送HTTP请求数据接收不完整问题解决方法,本文使用一个循环解决了数据不完整问题,需要的朋友可以参考下
    2015-02-02
  • 检查Python中的变量是否是字符串(两种不同方法)

    检查Python中的变量是否是字符串(两种不同方法)

    数据类型是编程语言最重要的特征,它区分了我们可以存储的不同类型的数据,如字符串、int和float,这篇文章主要介绍了两种不同的方法来检查Python中的变量是否是字符串,需要的朋友可以参考下
    2023-08-08
  • python特效之字符成像详解

    python特效之字符成像详解

    这篇文章主要为大家介绍了python特效之字符成像,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Python实现的HMacMD5加密算法示例

    Python实现的HMacMD5加密算法示例

    这篇文章主要介绍了Python实现的HMacMD5加密算法,简单说明了HMAC-MD5加密算法的概念、原理并结合实例形式分析了Python实现HMAC-MD5加密算法的相关操作技巧,,末尾还附带了Java实现HMAC-MD5加密算法的示例,需要的朋友可以参考下
    2018-04-04
  • Pandas处理缺失数据的方式汇总

    Pandas处理缺失数据的方式汇总

    许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解 Pandas 如何表示缺失数据,并探索 Pandas 在 Python 中处理缺失数据的一些内置工具,需要的朋友可以参考下
    2025-09-09
  • PyCharm安装库numpy失败问题的详细解决方法

    PyCharm安装库numpy失败问题的详细解决方法

    今天使用pycharm编译python程序时,由于要调用numpy包,但又未曾安装numpy,于是就根据pycharm的提示进行安装,最后竟然提示出错,下面这篇文章主要给大家介绍了关于PyCharm安装库numpy失败问题的详细解决方法,需要的朋友可以参考下
    2022-06-06

最新评论