夜雨聆风学习资料网

ARTICLE · 989922

EXE 源码还原:PyInstaller → 字节码反编译

EXE 源码还原:PyInstaller → 字节码反编译
TUTORIAL · 案例实战2026.08

源码丢了只剩 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 脚本,按分片读二进制,搜特征字符串就行。

...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 宏,全堆在自解压根目录里,和一堆中间产物混在一起。

1

收纳:把真正有价值的源码资产挑出来,统一放进 src/ 目录,按类型归置。

2

净化:删掉所有中间产物——近 200MB 的解压缓存、临时打包工具、嵌入式运行环境。

这一步看着琐碎,但它决定了还原出来的东西能不能被称为“项目”.一个塞满 200MB 垃圾文件的目录,没人愿意接手。

06

PART

那,为什么非得这么干?

WHY REVERSE

方法讲完了,聊聊动机。能促使人去逆一个 EXE 的,通常是这三个原因。

CASE 01

源码真丢了

人员更迭、离职交接、电脑迁移,自动化脚本和宏没进 Git,最后只在车间留了个 EXE。这时候逆向不是黑客行为,是找回公司数字化知识产权的唯一途径。

CASE 02

安全审计

黑盒 EXE 用户根本没法判断它安不安全。这次反编译出来就发现,工具内部会通过 curl.exe 向一个外部 Google Forms 异步提交用户输入的信息——Site、Project type、STR code。

功能本身也许无害,但在严格的信息安全合规管理下,这种未申报的数据外发是必须被识别和管控的。不还原源码,你永远不知道它背着你做了什么。

CASE 03

硬编码改不动

原工具把符合性报表的压力判定阈值写死成了 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

相关学习资料