当前位置: 代码网 > it编程>前端脚本>Python > 使用Python标准库对.pyc进行反编译的全过程

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

2026年03月09日 Python 我要评论
在生产环境中,我们经常只能拿到 python 的编译文件(.pyc),而没有原始 .py 源码。本文记录一次完整的从 .pyc 到可读源码的实践过程,并整理成可复用的“知识库条目&rdqu

在生产环境中,我们经常只能拿到 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反编译的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com