剑庭安
  • 首页
  • 站长工具
  • 自助开通会员
  • 其他资源
  • SEO工具
  • 站群源码
  • 泛程序
  • 蜘蛛模板
  • 蜘蛛池
  • 外链工具
  • 搜索
  • 登录
  • 注册
首页 分类列表 其他资源 【原创】PyInstaller 魔改壳从零还原——Git…
【原创】PyInstaller 魔改壳从零还原——GitHub/Gitee 外链发布 媒体助手破解字节码级逆向实录 其他资源 点击放大
作者:admin 发布:2026-09-03

【原创】PyInstaller 魔改壳从零还原——GitHub/Gitee 外链发布 媒体助手破解字节码级逆向实录

## 一、故事背景(废话不多说,先讲个真实的痛) 事情是这样的:朋友有一套自己开发的桌面软件——**「外链发布桌面版 / GEO 媒体助手」**,做外链发布工具用的(后面细说功能)。软件是用 Python + Tkinter 写的,当年用 PyInstaller 打成一个 3…
免费下载 0 条讨论
免费资源
登录后即可下载本页全部附件。
登录后查看
会员方案
169浏览量
0讨论
0下载链接
暂无附件资源状态
内容可验证
更新 2026-09-03
人工审核发布
评论区答疑

## 一、故事背景(废话不多说,先讲个真实的痛)

事情是这样的:朋友有一套自己开发的桌面软件——**「外链发布桌面版 / GEO 媒体助手」**,做外链发布工具用的(后面细说功能)。软件是用 Python + Tkinter 写的,当年用 PyInstaller 打成一个 39MB 的单文件 exe 分发。

 

这时候需要改软件的信息以及去除网络卡密验证:

1. 启动时那个授权验证窗口想直接跳过,进主界面。

没有源码,怎么改?——只能从 exe 反向来。于是就有了这篇实录。

**本文不是简单的"脱壳改资源",而是:自定义归档格式还原 + marshal 反序列化 + Python 3.8 字节码级注入 + 跳转重定位 + 代码对象重建 + 归档重构,全程无源码、无调试符号、无 IDA,纯二进制手术。**

---

## 二、软件简介

**GEO 媒体助手(外链发布桌面版)** 是一个面向 SEO/推广场景的桌面工具,核心功能:

- **多平台媒体账号管理**:支持 OSChina、网易号、米游社、百家号、百度等平台的 Cookie 导入、账号分组、登录状态管理;
- **标题库/文章库批量生成**:从 TXT/MD/HTML 读取标题和文章素材,按模板批量生成多篇带外链的文章;
- **外链构建**:支持域名列表随机、Markdown 链接/HTML 链接/可点击 URL 等多种外链格式,可控制链接位置(底部/每篇前/每篇后);
- **自动发布**:通过内置 Playwright 浏览器驱动登录各平台账号自动发布,支持发布间隔、失败重试、发布日志;
- **AI 辅助**:接大模型生成文章、自动配外链;
- **GitHub/Gitee 仓库托管**:账号托管在自有仓库,走 API 认证。

界面是 Tkinter 做的(ttk 控件 + Canvas 滚动区),打包内置了 Playwright 驱动(连 node.exe 都塞进去了,所以体积 39MB)。

---

## 三、逆向目标

| 目标 | 内容 |
|---|---|
| ① | 替换侧边栏推广文字 |
| ② | 文字点击跳转官网 `http://jta.mobi` |
| ③ | 主窗口标题追加官网 |
| ④ | 跳过启动授权验证窗口,直接进主界面 |

一句话:**在没有任何源码的前提下,改掉 exe 里的界面文案、行为逻辑、窗口标题,还要让它正常跑。**

---

## 四、环境与工具

- 目标:`外链发布桌面版_GEO媒体助手_标题库每标题多链接版.exe`(39,370,079 字节)
- 系统:Windows x64
- 工具:Python 3.8.6(自带 marshal/dis/struct/zlib,全程没装任何第三方逆向库)、PowerShell、十六进制编辑器
- 关键点:`zlib` 解归档、`marshal` 反序列化代码对象、`dis` 反汇编字节码、`types.CodeType` 重建代码对象

---

## 五、分析过程

### 5.1 查壳与框架识别

先看 PE 头:

```
MZ...PE..  Machine: 0x8664 (x64)  Sections: 7
.text  .rdata  .data  .pdata  .fptable  .rsrc  .reloc
```

`.fptable` 这个节很少见,说明不是常规编译器产物。再搜字符串特征:

```
python38.dll     -> Python 3.8
PyInstaller      -> PyInstaller 打包
pyi-             -> PyInstaller 运行时钩子
tk86t.dll        -> Tkinter 图形库
playwright\node.exe -> 内置浏览器自动化驱动
```

