从一个 .pyd 说起:Nuitka 打包之后还剩什么

从一个 .pyd 说起:Nuitka 打包之后还剩什么

拿到一个 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——这比在二进制里翻字符串高效得多。

整个过程我保持在本地、离线、只针对有授权的样本。这一点没有讨论空间。

收到2条评论
avatar
wood 21 小时前
同意"不是加密只是混淆"这个判断。判断顺序那段说得很对,先看壳能省很多事。
avatar
K 1 天前
代码块里那段 pefile 的遍历很实用,之前一直在手动翻字节。