拿到一个 Nuitka 打包的项目,目录结构和普通 Python 工程没什么两样,只是所有 .py 都变成了 .pyd。直觉上这是"编译没了",但实际拆开看,Nuitka 并不会把 Python 字节码完全抹掉——它只是换了一种存放方式。
先说结论
Nuitka 默认模式(standalone / onefile)下,模块常量表、字符串、函数名、docstring 大都以明文或简单编码形式留在二进制里。它不是加密,只是混淆。真正做加密的方案,通常是在其外面再包一层 loader。
判断顺序:先看有没有壳,再看常量表能不能直接提取,最后才考虑动态调试。顺序反了会浪费大量时间。
文件头里藏着什么
把 .pyd 拖进十六进制编辑器,前 2 字节是 MZ,标准 PE。真正的信息在 .rdata 段。
import pefile
pe = pefile.PE("module.pyd")
for sec in pe.sections:
name = sec.Name.rstrip(b"\x00").decode(errors="replace")
print(f"{name:10} vaddr={sec.VirtualAddress:#x} "
f"rawsize={sec.SizeOfRawData:#x}")
重点看 .rdata 和 .data 的大小比例。如果 .rdata 异常大(几 MB),基本可以确定常量都堆在那儿。
关于 IV 与密钥
如果 loader 用了 AES,密钥和 IV 通常会以两种形式出现:
- 硬编码在
.rdata,长度刚好 32 / 16 字节 - 由某个常量字符串经过一次哈希派生
后者更难找,但特征也明显——一定会引用 SHA-256 或 MD5 的常量表。
三种方案的对比
| 方案 | 破解难度 | 典型特征 |
|---|---|---|
| Nuitka 原生 | 低 | 常量明文、符号可读 |
| 外层 AES loader | 中 | 文件头 IV、密文段固定对齐 |
| VM 保护 | 高 | 大量间接跳转、无标准函数序言 |
下一步做什么
静态能看到的东西有限。真正跑起来之后,用调试器在 PyImport_ExecCodeModule 下断点,能直接拿到解码后的 code object——这比在二进制里翻字符串高效得多。
整个过程我保持在本地、离线、只针对有授权的样本。这一点没有讨论空间。