**结论:PyInstaller onefile 打包的 Python 3.8 + Tkinter 程序。** 但注意——后面会发现它绝不是标准 PyInstaller 壳,否则就没这篇文章了。

### 5.2 定位 PyInstaller 归档

PyInstaller onefile 结构:`[启动器 bootloader][CArchive 归档][88 字节 cookie]`,cookie 里存归档关键参数。

在文件尾部找到魔数 `MEI 0x0c 0x0b 0x0a 0x0b 0x0e`,解析 cookie:

```
lengthofPackage = 39,068,511   -> 归档起点 = 文件尾 - 该值 = 0x499A8 (301,480)
toc             = 38,987,303   -> TOC 相对偏移
toclen          = 81,120       -> TOC 长度
pyvers          = 308          -> Python 3.8
pylibname       = python38.dll
```

按标准格式去读 TOC(64 字节文件名 + 4 偏移 + 4 长度 + 4 压缩标志 = 76 字节一条),结果**全是乱码**。标准格式在这里完全失效——**这就是魔改点之一**。

### 5.3 破解自定义 TOC 结构(本文第一个硬骨头)

对 TOC 区逐字节分析后,发现真实结构:

```
每个条目 = [16 字节头部] [1 字节标志] [1 字节类型] [名称\0](整体 16 字节对齐)
头部 16 字节(大端):
  f1  : 未知标志(32/48/64,保留原样)
  off : 数据在数据区的偏移
  ln  : 压缩后长度
  f4  : 解压后长度
类型字符:m=模块  s=源码  b=二进制  z=PYZ归档  o=特殊条目
```

验证:第一条 `mstruct`,off=0,ln=219,f4=295。取数据区第 0 字节开始 219 字节,`zlib.decompress` 后正好 295 字节,是 `struct` 模块的 marshal 数据——**结构验证通过**。

这里还有个坑:名称前有个标志字节,部分条目标志是 `0x00`(如 `pyi-contents-directory _internal`、`PYZ.pyz`),找名称结束符必须**跳过标志字节**再搜 `\0`,否则会解析错位。

最终解析出 **1296 个条目**,恰好铺满 81,120 字节 TOC 区,一个不多一个不少——完美闭环。

### 5.4 数据区解压

数据区基准偏移是 `归档起点 + 88`(不是归档起点!)。

```
data_base = 301,480 + 88 = 301,568
```

验证:`VCRUNTIME140.dll` 条目 off=87,692 → `data_base + off` 处是 zlib 流,解压后 101,672 字节,头部 `MZ 90 00`——标准的 PE 文件头,**DLL 完整还原**。

`desktop_app` 条目(类型 s,源码):off=17,244,压缩 70,448 字节,解压后 **171,624 字节**,首字节 `0xE3`——这是 marshal 的 `FLAG_REF | TYPE_CODE`,即一个**代码对象**(整个 `desktop_app.py` 编译后的顶层代码对象)。

### 5.5 PYZ 模块流解析

归档里还有一个 1.7MB 的 PYZ 归档(条目名 `PYZ.pyz`),结构:

```
17 字节头('PYZ\0' + pyc 魔数 + 时间戳 + 长度)
+ 260 个独立的 zlib 模块流(每个流解压后是一个模块的 marshal 代码对象)
+ 尾部一段未压缩 marshal
```

逐流解压,模块名一一还原:`__future__`、`app.py`、`playwright`、`asyncio`……共 260 个模块。其中 `app.py` 是业务层(HTTP 服务 + GitHub/Gitee API + 百度推送),主界面逻辑全在 `desktop_app` 里。

### 5.6 反序列化主程序代码对象

`marshal.loads()` 直接还原整个 `desktop_app.py` 的代码对象树,共 **395 个嵌套代码对象**(函数/类)。遍历 `co_consts` 找目标字符串:

```
build_media_assistant_tab 函数中发现:
'定制平台发布\n联系\nTG: @yzlseo'   <- 要改的文字
```

对应的字节码(十六进制实测):

```
偏移 252  LOAD_CONST 32  '定制平台发布\n联系\nTG: @yzlseo'
偏移 270  LOAD_CONST 40  ('text','foreground',...,'borderwidth')
偏移 272  CALL_FUNCTION_KW 10      <- 创建 ttk.Label
偏移 274  LOAD_ATTR 5   (pack)
...
```

这就是侧边栏那个红字提示框的创建代码。修改点锁定。

### 5.7 文字替换

直接替换 `co_consts[32]`:

```
旧:'定制平台发布\n联系\nTG: @yzlseo'
新:'软件协议定制,高端复杂开发,逆向开发\n官网jta.mobi'
```

常量是**按索引引用**的,原地换值不影响任何 `LOAD_CONST` 索引,零风险。

