ARTICLE · 989922
EXE 源码还原:PyInstaller → 字节码反编译
源码丢了只剩 EXE?
把它从黑盒还原成源码
的 5 个关键动作
PyInstaller · Python 3.13 · 字节码反编译

楼兰的AI实战笔记
3 Parts + Conclusion
滑动
PART 01
特征侦测
先看它是什么壳
PART 02
环境对齐
版本必须一模一样
PART 03
字节码反编译
撞上 Python 3.13
PART ///
写在最后
三条经验
一个跑了多年的内部工具,只剩一个 137MB 的 EXE 在车间传。双击要等它在后台慢慢解压,参数写死在二进制里,没人知道它到底往哪儿发了数据。
这不是假设。这次要处理的,是一个 Moldflow 报表自动生成工具:137MB,集成了 Autodesk Moldflow SDK 和 28 个定制 VBS 宏,源码早在人员更迭和电脑迁移中丢了,只剩这个打包后的可执行文件在科室间流传。
我把它的源码完整还原出来了——包括主程序 Rheology.py、1700 多个依赖模块、28 个宏脚本,以及那个藏在里面的、谁也没料到的小动作。
这篇文章把整套方法拆给你:侦测 → 对齐 → 提取 → 还原 → 整理,五步闭环,每一步讲清楚为什么这么做、坑在哪。

01
PART
特征侦测:先看它是什么壳
BINARY SIGNATURE
拿到一个 137MB 的二进制文件,第一反应不应该是“怎么逆”,而是“它是什么”。
不同打包器打出来的包,结构天差地别:PyInstaller 是自解压 + PYZ 归档,Nuitka 直接编译成 C,.NET 走 IL 中间语言。猜错方向,后面全白干。
做法很轻,不需要上重型工具:写个几十行的 Python 脚本,按分片读二进制,搜特征字符串就行。
# 简化示意:分片扫描特征指纹
SIGS = [b"PyInstaller", b"_MEIPASS", b"python"]
with open("tool.exe", "rb") as f:
while chunk := f.read(1024 * 1024):
for s in SIGS:
if s in chunk:
print("hit:", s)
结果很干脆:PyInstaller、_MEIPASS、python 三个指纹全中。结论明确——这是 PyInstaller 打包的自解压 Python 应用,走的是“外壳 + PYZ 归档”的路子,有成熟的提取路径。
关键认知
这一步的价值是:把“逆向”降级成“解包”。
02
PART
环境对齐:版本必须一模一样
RUNTIME ALIGNMENT
这是整条链路里最容易翻车、也最少被提到的一步。
PyInstaller 提取器在解析 PYZ 包时,要靠 Python 自己的 marshal 反序列化机制去读 .pyc 字节码对象。而字节码格式是跟解释器版本绑定的。你用 Python 3.11 去解一个 3.13 打的包,marshal 解析直接报错,提取器会静默跳过那批核心依赖——表面上“解包成功”,实际上主程序根本没出来。
排查这类问题的成本极高,因为报错信息通常指向完全无关的模块。
正确做法是先把版本号锁死,再动手:识别出包体是 Python 3.13 编译后,去下一个官方的 Windows 嵌入式版(embeddable package),绿色、免安装、独立目录,跟本机环境完全隔离。

