逆向工程实战手册 第 78 章

第 78 章 Python 字节码逆向专项

Python 应用(尤其 PyInstaller 打包的)是现代恶意软件和商业软件的常见形态。Python 字节码逆向的核心:pyc 文件结构、反编译工具、版本差异、代码混淆(Pyarmor 等)。掌握后,绝大多数 Python 目标等于"读源码"。

📍 知识点地图 | 主题:Python字节码 | 前置:第14章 | 后续:— | 核心概念:pyc结构、uncompyle6、PyInstaller、Pyarmor

78.1 Python 程序形态(先分类)

形态 1: .py 源码(直接读)
形态 2: .pyc 字节码(编译产物)→ 反编译
形态 3: PyInstaller 打包(exe)→ 解包 → pyc
形态 4: Pyarmor 混淆 → 特殊处理
形态 5: 嵌入式(游戏/固件里的 Python)→ 提取后同 2

识别: 文件头特征/字符串(pyinstaller: "PyInstaller" 字符串)

78.2 pyc 文件结构(懂格式才能处理)

pyc 头(Python 3.7+):
  magic(4 字节: 版本标识)
  flags(4 字节)
  timestamp(4 字节)/ hash-based(可选)
  大小(4 字节)
  之后: code object(marshal 序列化的代码对象)

code object 结构:
  co_code(字节码指令)
  co_consts(常量元组: 数字/字符串/嵌套 code)
  co_names(名称元组: 变量名/函数名)
  co_varnames(局部变量)
  co_freevars/co_cellvars(闭包变量)

反编译 = 从 code object 还原 Python 源码

78.3 反编译工具链

# 1. uncompyle6(经典,支持到 3.8)
uncompyle6 -o out_dir app.pyc

# 2. decompyle3(3.7-3.8 改进版)

# 3. pycdc(C++ 实现,3.9+ 可用,需编译)
git clone https://github.com/zrax/pycdc && cd pycdc && cmake . && make
./pycdc app.pyc

# 4. 新版 Python(3.9-3.13+): pycdc 仍是主力
# 5. 查看字节码(工具不行时手动):
python3 -m dis app.pyc        # dis 模块反汇编

版本不匹配的处理

pyc 的 magic 对应 Python 版本(不同版本字节码不同)
工具报"unsupported version" → 换对应版本工具/编译 pycdc 支持
# 识别版本: magic 值对照表 / 前 4 字节

78.4 手动字节码分析(工具失败时)

# 读取并反汇编 code object
import marshal, dis, struct

with open("app.pyc", "rb") as f:
    f.read(16)                    # 跳过 pyc 头
    code = marshal.load(f)        # 反序列化 code object

dis.dis(code)                     # 打印字节码
# 递归: code.co_consts 里嵌套的 code object
for c in code.co_consts:
    if hasattr(c, "co_code"):
        dis.dis(c)

常见字节码模式(对照源码)

# 模式 1: 比较(XOR 校验的常见形态)
# 字节码: LOAD_FAST/LOAD_CONST/BINARY_XOR/COMPARE_OP
# 源码: if a[i] ^ key[i] != expected[i]:

# 模式 2: 字符串处理
# LOAD_CONST 字符串 → 拼接/比较

# 模式 3: 条件分支
# POP_JUMP_IF_FALSE/POP_JUMP_IF_TRUE → if/循环

# 反推思路:
#   从常量表(co_consts)直接拿字符串/数字
#   从字节码恢复逻辑顺序

78.5 PyInstaller 解包

# 1. 识别: 文件里有 "PyInstaller" / "pyi-"
# 2. 解包工具(pyinstxtractor):
git clone https://github.com/extremecoders-re/pyinstxtractor
python3 pyinstxtractor.py app.exe
# 输出: app.exe_extracted/
#   PYZ-00.pyz(压缩的 pyc 库)
#   主脚本 pyc(如 app.pyc)
#   struct 等内置模块

# 3. 解 PYZ(uncompyle6 或 pyinstxtractor 附带功能)
# 4. 反编译 pyc(78.3)
# 5. 修复 pyc 头(magic 不匹配时从 struct 模块复制)

78.6 Pyarmor 混淆处理

识别: 字符串 "pyarmor" / PyArmor 相关导入 / 特殊运行时

Pyarmor 原理:
  □ 字节码加密 + 运行时解密(不是简单混淆)
  □ 核心代码被加密,运行时才解密执行

处理思路:
  1. 运行时 dump(最有效):
     Frida/调试器断在解密后 → dump 内存中的 code object
     或修改运行时(hook marshal.loads)dump 解密结果
  2. 静态: 找到解密密钥 → 手动解密(较难)
  3. 工具: PyArmor 相关的社区脱壳工具(更新快,按版本搜)
  4. 行为级: 动态观察(第 42 章方法)当兜底

78.7 实战工作流(端到端)

目标: 分析一个 PyInstaller 打包的恶意软件

1. 识别: file/strings → PyInstaller 特征
2. 解包: pyinstxtractor → 提取主脚本 pyc
3. 反编译: pycdc/uncompyle6 → 还原源码
4. 分析: 源码直接读(找 C2/持久化/窃取逻辑)
5. 混淆? Pyarmor → 运行时 dump
6. 动态验证: 沙箱行为对照源码
7. 产出: 源码级理解 + IOC

78.8 检查清单

□ 形态识别(源码/pyc/PyInstaller/Pyarmor)
□ pyc 版本识别(magic)
□ 工具反编译(uncompyle6/pycdc)
□ 失败 → 手动 dis 分析
□ PyInstaller 解包(pyinstxtractor)
□ Pyarmor → 运行时 dump
□ 嵌套/闭包 code object 完整提取
□ 常量表/字符串提取(co_consts)
□ 报告: 还原的源码+行为

动手练习

  1. 写一个 Python 程序编译成 pyc,用 uncompyle6 和 pycdc 反编译,对比还原质量。
  2. 用 PyInstaller 打包一个测试程序,pyinstxtractor 解包并还原源码。
  3. 用 dis 手动分析一个含 XOR 校验的 pyc,从字节码反推源码逻辑。
  4. 尝试对 Pyarmor 混淆的测试样本做运行时 dump(隔离环境)。

深入阅读

  • 仓库:skills/reverse-engineering/languages.md(Python 字节码章节:opcode 重映射/Pyarmor 静态解包/版本专项)
  • 仓库:skills/reverse-engineering/tools.md(Python 字节码/PyInstaller 章节)
  • 仓库:skills/reverse-engineering/patterns*.md(Python 相关 CTF 模式)
  • 仓库:skills/malware-analysis/SKILL.md(Python 恶意软件分析衔接)