### 5.8 字节码级注入:给文字加点击事件(最硬核的部分)

需求:点击那段文字 → 打开 `http://jta.mobi`。

原始流程:`ttk.Label(...).pack(...)` 链式调用,Label 对象用完即弃,**没有保存引用**。要绑定点击事件,必须:

1. 用 `STORE_FAST` 把 Label 存进局部变量;
2. `bind('<Button-1>', lambda e: webbrowser.open('http://jta.mobi'))`;
3. 再 `pack`。

在偏移 274 处插入 20 字节:

```
偏移 274  STORE_FAST  27     ; promo_label = Label(...)
偏移 276  LOAD_FAST   27     ; promo_label
偏移 278  LOAD_METHOD 16     ; .bind
偏移 280  LOAD_CONST  170    ; '<Button-1>'
偏移 282  LOAD_CONST  171    ; <lambda 代码对象>
偏移 284  LOAD_CONST  172    ; lambda 限定名(坑!见下)
偏移 286  MAKE_FUNCTION 0    ; 生成 lambda 函数
偏移 288  CALL_METHOD 2      ; bind('<Button-1>', func)
偏移 290  POP_TOP
偏移 292  LOAD_FAST   27     ; promo_label(继续原 pack 链)
```

**这 20 字节踩了三个 Python 3.8 字节码的暗坑**:

**坑 1:3.8 是 2 字节指令,不是 3 字节。** 一开始按 3.11 的 wordcode 思路写成 `[opcode][arg高][arg低]`,结果解释器直接 `SystemError: unknown opcode` 段错误。3.8 是 `[opcode][arg字节]`,大于 255 用 `EXTENDED_ARG` 前缀,跟 3.11+ 完全不是一个格式。

**坑 2:`POP_TOP` 这类无参指令也是 2 字节**,要带 `0x00` 填充,否则后面全部错位 1 字节——又是一个安静的内存崩溃。

**坑 3:`MAKE_FUNCTION 0` 要弹两个值**:代码对象 + 限定名(qualname)。编译器生成 lambda 时是:

```
LOAD_CONST <lambda code>
LOAD_CONST 'xxx.<locals>.<lambda>'   <- 限定名
MAKE_FUNCTION 0
```

少了限定名那行,`MAKE_FUNCTION` 会把 `'<Button-1>'` 当限定名弹掉,随后 `CALL_METHOD` 栈下溢——直接 `Access Violation`。

**坑 4(跳转重定位):** 插入 20 字节后,后面所有代码偏移 +20,所有跳转目标必须重算。这里又一个 3.8 语义坑:

```
绝对跳转(arg 就是目标):JUMP_ABSOLUTE / POP_JUMP_IF_FALSE / POP_JUMP_IF_TRUE ...
相对跳转(arg 是相对距离):FOR_ITER / JUMP_FORWARD / SETUP_FINALLY ...
```

`FOR_ITER` 在 3.8 是**相对跳转**(目标 = 指令偏移 + 2 + arg),在 3.10 才改成相对……不对,3.8 就是相对。一开始把 `FOR_ITER` 当绝对跳转 +20,循环直接跳飞。相对跳转有个特性:**指令和目标是同步平移的,arg 其实不用改**——只有跨过插入点的才需要重算。另外带 `EXTENDED_ARG` 的跳转(如 `EXTENDED_ARG 5` + `JUMP_ABSOLUTE`)在重编码时还要小心别把前缀重复编码。

**坑 5(lnotab):** `co_lnotab` 里的行号增量是**有符号字节**(`0xF6` = -10),按无符号解会把行号变成天文数字。重编码时第一个条目的行增量要相对 `co_firstlineno`。

全部处理完,`dis` 验证:**0 个坏跳转目标**,插入区字节码与编译器原生输出完全对齐。

### 5.9 main() 逻辑改写:跳过授权验证窗口

反汇编顶层 `main()`:

```
4476  license_window = LicenseWindow()
4477  license_window.mainloop()
4478  if not license_window.authorized: return
4480  app = DesktopApp()
4481  app.mainloop()
```

授权校验只在 `LicenseWindow`(验证窗口)和 `main()` 里,`DesktopApp.__init__` 没有任何授权检查。所以直接重写 `main()` 为:

```
def main():
    app = DesktopApp()
    app.mainloop()
```

新代码对象(9 条指令):

```
LOAD_GLOBAL 0   DesktopApp
CALL_FUNCTION 0
STORE_FAST  0   app
LOAD_FAST   0   app
LOAD_METHOD 1   mainloop
CALL_METHOD 0
POP_TOP
LOAD_CONST  0   None
RETURN_VALUE
```

`co_names` 里彻底移除 `LicenseWindow`——启动直接进主界面。

### 5.10 窗口标题常量替换