前置条件
解包用的 Python,必须和打包用的 Python 逐位对齐。
03
PART
封包提取:剥掉自解压壳
ARCHIVE EXTRACTION
环境对齐之后,这一步反而是最省事的。
用 pyinstxtractor.py 在那个嵌入式 Python 3.13 环境下跑一遍,它会解析 EXE 尾部的 CArchive 结构(自解压壳),把里面的内容一股脑倒出来:主入口的 .pyc、核心依赖包 PYZ.pyz、以及 1700 多个依赖模块,包括完整的 Autodesk Moldflow SDK。
CMDpython313\python.exe pyinstxtractor.py tool.exe
到这一步,你手上已经有全部资产了——只是主程序还是字节码,读不了。
04
PART
字节码反编译:撞上 Python 3.13 的墙
BYTECODE DECOMPILATION
解压出来的主入口是 Rheology.pyc,Python 3.13 字节码。
常规路径在这里断了。Python 3.13 换了全新的虚拟机指令集和操作码(比如 MAKE_FUNCTION 的处理方式),主流的 uncompyle6 已经停止跟进新版本,pycdc 的稳定版也扛不住,一跑就报错中断。
这不是配置问题,是工具链的代差。
破局的思路是:别死磕一个反编译器,去找专门追新版 Python 的那个。
最终用的是 depyo——一个纯 Python 实现的反编译器,维护目标直接覆盖 Python 3.13 到 3.15。纯 Python 意味着没有编译依赖,装上就能跑,也更容易看懂它报的错。
结果是干净无损的 Rheology.py,结构完整、变量名保留、逻辑清晰。
选型原则
工具链选型上,“最新”比“最主流”重要。
05
PART
资产收纳与净化:别把垃圾留给下一个人
CLEANUP & ORGANIZE
解包出来的东西是散的、脏的:核心 Python 主程序、截图小工具、Autodesk 类库、28 个 VBS 宏,全堆在自解压根目录里,和一堆中间产物混在一起。
收纳:把真正有价值的源码资产挑出来,统一放进 src/ 目录,按类型归置。
净化:删掉所有中间产物——近 200MB 的解压缓存、临时打包工具、嵌入式运行环境。
这一步看着琐碎,但它决定了还原出来的东西能不能被称为“项目”.一个塞满 200MB 垃圾文件的目录,没人愿意接手。
06
PART
那,为什么非得这么干?
WHY REVERSE
方法讲完了,聊聊动机。能促使人去逆一个 EXE 的,通常是这三个原因。
源码真丢了
人员更迭、离职交接、电脑迁移,自动化脚本和宏没进 Git,最后只在车间留了个 EXE。这时候逆向不是黑客行为,是找回公司数字化知识产权的唯一途径。
安全审计
黑盒 EXE 用户根本没法判断它安不安全。这次反编译出来就发现,工具内部会通过 curl.exe 向一个外部 Google Forms 异步提交用户输入的信息——Site、Project type、STR code。

功能本身也许无害,但在严格的信息安全合规管理下,这种未申报的数据外发是必须被识别和管控的。不还原源码,你永远不知道它背着你做了什么。
硬编码改不动
原工具把符合性报表的压力判定阈值写死成了 80 MPa(代码里就是一句 Pressure < 80)。等材料换了、客户标准改成 100 MPa 或 60 MPa,二进制 EXE 你没法改——要么找原作者,要么还原源码改阈值。
07
PART
还原之后,拿到的是什么
THE PAYOFF
挽回高价值资产
源码可移植、可优化、可继承
消除黑盒开销
启动更快、不堆临时文件
纳入版本控制
.py .vbs 进 Git 协作
第一,高价值的知识资产回来了.28 个 VBS 宏不是代码,是前人几百上千小时积累下来的流道分析、保压、翘曲变形评估经验。源码在手,这些经验就能被移植、优化、长进下一代工具里。
第二,黑盒开销没了.137MB 的 EXE 每次双击都要后台解压释放一遍,慢,还往 Temp 目录堆文件。还原成纯 Python 直接跑,启动和运行效率提升数倍,临时文件隐患一并消失。
第三,终于能进 Git 了..py 脚本、.vbs 宏、moldflow 库,全部是文本,可以提交、分支、协作、Code Review。
一个二进制文件只能被使用,一份源码才能被继承。
///
LAST
写在最后
CLOSING
整套流程复盘下来,真正卡人的不是某条命令,而是三件事:先认壳再动手、版本严格对齐、工具链追新不追热。
如果你手上也有一个“没人敢动、也没人改得了”的 EXE,它可能没你想的那么硬。
源码不是消失了,只是被包了一层壳。
我是 楼兰的AI实战笔记,探索AI前沿技术,分享实战经验,让智能落地每一步。
如果今天这篇对你有启发,欢迎点赞、转发,我们下篇见。
还没关注?点击文末「楼兰的AI实战笔记」头像旁的「关注」按钮,即可第一时间收到更新。
THANKS FOR READING