`DesktopApp.__init__` 里:

```
偏移 12  LOAD_METHOD 2  (title)
偏移 14  LOAD_CONST  1  '外链发布 / GEO 媒体助手'
```

常量原地替换为 `'外链发布 / GEO 媒体助手 - 官网 jta.mobi'`。

### 5.11 归档重构

改动涉及 `desktop_app` 条目数据长度变化(压缩后 71,523 vs 70,448 字节),需要**整体重建归档**:

1. 启动器(0 ~ 301,480)**逐字节原样保留**;
2. 归档头 88 字节原样保留;
3. 按 TOC 顺序重写全部 1296 个条目的数据(只有 desktop_app 变了,其余 1295 个条目的 zlib 流原样搬运);
4. 重写 TOC:条目名/标志/类型/f1/f4 全部不变,仅更新 `off`(数据偏移)和 `ln`(压缩长度);
5. 重写 cookie:`lengthofPackage`、`toc`、`toclen` 按新布局更新;
6. 特殊条目处理:`pyi-contents-directory _internal`(零长度标记)的 `off` 保持与 `PYZ.pyz` 一致。

重建后用同一套解析器**回读校验**:1296 条、名称/标志/f1 全等、仅 desktop_app 的 f4 从 171,624 变为 171,903、全部条目可解压、`VCRUNTIME140.dll` 仍是 MZ 头——**归档级一致性校验通过**。

---

## 六、效果验证

### 6.1 功能级验证(真实 Tk 环境跑起来)

把改版模块在本地 Python 3.8 + 真实 Tk 环境执行:

```
模块加载成功
界面构建成功
找到推广文字:'软件协议定制,高端复杂开发,逆向开发\n官网jta.mobi'  ✓
<Button-1> 绑定存在  ✓
模拟点击 -> 捕获到 webbrowser.open('http://jta.mobi')  ✓
```

### 6.2 实机验证

双击改版 exe,进程窗口标题实时显示:

```
外链发布 / GEO 媒体助手 - 官网 jta.mobi
```

**没有授权验证窗口,直接进主界面。** 侧边栏新文字、点击跳转、新标题全部生效。

---

## 七、总结与思考

1. **PyInstaller 壳 ≠ 标准壳。** 这个 exe 的 CArchive TOC 是自定义格式(16 字节字段 + 标志 + 类型 + 16 对齐),标准 pyinstxtractor 直接失效。逆向自定义打包格式,靠的是对 PyInstaller 归档原理的理解 + 逐字节比对 + 数据自洽性验证(偏移连续性、zlib 长度吻合、PE 头还原)。

2. **Python 字节码手术比改 PE 还讲究。** 3.8 与 3.11 的指令编码、绝对/相对跳转语义、无参指令填充、`MAKE_FUNCTION` 的 qualname、lnotab 有符号增量——每个细节错了都是段错误。**写字节码注入器 = 写一个小型汇编器。**

3. **代码对象是可逆的。** Python 的 `marshal` 格式在 3.8 下完整保留了 `co_consts/co_names/co_code/co_lnotab`,只要版本对得上,源码结构(函数、类、常量)基本能无损还原——这比反汇编 C++ 的伪代码友好太多了。

4. **改自己的软件,比"破解"更有意义。** 这套流程对**恢复自有软件源码能力**的场景极其实用:源码丢了?只要 exe 还在,逻辑可读、可改、可重建。

5. 整个流程**零第三方工具**,一个 Python 3.8 解释器 + 手写的解析/重建脚本搞定。逆向的尽头,是扎实的格式理解和耐心。

---

*全文完。有疑问的坛友欢迎交流,也接同类"源码丢失恢复/魔改"的需求:软件协议定制、高端复杂开发、逆向开发,官网 jta.mobi。*




软件下载:1039545483

资源标题【原创】PyInstaller 魔改壳从零还原——GitHub/Gitee 外链发布 媒体助手破解字节码级逆向实录
所属分类其他资源
发布作者admin
发布时间2026-09-03 09:19:18
下载权限 免费下载
附件数量0 个下载链接
浏览次数169
讨论数量0
主题状态正常讨论中

本资源免费,登录后即可在本页「下载链接」区域获取全部附件。

请在评论区留言说明具体链接,或联系作者补档。楼主更新后本页会同步最新的下载地址。

请先阅读正文中的授权说明;未注明的默认仅限站内学习交流,禁止直接搬运到其他站点二次售卖。
还没有讨论,快来抢沙发~
登录 后才能参与讨论
下载链接
暂无下载链接
同分类推荐
暂无同类资源
免费登录即可下载
查看会员
放大预览

© 2026 剑庭安 - 网站开发定制 SEO 优化蜘蛛池代做 在线看手相算命. 保留所有权利。

Powered by 酷像SEOv3.0