29 Commits
Author SHA1 Message Date
牟不懂哎 7dc5604958 Update README.md 2026-07-10 10:19:10 +08:00
牟不懂哎 6a52cf87a2 Update README.md 2026-07-10 10:18:27 +08:00
牟不懂哎 676f1836c1 Update README.md 2026-06-22 10:25:31 +08:00
牟不懂哎 f9ec893a1e Update README.md 2026-06-22 10:24:57 +08:00
牟不懂哎 8a431e61dd Update README.md 2026-06-22 10:24:22 +08:00
牟不懂哎 c348c12900 Update README.md 2026-06-18 15:42:27 +08:00
Spade A e6d2706e2b Update README.md 2026-06-07 11:58:03 +08:00
牟不懂哎 a31a7d4d03 Update README.md 2026-06-01 16:40:59 +08:00
牟不懂哎 2f77fabd76 Add files via upload 2026-06-01 16:37:03 +08:00
牟不懂哎 43abf4fe8c v1.1.0 2026-06-01 16:35:53 +08:00
牟不懂哎 c71c22f7d1 v1.1.0 2026-06-01 16:34:09 +08:00
牟不懂哎 970c0bad7d v1.0.9 2026-06-01 11:34:56 +08:00
牟不懂哎 69983bfbf0 v 2026-06-01 11:34:36 +08:00
牟不懂哎 e419752a5c v 2026-06-01 11:33:37 +08:00
牟不懂哎 68598ed26d v1.0.9 2026-06-01 10:41:51 +08:00
牟不懂哎 e7d33319c8 Merge pull request #15 from XingXiaoWu/main
feat: addresses.19823.json
2026-05-24 21:05:02 +08:00
Xing.Wu 6a94b31be7 fix: 修复指针链解析的问题,并从WMPFDebugger同步大佬们的解析 2026-05-14 23:16:08 +08:00
Xing.Wu c7653cc698 feat: addresses.19823.json 2026-05-14 22:50:00 +08:00
牟不懂哎 02b58948a0 v1.0.8 2026-05-04 23:10:40 +08:00
牟不懂哎 797004582f v1.0.8 2026-05-04 23:07:44 +08:00
牟不懂哎 6e4a3b2464 Merge branch 'main' of https://github.com/Spade-sec/First 2026-05-04 23:05:28 +08:00
牟不懂哎 c2f668b9db v1.0.8 2026-05-04 23:03:28 +08:00
牟不懂哎 2940e15105 Merge pull request #11 from XiangXtreme/main
新增H5服务号支持调试功能,uv管理项目等功能
2026-05-01 20:28:09 +08:00
XiangXtreme 884d882eb7 init 2026-04-30 13:09:26 +08:00
牟不懂哎 b8bfac4641 v1.0.7 2026-04-22 19:40:29 +08:00
牟不懂哎 4a64e54935 v1.0.6 2026-04-19 15:11:59 +08:00
牟不懂哎 285b893d07 v1.0.5 2026-04-17 13:14:32 +08:00
牟不懂哎 136a312b6c v1.0.4 2026-04-16 18:57:31 +08:00
牟不懂哎 572eca8eaa Update README.md 2026-04-13 16:54:15 +08:00
83 changed files with 5092 additions and 566 deletions
+4
View File
@@ -0,0 +1,4 @@
.venv/
.deps_installed
__pycache__/
*.py[cod]
+70 -127
View File
@@ -1,157 +1,100 @@
# First # First
> 微信小程序调试工具 —— 基于 Frida + CDP 代理,支持 GUI 与 CLI 双模式 微信小程序安全调试工具 — Frida + CDP,支持 macOS / Windows
---
本项目仅支持windows,macos用户使用pd虚拟机即可
## 截图预览
### 主界面 / Control Panel > 自v1.1.0版本后开始闭源,往后版本更新只上传编译好的版本,下载最新版请去release下载。
![zhuyem](https://s1.galgame.fun/imgb/u0/20260408_69d655f49ca7e.png) ## 免责声明
### 路由导航 本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
![luyou](https://s1.galgame.fun/imgb/u0/20260408_69d6560ca1a39.png)
### 云函数分析 ## 功能
![QQ20260402-192623](https://s1.galgame.fun/imgb/u0/20260402_69ce527ca5480.png)
### 调试开关(JSRPC 与调用 wx.cloud) - Frida 注入微信,CDP 代理桥接 Chrome DevTools
![调试](https://s1.galgame.fun/imgb/u0/20260408_69d6564195009.png) - 云函数动态捕获(Hook wx.cloud.callFunction,参数修改重放)
- wx.* API 捕获(login / request / getUserProfile 等)
- 路由枚举与导航,跳转守卫检测
- wxapkg 解密解包 + 敏感信息扫描
- MCP Server 集成(AI 辅助分析)
- 偏移量 / Skills 在线更新
### 安全扫描 ## 截图
待优化 ### 主页
![11071](https://s1.galgame.fun/imgb/u55/20260601_6a1d43338429a.png)
### 云函数捕获
![11069](https://s1.galgame.fun/imgb/u55/20260601_6a1d4333632a4.png)
### wx.api捕获
![11072](https://s1.galgame.fun/imgb/u55/20260601_6a1d43338219d.png)
### 反编译文件编辑搜索
![11073](https://s1.galgame.fun/imgb/u55/20260601_6a1d433372934.png)
### Frist-MCP
![11070](https://s1.galgame.fun/imgb/u55/20260601_6a1d433358324.png)
![11074](https://s1.galgame.fun/imgb/u55/20260601_6a1d43367a592.png)
### 自动更新
![3915](https://s1.galgame.fun/imgb/u55/20260601_6a1d4537482b8.png)
--- ## 下载
## 功能特性 [Releases](https://github.com/Spade-sec/First/releases)
- 通过 Frida 注入微信客户端,转发小程序调试协议 | 平台 | 文件 |
- CDP 代理桥接,支持 Chrome DevTools 直接连接调试
- 路由枚举与一键跳转导航
- 云函数调用监控与参数分析
- UserScript 自动注入(支持按 URL 匹配、run-at 时机控制)
- 内置安全扫描模块,生成扫描报告
- 深色 / 浅色主题切换
- GUI(PySide6)与 CLI 双模式运行
---
## 环境要求
| 依赖 | 版本 |
|------|------| |------|------|
| Python | >= 3.10 | | macOS (Apple Silicon) | `First-ARM.dmg` |
| frida | >= 17.0.0 | | macOS (Intel) | `First-Intel.dmg` |
| websockets | >= 12.0 | | Windows | `First-Windows.zip` |
| protobuf | >= 4.0.0 |
| PySide6 | >= 6.5.0 |
安装依赖: ## 支持的微信版本
### Windows
- WMPF 版本:11581, 11633, 13331, 13341, 13487, 13639, 13655, 13871, 13909, 14161, 14199, 14315, 16133, 16203, 16389, 16467, 16771, 16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201(实时更新),19977(微信群友“执守中一”)
- 推荐微信版本:**4.1.10**
- 下载地址:[weixin/4.1.10](https://github.com/vs-olitus/wx-version/releases/tag/4.1.10-windows)
### macOS
- WMPF 版本:19978(微信群友“人杰提供”), 17078, 18152, 18788(实时更新)
- 推荐微信版本:**4.1.10**
- 下载地址:[weixin/4.1.10](https://github.com/vs-olitus/wx-version/releases/tag/4.1.10-mac)
## 快速开始
1. 打开 First
2. 点击启动 — Frida 自动注入微信
3. 在微信中打开小程序
4. 使用 DevTools / 云捕获 / 导航等功能
## macOS 注意事项
如果 Frida 注入报错,需要解除系统对进程附加的限制,任选其一:
**方案一:关闭 SIP(系统完整性保护)**
> 关闭后 Frida 才能正常注入进程。参考教程:[macOS SIP 开启关闭教程](https://cloud.tencent.com/developer/article/1496058)
**方案二:强制重签名 WeChat**
```bash ```bash
pip install -r requirements.txt sudo codesign --force --deep --sign - /Applications/WeChat.app
``` ```
--- ## 参考
## 微信版本要求
- WMPF版本支持列表:11581, 11633, 13331, 13341, 13487, 13639, 13655, 13871, 13909, 14161, 14199, 14315, 16133, 16203, 16389, 16467, 16771,
16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201
- 推荐微信版本为4.1.0.30
- 下载地址[weixin/4.1.0.30](https://github.com/vs-olitus/wx-version/releases/tag/4.1.0.30)
## 使用方法
### GUI 模式(推荐)
```bash
python gui.py
```
双击启动.bat
启动后在主界面点击 **启动调试** 即可开始调试。
### 小程序页面操作
点击启动调试前请勿打开小程序, **启动调试**打开后再次启动**小程序**即可。
### CLI 模式
```bash
# 默认端口启动
python main.py
# 自定义端口
python main.py --debug-port 9421 --cdp-port 62000
# 开启详细日志
python main.py --debug-main --debug-frida
```
### 连接 Chrome DevTools
启动后,在 Chrome 地址栏输入:
```
devtools://devtools/bundled/inspector.html?ws=127.0.0.1:62000
```
### 参数说明
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--debug-port` | `9421` | 远程调试服务端口 |
| `--cdp-port` | `62000` | CDP 代理监听端口 |
| `--debug-main` | 关闭 | 输出主进程调试日志 |
| `--debug-frida` | 关闭 | 输出 Frida 客户端日志 |
| `--scripts-dir` | `./userscripts` | UserScript 目录路径 |
| `--script` | — | 指定单个 .js 文件注入(可多次使用) |
### UserScript 注入
将 `.js` 脚本放入 `userscripts/` 目录,工具启动时自动加载并按规则注入。
也可手动指定文件:
```bash
python main.py --script ./my_hook.js --script ./another.js
```
---
## 打包为可执行文件
```bash
pyinstaller WMPFDebugger.spec
```
---
## 常见问题解决办法
1. Frida 已显示连接,但小程序端显示未连接或无法断点调试
若操作顺序无误,建议先彻底卸载微信并重启电脑(如有重要聊天记录请提前备份)。
删除路径C:\Users\用户名\AppData\Roaming\Tencent\xwechat\XPlugin\Plugins\RadiumWMPF下所有以数字命名的文件夹,再次重启电脑后,安装微信 4.1.0.30 版本。安装完成后检查上述路径,确认文件夹编号为 16389。
## 参考项目
本项目参考并学习了以下优秀工具的实现思路:
- [evi0s/WMPFDebugger](https://github.com/evi0s/WMPFDebugger) - [evi0s/WMPFDebugger](https://github.com/evi0s/WMPFDebugger)
- [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK) - [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK)
- [残笑/FindSomething](https://github.com/momosecurity/FindSomething) - [残笑/FindSomething](https://github.com/momosecurity/FindSomething)
- [进击的HACK/JSRPC与调用wx.cloud](https://mp.weixin.qq.com/s/hTlekrCPiMJCvsHYx7CAxw) - [进击的HACK / JSRPC 与调用 wx.cloud](https://mp.weixin.qq.com/s/hTlekrCPiMJCvsHYx7CAxw)
--- - [linguo2625469/WMPFDebugger-mac](https://github.com/linguo2625469/WMPFDebugger-mac)
## 致谢 ## 致谢
感谢 **0xsdeo** 师傅的大力支持与思路提供。 感谢 **0xsdeo** 师傅的大力支持与思路提供。
--- ---
## 交流群 ## 交流群
![微信图片_20260408212151_1256_3](https://s1.galgame.fun/imgb/u0/20260408_69d6567d777a9.jpg) 加不了群加我进群:
## 免责声明 <img src="https://s1.galgame.fun/imgb/u55/20260710_6a5054d733324.jpg" alt="微信群二维码" width="300" /><img src="https://s1.galgame.fun/imgb/u55/20260601_6a1d196618084.png" alt="微信二维码" width="300" />
本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
+19
View File
@@ -0,0 +1,19 @@
{
"Version": 18788,
"Arch": {
"arm64": {
"LoadStartHookOffset": "0x4F0620C",
"LoadStartHookOffset2": "0x81CEC08",
"CDPFilterHookOffset": "0x81BFC04",
"ResourceCachePolicyHookOffset": "0x4F699E8",
"StructOffset": 1376
},
"x64": {
"LoadStartHookOffset": "0x5994BA0",
"LoadStartHookOffset2": "0x91FFB90",
"CDPFilterHookOffset": "0x91F1C20",
"ResourceCachePolicyHookOffset": "0x5A066E0",
"StructOffset": 1368
}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"Version": 18152,
"Arch": {
"arm64": {
"LoadStartHookOffset": "0x4F43880",
"LoadStartHookOffset2": "0x8394F24",
"CDPFilterHookOffset": "0x8385F44",
"ResourceCachePolicyHookOffset": "0x4FA746C",
"StructOffset": 1376
},
"x64": {
"LoadStartHookOffset": "0x5971FF0",
"LoadStartHookOffset2": "0x91D58F0",
"CDPFilterHookOffset": "0x91C68D0",
"ResourceCachePolicyHookOffset": "0x59E0950",
"StructOffset": 1376
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"Version": 18788,
"Arch": {
"arm64": {
"LoadStartHookOffset": "0x4F6454C",
"LoadStartHookOffset2": "0x83BC8C8",
"CDPFilterHookOffset": "0x83AE884",
"ResourceCachePolicyHookOffset": "0x4FCAD90",
"StructOffset": 1368
},
"x64": {
"LoadStartHookOffset": "0x5994BA0",
"LoadStartHookOffset2": "0x91FFB90",
"CDPFilterHookOffset": "0x91F1C20",
"ResourceCachePolicyHookOffset": "0x5A066E0",
"StructOffset": 1368
}
}
}
@@ -2,5 +2,5 @@
"Version": 11581, "Version": 11581,
"LoadStartHookOffset": "0x28E9190", "LoadStartHookOffset": "0x28E9190",
"CDPFilterHookOffset": "0x38C4350", "CDPFilterHookOffset": "0x38C4350",
"SceneOffsets": [1208, 1160, 488] "SceneOffsets": [56, 1208, 8, 1160, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 11633, "Version": 11633,
"LoadStartHookOffset": "0x28F22A0", "LoadStartHookOffset": "0x28F22A0",
"CDPFilterHookOffset": "0x38D41E0", "CDPFilterHookOffset": "0x38D41E0",
"SceneOffsets": [1208, 1160, 488] "SceneOffsets": [56, 1208, 8, 1160, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13331, "Version": 13331,
"LoadStartHookOffset": "0x0FFC200", "LoadStartHookOffset": "0x0FFC200",
"CDPFilterHookOffset": "0x2420100", "CDPFilterHookOffset": "0x2420100",
"SceneOffsets": [1272, 1224, 488] "SceneOffsets": [56, 1272, 8, 1224, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13341, "Version": 13341,
"LoadStartHookOffset": "0x10009E0", "LoadStartHookOffset": "0x10009E0",
"CDPFilterHookOffset": "0x242E8E0", "CDPFilterHookOffset": "0x242E8E0",
"SceneOffsets": [1272, 1224, 488] "SceneOffsets": [56, 1272, 8, 1224, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13487, "Version": 13487,
"LoadStartHookOffset": "0x0FFB600", "LoadStartHookOffset": "0x0FFB600",
"CDPFilterHookOffset": "0x241FEB0", "CDPFilterHookOffset": "0x241FEB0",
"SceneOffsets": [1272, 1224, 488] "SceneOffsets": [56, 1272, 8, 1224, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13639, "Version": 13639,
"LoadStartHookOffset": "0x1000990", "LoadStartHookOffset": "0x1000990",
"CDPFilterHookOffset": "0x2424DE0", "CDPFilterHookOffset": "0x2424DE0",
"SceneOffsets": [1272, 1224, 488] "SceneOffsets": [56, 1272, 8, 1224, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13655, "Version": 13655,
"LoadStartHookOffset": "0x100F4B0", "LoadStartHookOffset": "0x100F4B0",
"CDPFilterHookOffset": "0x244A9E0", "CDPFilterHookOffset": "0x244A9E0",
"SceneOffsets": [1280, 1232, 488] "SceneOffsets": [56, 1280, 8, 1232, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13871, "Version": 13871,
"LoadStartHookOffset": "0x101F160", "LoadStartHookOffset": "0x101F160",
"CDPFilterHookOffset": "0x246FC40", "CDPFilterHookOffset": "0x246FC40",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 13909, "Version": 13909,
"LoadStartHookOffset": "0x101F0E0", "LoadStartHookOffset": "0x101F0E0",
"CDPFilterHookOffset": "0x246FDC0", "CDPFilterHookOffset": "0x246FDC0",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 14161, "Version": 14161,
"LoadStartHookOffset": "0x10246C0", "LoadStartHookOffset": "0x10246C0",
"CDPFilterHookOffset": "0x24839B0", "CDPFilterHookOffset": "0x24839B0",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 14199, "Version": 14199,
"LoadStartHookOffset": "0x10246F0", "LoadStartHookOffset": "0x10246F0",
"CDPFilterHookOffset": "0x24839E0", "CDPFilterHookOffset": "0x24839E0",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 14315, "Version": 14315,
"LoadStartHookOffset": "0x10004C0", "LoadStartHookOffset": "0x10004C0",
"CDPFilterHookOffset": "0x2424B50", "CDPFilterHookOffset": "0x2424B50",
"SceneOffsets": [1272, 1224, 488] "SceneOffsets": [56, 1272, 8, 1224, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16133, "Version": 16133,
"LoadStartHookOffset": "0x470FAD0", "LoadStartHookOffset": "0x470FAD0",
"CDPFilterHookOffset": "0x90FC7E0", "CDPFilterHookOffset": "0x90FC7E0",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16203, "Version": 16203,
"LoadStartHookOffset": "0x4710890", "LoadStartHookOffset": "0x4710890",
"CDPFilterHookOffset": "0x90FD640", "CDPFilterHookOffset": "0x90FD640",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16389, "Version": 16389,
"LoadStartHookOffset": "0x24E4830", "LoadStartHookOffset": "0x24E4830",
"CDPFilterHookOffset": "0x2E2A880", "CDPFilterHookOffset": "0x2E2A880",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16467, "Version": 16467,
"LoadStartHookOffset": "0x24E4FD0", "LoadStartHookOffset": "0x24E4FD0",
"CDPFilterHookOffset": "0x2E2CC90", "CDPFilterHookOffset": "0x2E2CC90",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16771, "Version": 16771,
"LoadStartHookOffset": "0x24E9130", "LoadStartHookOffset": "0x24E9130",
"CDPFilterHookOffset": "0x2E3C470", "CDPFilterHookOffset": "0x2E3C470",
"SceneOffsets": [1360, 1312, 488] "SceneOffsets": [56, 1360, 8, 1312, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16815, "Version": 16815,
"LoadStartHookOffset": "0x2509690", "LoadStartHookOffset": "0x2509690",
"CDPFilterHookOffset": "0x2E768D0", "CDPFilterHookOffset": "0x2E768D0",
"SceneOffsets": [1416, 1360, 488] "SceneOffsets": [56, 1416, 8, 1360, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 16965, "Version": 16965,
"LoadStartHookOffset": "0x2570220", "LoadStartHookOffset": "0x2570220",
"CDPFilterHookOffset": "0x2F844A0", "CDPFilterHookOffset": "0x2F844A0",
"SceneOffsets": [1416, 1360, 488] "SceneOffsets": [56, 1416, 8, 1360, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 17037, "Version": 17037,
"LoadStartHookOffset": "0x257D0A0", "LoadStartHookOffset": "0x257D0A0",
"CDPFilterHookOffset": "0x2FB2310", "CDPFilterHookOffset": "0x2FB2310",
"SceneOffsets": [1408, 1352, 488] "SceneOffsets": [56, 1408, 8, 1352, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 17071, "Version": 17071,
"LoadStartHookOffset": "0x258F370", "LoadStartHookOffset": "0x258F370",
"CDPFilterHookOffset": "0x2FD3080", "CDPFilterHookOffset": "0x2FD3080",
"SceneOffsets": [1408, 1352, 488] "SceneOffsets": [56, 1408, 8, 1352, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 17127, "Version": 17127,
"LoadStartHookOffset": "0x2590910", "LoadStartHookOffset": "0x2590910",
"CDPFilterHookOffset": "0x2FD4040", "CDPFilterHookOffset": "0x2FD4040",
"SceneOffsets": [1408, 1352, 488] "SceneOffsets": [56, 1408, 8, 1352, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 18055, "Version": 18055,
"LoadStartHookOffset": "0x25A1040", "LoadStartHookOffset": "0x25A1040",
"CDPFilterHookOffset": "0x30031E0", "CDPFilterHookOffset": "0x30031E0",
"SceneOffsets": [1416, 1352, 488] "SceneOffsets": [56, 1416, 8, 1352, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 18151, "Version": 18151,
"LoadStartHookOffset": "0x25A2E20", "LoadStartHookOffset": "0x25A2E20",
"CDPFilterHookOffset": "0x3013D20", "CDPFilterHookOffset": "0x3013D20",
"SceneOffsets": [1416, 1352, 488] "SceneOffsets": [56, 1416, 8, 1352, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 18787, "Version": 18787,
"LoadStartHookOffset": "0x25B2870", "LoadStartHookOffset": "0x25B2870",
"CDPFilterHookOffset": "0x3028AD0", "CDPFilterHookOffset": "0x3028AD0",
"SceneOffsets": [1408, 1344, 488] "SceneOffsets": [56, 1408, 8, 1344, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 18891, "Version": 18891,
"LoadStartHookOffset": "0x25B50C0", "LoadStartHookOffset": "0x25B50C0",
"CDPFilterHookOffset": "0x30245E0", "CDPFilterHookOffset": "0x30245E0",
"SceneOffsets": [1408, 1344, 488] "SceneOffsets": [56, 1408, 8, 1344, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 18955, "Version": 18955,
"LoadStartHookOffset": "0x25B52C0", "LoadStartHookOffset": "0x25B52C0",
"CDPFilterHookOffset": "0x30248B0", "CDPFilterHookOffset": "0x30248B0",
"SceneOffsets": [1408, 1344, 488] "SceneOffsets": [56, 1408, 8, 1344, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 19027, "Version": 19027,
"LoadStartHookOffset": "0x25B52D0", "LoadStartHookOffset": "0x25B52D0",
"CDPFilterHookOffset": "0x3024AD0", "CDPFilterHookOffset": "0x3024AD0",
"SceneOffsets": [1408, 1344, 488] "SceneOffsets": [56, 1408, 8, 1344, 16, 488]
} }
@@ -2,5 +2,5 @@
"Version": 19201, "Version": 19201,
"LoadStartHookOffset": "0x25B5DD0", "LoadStartHookOffset": "0x25B5DD0",
"CDPFilterHookOffset": "0x301B3C0", "CDPFilterHookOffset": "0x301B3C0",
"SceneOffsets": [1376, 1312, 456] "SceneOffsets": [56, 1376, 8, 1312, 16, 456]
} }
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19339,
"LoadStartHookOffset": "0x25B5DD0",
"CDPFilterHookOffset": "0x301BA00",
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
}
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19459,
"LoadStartHookOffset": "0x25BB580",
"CDPFilterHookOffset": "0x3022F20",
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
}
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19481,
"LoadStartHookOffset": "0x25BBA80",
"CDPFilterHookOffset": "0x3023420",
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
}
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19749,
"LoadStartHookOffset": "0x25E77C0",
"CDPFilterHookOffset": "0x30ABC60",
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
}
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19769,
"LoadStartHookOffset": "0x25E7990",
"CDPFilterHookOffset": "0x30AC0A0",
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
}
+6
View File
@@ -0,0 +1,6 @@
{
"Version": 19823,
"LoadStartHookOffset": "0x25ED1C0",
"CDPFilterHookOffset": "0x30B3320",
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
}
+197 -78
View File
@@ -1,124 +1,243 @@
const VERBOSE = false;
const PLATFORM = Process.platform; // 'darwin' | 'windows'
const ARCH = Process.arch; // 'arm64' | 'x64' | 'x86_64' | ...
// ── Module Resolution ──
const getMainModule = (version) => { const getMainModule = (version) => {
if (PLATFORM === "darwin") {
return Process.findModuleByName("WeChatAppEx Framework");
}
// Windows: flue.dll for newer versions
if (version >= 13331) { if (version >= 13331) {
return Process.findModuleByName("flue.dll"); return Process.findModuleByName("flue.dll");
} }
return Process.findModuleByName("WeChatAppEx.exe"); return Process.findModuleByName("WeChatAppEx.exe");
}; };
const patchCDPFilter = (base, config) => { // ── Scene Values ──
// xref: SendToClientFilter OR devtools_message_filter_applet_webview.cc
const offset = config.CDPFilterHookOffset; const SCENE_WHITELIST = [
1005, 1007, 1008, 1012, 1027, 1035, 1053, 1074,
1145, 1168, 1178, 1256, 1260, 1302, 1308,
];
// ── CDP Filter Patch (shared logic, platform-specific pointer access) ──
const patchCDPFilter = (base, offset) => {
// xref: SendToClientFilter / devtools_message_filter_applet_webview.cc
Interceptor.attach(base.add(offset), { Interceptor.attach(base.add(offset), {
onEnter(args) { onEnter(args) {
send( if (PLATFORM === "windows") {
`[patch] CDP filter on enter, original value of input: ${args[0].readPointer()}`,
);
this.inputValue = args[0]; this.inputValue = args[0];
}
}, },
onLeave(retval) { onLeave(retval) {
const inputValue = this.inputValue.readPointer(); let target;
if (inputValue.isNull() || inputValue.add(8).isNull()) { if (PLATFORM === "darwin") {
// there's a chance the value could be null // macOS: retval points directly to the struct
// return here to avoid crash if (!retval || retval.isNull()) return;
return; target = retval.add(8);
} else {
// Windows: dereference args[0] to get the struct pointer
const ptr = this.inputValue.readPointer();
if (ptr.isNull() || ptr.add(8).isNull()) return;
target = ptr.add(8);
} }
VERBOSE && console.log(`[patch] CDP filter v8[2]: ${target.readU32()}`);
send( if (target.readU32() === 6) {
`[patch] CDP filter on leave, patch input, now value: ${inputValue}; ` + target.writeU32(0x0);
`*(input + 8) = ${inputValue.add(8).readU32()}`,
);
if (inputValue.add(8).readU32() == 6) {
inputValue.add(8).writeU32(0x0);
} }
}, },
}); });
}; };
const hookOnLoadScene = (a1, sceneOffsets) => { // ── Resource Cache Policy Patch (darwin only) ──
const miniappConfigPtr = a1
.add(56)
.readPointer()
.add(sceneOffsets[0])
.readPointer();
const miniappScenePtr = miniappConfigPtr
.add(8)
.readPointer()
.add(sceneOffsets[1])
.readPointer()
.add(16)
.readPointer()
.add(488);
send(`[hook] scene: ${miniappScenePtr.readInt()}`);
// 1000: from issue #83 <-- will crash the process const patchResourceCachePolicy = (base, offset) => {
// 1007: from issue #80 // xref: WAPCAdapterAppIndex.js
// 1008: from issue #53 Interceptor.attach(base.add(offset), {
// 1027: from issue #78 onLeave(retval) {
// 1035: from issue #78 VERBOSE && console.log(`[patch] cache policy retval: ${retval.toInt32()} -> 0`);
// 1053: from issue #25 retval.replace(0x0);
// 1074: from issue #32 },
// 1145: from search });
// 1178: from phone (issue #117)
// 1256: from recent
// 1260: from frequently used
// 1302: from services
// 1308: minigame?
const sceneNumberArray = [
1005, 1007, 1008, 1027, 1035, 1053, 1074, 1145, 1178, 1256, 1260, 1302,
1308,
];
if (!sceneNumberArray.includes(miniappScenePtr.readInt())) {
return;
}
send("[hook] hook scene condition -> 1101");
miniappScenePtr.writeInt(1101);
// TODO: customize debugging endpoint
// const websocketServerStringPtr = passArgs.add(8).readPointer().add(520);
// VERBOSE && console.log("[hook] hook websocket server, original: ", websocketServerStringPtr.readUtf8String());
// websocketServerStringPtr.writeUtf8String("ws://127.0.0.1:8189/");
}; };
const patchOnLoadStart = (base, config) => { // ── OnLoadStart: Darwin ──
// xref: AppletIndexContainer::OnLoadStart
Interceptor.attach(base.add(config.LoadStartHookOffset), { const darwinPatchLoadStart = (base, offset) => {
// xref: AppletIndexContainer::OnLoadStart (first match)
// Force debug mode by setting second param to true
Interceptor.attach(base.add(offset), {
onEnter() {
console.log("[hook] AppletIndexContainer::OnLoadStart onEnter");
if (ARCH === "arm64") {
this.context.x1 = (this.context.x1 & ~0xff) | 0x1;
} else {
// x64 / x86_64: System V ABI, second param = RSI
this.context.rsi = (this.context.rsi & ~0xff) | 0x1;
}
},
onLeave() {},
});
};
const darwinPatchLoadStart2 = (base, offset, structOffset) => {
// xref: AppletIndexContainer::OnLoadStart (last function)
// Hijack scene value via struct pointer chain
Interceptor.attach(base.add(offset), {
onEnter(args) { onEnter(args) {
send( try {
`[inteceptor] AppletIndexContainer::OnLoadStart onEnter, ` + const v4 = args[0].add(8).readPointer();
`indexContainer.this: ${this.context.rcx}`, if (!v4 || v4.isNull()) return;
); const q1 = v4.add(structOffset).readPointer();
// write dl to 0x1 if (!q1 || q1.isNull()) return;
const q2 = q1.add(16).readPointer();
if (!q2 || q2.isNull()) return;
const scenePtr = q2.add(488);
const scene = scenePtr.readInt();
console.log(`[hook] scene: ${scene}`);
if (SCENE_WHITELIST.includes(scene)) {
console.log("[hook] hook scene -> 1101");
scenePtr.writeInt(1101);
}
} catch (e) {
console.error(`[hook] onLoadStart2 error: ${e}`);
}
},
onLeave() {},
});
};
// ── OnLoadStart: Windows ──
const winPatchLoadStart = (base, config) => {
// xref: AppletIndexContainer::OnLoadStart
// Windows combines debug flag + scene hijack in one hook
Interceptor.attach(base.add(config.LoadStartHookOffset), {
onEnter() {
// Microsoft x64 ABI: second param = RDX, force debug mode
if ((this.context.rdx & 0xff) !== 1) { if ((this.context.rdx & 0xff) !== 1) {
this.context.rdx = (this.context.rdx & ~0xff) | 0x1; this.context.rdx = (this.context.rdx & ~0xff) | 0x1;
} }
// handle onLoad scene // Scene hijack via SceneOffsets pointer chain
hookOnLoadScene(this.context.rcx, config.SceneOffsets); try {
}, const offsets = config.SceneOffsets;
onLeave(retval) { const miniappConfigPtr = this.context.rcx
// do nothing .add(offsets[0])
.readPointer()
.add(offsets[1])
.readPointer();
const scenePtr = miniappConfigPtr
.add(offsets[2])
.readPointer()
.add(offsets[3])
.readPointer()
.add(offsets[4])
.readPointer()
.add(offsets[5]);
const scene = scenePtr.readInt();
send(`[hook] scene: ${scene}`);
if (SCENE_WHITELIST.includes(scene)) {
send("[hook] hook scene -> 1101");
scenePtr.writeInt(1101);
}
} catch (e) {
send(`[hook] scene hook error: ${e}`);
}
}, },
onLeave() {},
}); });
}; };
// ── Config Parsing ──
const parseConfig = () => { const parseConfig = () => {
const rawConfig = `@@CONFIG@@`; const rawConfig = `@@CONFIG@@`;
if (rawConfig.includes("@@")) { if (rawConfig.includes("@@")) {
// test addresses // Fallback test config
if (PLATFORM === "darwin") {
return {
Version: 17078,
Arch: {
arm64: {
LoadStartHookOffset: "0x4F0620C",
LoadStartHookOffset2: "0x81CEC08",
CDPFilterHookOffset: "0x81BFC04",
ResourceCachePolicyHookOffset: "0x4F699E8",
StructOffset: 1376,
},
},
};
}
return { return {
Version: 18955, Version: 18955,
LoadStartHookOffset: "0x25B52C0", LoadStartHookOffset: "0x25B52C0",
CDPFilterHookOffset: "0x30248B0", CDPFilterHookOffset: "0x30248B0",
SceneOffsets: [1408, 1344], SceneOffsets: [56, 1408, 8, 1344, 16, 488],
}; };
} }
return JSON.parse(rawConfig); return JSON.parse(rawConfig);
}; };
const resolveArchConfig = (config) => {
// Windows configs have offsets at top level — return directly
if (config && config.LoadStartHookOffset) {
return config;
}
// Mac configs nest under Arch.{arm64|x64}
const table = config?.Arch || config?.arch;
if (!table) return null;
const candidates = [ARCH];
if (ARCH === "x86_64") candidates.push("x64");
if (ARCH === "amd64") candidates.push("x64");
for (const key of candidates) {
const picked = table[key];
if (picked && picked.LoadStartHookOffset) {
return { ...picked, Version: config.Version, __arch: key };
}
}
return null;
};
// ── Main ──
const main = () => { const main = () => {
const config = parseConfig(); const rawConfig = parseConfig();
const config = resolveArchConfig(rawConfig);
if (!config) {
console.error(`[frida] no config for platform=${PLATFORM} arch=${ARCH}`);
return;
}
const mainModule = getMainModule(config.Version); const mainModule = getMainModule(config.Version);
patchOnLoadStart(mainModule.base, config); if (!mainModule) {
patchCDPFilter(mainModule.base, config); const expected = PLATFORM === "darwin" ? "WeChatAppEx Framework" : "flue.dll / WeChatAppEx.exe";
console.error(`[frida] module not found: ${expected}`);
return;
}
console.log(`[frida] version=${config.Version} platform=${PLATFORM} arch=${config.__arch || ARCH}`);
console.log(`[frida] module base: ${mainModule.base}`);
const base = mainModule.base;
// CDP filter patch — both platforms
patchCDPFilter(base, config.CDPFilterHookOffset);
if (PLATFORM === "darwin") {
darwinPatchLoadStart(base, config.LoadStartHookOffset);
darwinPatchLoadStart2(base, config.LoadStartHookOffset2, config.StructOffset);
if (config.ResourceCachePolicyHookOffset) {
patchResourceCachePolicy(base, config.ResourceCachePolicyHookOffset);
}
} else {
winPatchLoadStart(base, config);
}
}; };
main(); main();
+2412 -151
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -1,8 +1,15 @@
{ {
"theme": "dark", "theme": "dark",
"debug_port": "9421",
"cdp_port": "62000", "cdp_port": "62000",
"debug_main": false, "debug_main": false,
"debug_frida": false, "debug_frida": false,
"selected_preload_scripts": [] "extract_packages_dir": "",
"extract_custom_patterns": {},
"auto_decompile": false,
"auto_scan": false,
"global_hook_scripts": [],
"font_ui": "PingFang SC",
"font_ui_size": 12,
"font_mono": "Menlo",
"font_mono_size": 15
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

+230
View File
@@ -0,0 +1,230 @@
# First
> 微信小程序安全调试工具 —— 基于 Frida + CDP 代理,支持 Windows / macOS 双平台,GUI 与 CLI 双模式
---
## 截图预览
### 主界面
![主界面](https://s1.galgame.fun/imgb/u0/20260408_69d655f49ca7e.png)
### 路由导航
![路由导航](https://s1.galgame.fun/imgb/u0/20260408_69d6560ca1a39.png)
### 云函数分析
![云函数分析](https://s1.galgame.fun/imgb/u0/20260402_69ce527ca5480.png)
### 调试开关(JSRPC / wx.cloud 调用)
![调试开关](https://s1.galgame.fun/imgb/u0/20260408_69d6564195009.png)
### 敏感信息提取
![敏感信息提取-1](https://s1.galgame.fun/imgb/u55/20260416_69e0be0e1c1d7.png)
<img src="https://s1.galgame.fun/imgb/u55/20260416_69e0be0c38e39.png" alt="敏感信息提取-2" />
![敏感信息提取-3](https://s1.galgame.fun/imgb/u55/20260416_69e0be0c70d46.png)
---
## 功能特性
- Frida 动态注入微信客户端,转发小程序调试协议
- CDP 代理桥接,Chrome DevTools 直连调试
- 路由枚举、分类与一键跳转
- 云函数调用监控与参数分析(动态 Hook + 静态扫描)
- UserScript 自动注入(支持 URL 匹配、run-at 时机控制)
- wxapkg 解密解包 + 敏感信息扫描(IP / 密钥 / 云存储 / JWT 等)
- 深色 / 浅色主题切换
- GUI(PySide6)与 CLI 双模式
---
## 环境要求
| 依赖 | 版本 |
|------|------|
| Python | >= 3.10 |
| frida | >= 17.0.0 |
| websockets | >= 12.0 |
| protobuf | >= 4.0.0 |
| PySide6 | >= 6.5.0 |
| pycryptodome | 最新版 |
安装依赖:
```bash
pip install -r requirements.txt
```
---
## 支持的微信版本
### Windows
- WMPF 版本:11581, 11633, 13331, 13341, 13487, 13639, 13655, 13871, 13909, 14161, 14199, 14315, 16133, 16203, 16389, 16467, 16771, 16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201
- 推荐微信版本:**4.1.0.30**
- 下载地址:[weixin/4.1.0.30](https://github.com/vs-olitus/wx-version/releases/tag/4.1.0.30)
### macOS
- WMPF 版本:18152, 18788
- 推荐微信版本:**4.1.7.30**
- 下载地址:[weixin/4.1.7.30](https://github.com/vs-olitus/wx-version/releases/tag/4.1.7.30)
---
## 快速开始
### Windows
**一键启动:** 双击 `启动.bat`,首次运行会自动安装依赖。
**手动启动:**
```bash
python gui.py
```
### macOS
**一键启动:** 终端执行 `./启动.sh`,首次运行会自动安装依赖。
**手动启动:**
```bash
python3 gui.py
```
启动后在主界面点击 **启动调试**,然后再打开小程序即可(请勿在启动调试前打开小程序)。
### CLI 模式
```bash
# 默认端口启动
python main.py
# 自定义端口
python main.py --cdp-port 62000
# 开启详细日志
python main.py --debug-main --debug-frida
```
### 连接 Chrome DevTools
启动后,在 Chrome 地址栏输入:
```
devtools://devtools/bundled/inspector.html?ws=127.0.0.1:62000
```
---
## 服务号调试操作
1. 打开一个小程序页面作为占位(保持不关闭)
2. 访问需要调试的服务号内容(如 H5 网页)
![PixPin_2026-05-04_22-48-42](https://s1.galgame.fun/imgb/u55/20260504_69f8b299a7d85.png)
3. 在工具栏「服务」目标列表中找到目标网页,双击后切换至调试视图即可开始调试
![PixPin_2026-05-04_22-51-14](https://s1.galgame.fun/imgb/u55/20260504_69f8b299a7d94.png)
![PixPin_2026-05-04_22-51-39](https://s1.galgame.fun/imgb/u55/20260504_69f8b299a804b.png)
---
## macOS 注意事项
如果 Frida 注入报错,需要解除系统对进程附加的限制,任选其一:
**方案一:关闭 SIP(系统完整性保护)**
> 关闭后 Frida 才能正常注入进程。参考教程:[macOS SIP 开启关闭教程](https://cloud.tencent.com/developer/article/1496058)
**方案二:强制重签名 WeChat**
```bash
sudo codesign --force --deep --sign - /Applications/WeChat.app
```
---
## 参数说明
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--debug-port` | `9421` | 远程调试服务端口 |
| `--cdp-port` | `62000` | CDP 代理监听端口 |
| `--debug-main` | 关闭 | 输出主进程调试日志 |
| `--debug-frida` | 关闭 | 输出 Frida 客户端日志 |
| `--scripts-dir` | `./userscripts` | UserScript 目录路径 |
| `--script` | — | 指定单个 .js 文件注入(可多次使用) |
---
## UserScript 注入
将 `.js` 脚本放入 `userscripts/` 目录,启动时自动加载并注入。也可手动指定:
```bash
python main.py --script ./my_hook.js --script ./another.js
```
---
## 打包为可执行文件
```bash
pyinstaller WMPFDebugger.spec
```
---
## 常见问题
**Q: Frida 已连接,但小程序端显示未连接或无法断点调试?**
确认操作顺序无误后,尝试以下步骤:
1. 彻底卸载微信并重启电脑(重要聊天记录请提前备份)
2. 删除 `C:\Users\用户名\AppData\Roaming\Tencent\xwechat\XPlugin\Plugins\RadiumWMPF` 下所有数字命名的文件夹
3. 再次重启后安装微信 4.1.0.30 版本
4. 检查上述路径,确认文件夹编号为 `16389`
---
## 参考项目
- [evi0s/WMPFDebugger](https://github.com/evi0s/WMPFDebugger)
- [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK)
- [残笑/FindSomething](https://github.com/momosecurity/FindSomething)
- [进击的HACK / JSRPC 与调用 wx.cloud](https://mp.weixin.qq.com/s/hTlekrCPiMJCvsHYx7CAxw)
- [linguo2625469/WMPFDebugger-mac](https://github.com/linguo2625469/WMPFDebugger-mac)
---
## 致谢
感谢 **0xsdeo** 师傅的大力支持与思路提供。
---
## 交流群
群满 200 人后需要手动邀请,请加我拉群:
![微信二维码](https://s1.galgame.fun/imgb/u55/20260413_69dcaf1310fc4.jpg)
---
## 免责声明
本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
+16
View File
@@ -0,0 +1,16 @@
[project]
name = "first"
version = "0.1.0"
description = "WeChat mini program security debugging tool based on Frida and CDP proxy."
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"frida>=17.0.0",
"websockets>=12.0",
"protobuf>=4.0.0",
"pyside6>=6.5.0",
"pycryptodome",
]
[tool.uv]
package = false
+1
View File
@@ -2,3 +2,4 @@ frida>=17.0.0
websockets>=12.0 websockets>=12.0
protobuf>=4.0.0 protobuf>=4.0.0
PySide6>=6.5.0 PySide6>=6.5.0
pycryptodome
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -7
View File
@@ -7,9 +7,9 @@ CDP_PORT = 62000
class CliOptions: class CliOptions:
def __init__(self, debug_port: int, cdp_port: int, debug_main: bool, debug_frida: bool, def __init__(self, cdp_port: int, debug_main: bool, debug_frida: bool,
scripts_dir: str = "", script_files: list = None): scripts_dir: str = "", script_files: list = None):
self.debug_port = debug_port self.debug_port = DEBUG_PORT # 小程序硬编码 9421,不可修改
self.cdp_port = cdp_port self.cdp_port = cdp_port
self.debug_main = debug_main self.debug_main = debug_main
self.debug_frida = debug_frida self.debug_frida = debug_frida
@@ -35,18 +35,16 @@ def parse_cli_options() -> CliOptions:
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""Examples: epilog=f"""Examples:
python main.py python main.py
python main.py --debug-port 9421 --cdp-port 62000 python main.py --cdp-port 62000
python main.py --debug-main --debug-frida python main.py --debug-main --debug-frida
Default ports: Default ports:
Debug server: {DEBUG_PORT} Debug server: {DEBUG_PORT} (fixed)
CDP proxy: {CDP_PORT} CDP proxy: {CDP_PORT}
Usage: Usage:
Connect Chrome DevTools to: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:<cdp-port>""" Connect Chrome DevTools to: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:<cdp-port>"""
) )
parser.add_argument("--debug-port", type=str, default=None,
help=f"Remote debug server port (default: {DEBUG_PORT})")
parser.add_argument("--cdp-port", type=str, default=None, parser.add_argument("--cdp-port", type=str, default=None,
help=f"CDP proxy server port (default: {CDP_PORT})") help=f"CDP proxy server port (default: {CDP_PORT})")
parser.add_argument("--debug-main", action="store_true", default=False, parser.add_argument("--debug-main", action="store_true", default=False,
@@ -72,7 +70,6 @@ Usage:
script_files.append(os.path.abspath(f)) script_files.append(os.path.abspath(f))
return CliOptions( return CliOptions(
debug_port=parse_port("--debug-port", args.debug_port, DEBUG_PORT),
cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT), cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT),
debug_main=args.debug_main, debug_main=args.debug_main,
debug_frida=args.debug_frida, debug_frida=args.debug_frida,
+14 -1
View File
@@ -25,6 +25,7 @@ class CloudAuditor:
"""启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame""" """启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame"""
if self._enabled: if self._enabled:
return {"ok": True, "already": True} return {"ok": True, "already": True}
self._injected = False # 强制重新注入(连接可能已更换)
await self.inject() await self.inject()
result = await self.engine.evaluate_js( result = await self.engine.evaluate_js(
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0) "JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
@@ -52,11 +53,23 @@ class CloudAuditor:
pass pass
async def poll(self): async def poll(self):
"""拉取新捕获的云函数调用,只返回增量""" """拉取新捕获的云函数调用,只返回增量。
自动检测 Hook 是否丢失(页面切换后),并重新注入。
"""
if not self._enabled: if not self._enabled:
return [] return []
try: try:
await self.inject() await self.inject()
# 检查 Hook 是否仍然存活(页面导航后可能丢失)
alive_result = await self.engine.evaluate_js(
"(function(){try{return window.cloudAudit&&window.cloudAudit._hooked?'1':'0'}catch(e){return '0'}})()",
timeout=3.0)
alive = self._extract_value(alive_result)
if alive != '1':
# Hook 丢失(页面切换/刷新),重新安装
await self.engine.evaluate_js(
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
self._seen_count = 0 # 重置计数器,新上下文从 0 开始
result = await self.engine.evaluate_js( result = await self.engine.evaluate_js(
"JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0) "JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0)
value = self._extract_value(result) value = self._extract_value(result)
+86 -43
View File
@@ -4,7 +4,9 @@ DebugEngine - wraps debug_server, proxy_server, frida_server into a reusable cla
import asyncio import asyncio
import json import json
import random import random
import platform
import re import re
import subprocess
import sys import sys
from collections import Counter from collections import Counter
from pathlib import Path from pathlib import Path
@@ -434,66 +436,95 @@ class DebugEngine:
# ── Frida ── # ── Frida ──
async def _start_frida(self): # ── Frida: platform helpers ──
logger = self.logger
def _get_project_root(self):
if getattr(sys, 'frozen', False):
return Path(getattr(sys, '_MEIPASS', Path(sys.executable).parent)).resolve()
return Path(__file__).parent.parent.resolve()
def _find_wmpf_pids_win(self):
"""Windows: find main WeChatAppEx.exe PID via ppid frequency."""
device = frida.get_local_device() device = frida.get_local_device()
processes = device.enumerate_processes(scope="metadata") processes = device.enumerate_processes(scope="metadata")
wmpf_processes = [p for p in processes if p.name == "WeChatAppEx.exe"] wmpf_procs = [p for p in processes if p.name == "WeChatAppEx.exe"]
if not wmpf_processes: if not wmpf_procs:
raise RuntimeError("[frida] WeChatAppEx.exe process not found") raise RuntimeError("[frida] WeChatAppEx.exe process not found")
wmpf_ppids = [] ppids = [p.parameters.get("ppid", 0) or 0 for p in wmpf_procs]
for p in wmpf_processes: main_pid = Counter(ppids).most_common(1)[0][0]
ppid = p.parameters.get("ppid", 0) if main_pid == 0:
wmpf_ppids.append(ppid if ppid else 0) raise RuntimeError("[frida] WeChatAppEx.exe main process not found")
if not wmpf_ppids: main_proc = next((p for p in processes if p.pid == main_pid), None)
raise RuntimeError("[frida] WeChatAppEx.exe process not found") if main_proc is None:
raise RuntimeError("[frida] could not locate main WMPF process")
pid_counts = Counter(wmpf_ppids) proc_path = main_proc.parameters.get("path", "")
wmpf_pid = pid_counts.most_common(1)[0][0] nums = re.findall(r"\d+", proc_path)
if not nums:
raise RuntimeError("[frida] cannot detect WMPF version from path")
version = int(nums[-1])
if version == 0:
raise RuntimeError("[frida] invalid WMPF version")
if wmpf_pid == 0: return [main_pid], version
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
wmpf_process = None def _find_wmpf_pids_darwin(self):
for p in processes: """macOS: find WeChatAppEx PIDs via pgrep, version via Info.plist."""
if p.pid == wmpf_pid: try:
wmpf_process = p out = subprocess.check_output(
break ["pgrep", "-f", "/MacOS/WeChatAppEx.app/Contents/MacOS/WeChatAppEx"],
text=True,
).strip()
pids = [int(p) for p in out.splitlines() if p.strip()]
except (subprocess.CalledProcessError, ValueError):
pids = []
if not pids:
raise RuntimeError("[frida] WeChatAppEx process not found on macOS")
if wmpf_process is None: try:
raise RuntimeError("[frida] Could not find main WMPF process") ver_out = subprocess.check_output(
["defaults", "read",
"/Applications/WeChat.app/Contents/MacOS/WeChatAppEx.app/Contents/Info.plist",
"CFBundleVersion"],
text=True,
).strip()
version = int(ver_out.split(".")[1])
except Exception:
raise RuntimeError("[frida] cannot detect WeChatAppEx version from Info.plist")
wmpf_process_path = wmpf_process.parameters.get("path", "") return pids, version
version_matches = re.findall(r"\d+", wmpf_process_path)
if not version_matches:
raise RuntimeError("[frida] error in find wmpf version")
wmpf_version = int(version_matches[-1])
if wmpf_version == 0:
raise RuntimeError("[frida] error in find wmpf version")
session = device.attach(wmpf_pid) async def _start_frida(self):
logger = self.logger
is_mac = sys.platform == "darwin"
platform_dir = "mac" if is_mac else "win"
if getattr(sys, 'frozen', False): # Find PIDs and version
# PyInstaller onefile: resources in _MEIPASS; onedir: next to exe if is_mac:
project_root = Path(getattr(sys, '_MEIPASS', Path(sys.executable).parent)).resolve() pids, wmpf_version = self._find_wmpf_pids_darwin()
else: else:
project_root = Path(__file__).parent.parent.resolve() pids, wmpf_version = self._find_wmpf_pids_win()
# Load hook script and config
project_root = self._get_project_root()
hook_path = project_root / "frida" / "hook.js" hook_path = project_root / "frida" / "hook.js"
if not hook_path.exists(): if not hook_path.exists():
raise RuntimeError("[frida] hook script not found") raise RuntimeError("[frida] hook script not found")
script_content = hook_path.read_text(encoding="utf-8")
config_path = project_root / "frida" / "config" / f"addresses.{wmpf_version}.json" config_path = project_root / "frida" / "config" / platform_dir / f"addresses.{wmpf_version}.json"
if not config_path.exists(): if not config_path.exists():
raise RuntimeError(f"[frida] version config not found: {wmpf_version}") raise RuntimeError(f"[frida] version config not found: {platform_dir}/{wmpf_version}")
config_content = config_path.read_text(encoding="utf-8")
config_content = json.dumps(json.loads(config_content))
script_content = hook_path.read_text(encoding="utf-8")
config_content = json.dumps(json.loads(config_path.read_text(encoding="utf-8")))
final_script = script_content.replace("@@CONFIG@@", config_content) final_script = script_content.replace("@@CONFIG@@", config_content)
script = session.create_script(final_script)
# Inject into all target PIDs
device = frida.get_local_device()
session = None
script = None
def on_message(message, data): def on_message(message, data):
if message.get("type") == "error": if message.get("type") == "error":
@@ -501,10 +532,22 @@ class DebugEngine:
return return
logger.frida_debug("[frida client]", message.get("payload", "")) logger.frida_debug("[frida client]", message.get("payload", ""))
script.on("message", on_message) for pid in pids:
script.load() try:
s = device.attach(pid)
sc = s.create_script(final_script)
sc.on("message", on_message)
sc.load()
logger.info(f"[frida] injected pid={pid}, version={wmpf_version}")
# Keep first successful session/script as primary
if session is None:
session, script = s, sc
except Exception as e:
logger.error(f"[frida] failed to inject pid={pid}: {e}")
logger.info(f"[frida] script loaded, WMPF version: {wmpf_version}, pid: {wmpf_pid}") if session is None:
raise RuntimeError("[frida] failed to inject any WeChatAppEx process")
logger.info(f"[frida] ready ({platform_dir}), version={wmpf_version}, {len(pids)} process(es)")
logger.info("[frida] you can now open any miniapps") logger.info("[frida] you can now open any miniapps")
return session, script return session, script
+194
View File
@@ -0,0 +1,194 @@
"""
extract_worker.py — 独立子进程 worker
由 GUI 通过 subprocess 启动,避免阻塞主 UI
支持两种模式:
1. decompile —— 解密解包单个小程序
2. scan —— 扫描已解包目录的敏感信息
通信协议 (stdout JSON lines):
{"type": "progress", "done": N, "total": N}
{"type": "log", "msg": "..."}
{"type": "result", "data": {...}}
{"type": "error", "msg": "..."}
命令行:
python extract_worker.py decompile --packages-dir DIR --appid APPID --output-dir DIR
python extract_worker.py scan --scan-dir DIR --output-dir DIR [--custom-patterns JSON_FILE]
"""
import json
import os
import sys
import traceback
def _init_stdout():
"""Windows 下强制 stdout 使用 UTF-8,避免 GBK 编码错误"""
if sys.platform == "win32":
import io
sys.stdout = io.TextIOWrapper(
sys.stdout.buffer, encoding="utf-8", errors="replace"
)
sys.stderr = io.TextIOWrapper(
sys.stderr.buffer, encoding="utf-8", errors="replace"
)
def _emit(obj):
"""输出一行 JSON 到 stdout"""
print(json.dumps(obj, ensure_ascii=False), flush=True)
def do_decompile(args):
"""解密解包单个小程序"""
src_dir = os.path.dirname(os.path.abspath(__file__))
base_dir = os.path.dirname(src_dir)
if base_dir not in sys.path:
sys.path.insert(0, base_dir)
from src.wxapkg import find_wxapkg_files, extract_wxapkg
packages_dir = args.packages_dir
appid = args.appid
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
_emit({"type": "log", "msg": f"正在查找 {appid} 的 wxapkg 文件..."})
# 查找该 appid 的包
all_pkgs = find_wxapkg_files(packages_dir)
pkgs = [p for p in all_pkgs if p["appid"] == appid]
if not pkgs:
_emit({"type": "log", "msg": f"未找到 {appid} 的 wxapkg 文件"})
_emit({"type": "result", "data": {"appid": appid, "files": 0, "extracted": 0}})
return
_emit({"type": "log", "msg": f"发现 {len(pkgs)} 个 wxapkg 文件"})
decompile_dir = os.path.join(output_dir, "decompiled")
os.makedirs(decompile_dir, exist_ok=True)
total = len(pkgs)
extracted_total = 0
for i, pkg in enumerate(pkgs, 1):
try:
files = extract_wxapkg(pkg["path"], decompile_dir, appid)
extracted_total += len(files)
_emit({"type": "log", "msg": f" 解包 {pkg['name']}: {len(files)} 个文件"})
except Exception as e:
_emit({"type": "log", "msg": f" 解包失败 {pkg['name']}: {e}"})
_emit({"type": "progress", "done": i, "total": total})
_emit({"type": "result", "data": {
"appid": appid,
"files": total,
"extracted": extracted_total,
"decompile_dir": decompile_dir,
}})
_emit({"type": "log", "msg": f"反编译完成! 共提取 {extracted_total} 个文件"})
def do_scan(args):
"""扫描已解包的目录"""
src_dir = os.path.dirname(os.path.abspath(__file__))
base_dir = os.path.dirname(src_dir)
if base_dir not in sys.path:
sys.path.insert(0, base_dir)
from src.extractor import Extractor
scan_dir = args.scan_dir
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
extractor = Extractor()
# 加载自定义正则
if args.custom_patterns and os.path.isfile(args.custom_patterns):
try:
with open(args.custom_patterns, "r", encoding="utf-8") as f:
custom = json.load(f)
loaded = 0
for name, info in custom.items():
# 兼容新格式 {"regex": str, "enabled": bool} 和旧格式 str
if isinstance(info, dict):
if not info.get("enabled", True):
continue
pat = info.get("regex", "")
else:
pat = info
if pat:
extractor.add_custom_pattern(name, pat)
loaded += 1
_emit({"type": "log", "msg": f"已加载 {loaded} 个自定义正则"})
except Exception as e:
_emit({"type": "log", "msg": f"加载自定义正则失败: {e}"})
_emit({"type": "log", "msg": f"正在扫描目录: {scan_dir}"})
def on_progress(done, total):
_emit({"type": "progress", "done": done, "total": total})
scan_result = extractor.scan_directory(scan_dir, on_progress=on_progress)
# 导出
result_dir = output_dir
html_path = os.path.join(result_dir, "report.html")
json_path = os.path.join(result_dir, "report.json")
Extractor.export_html(scan_result, html_path)
Extractor.export_json(scan_result, json_path)
summary = Extractor.get_summary(scan_result)
total_findings = sum(summary.values())
_emit({"type": "result", "data": {
"files_scanned": scan_result.get("files_scanned", 0),
"elapsed": scan_result.get("elapsed", 0),
"findings": total_findings,
"summary": summary,
"html_path": html_path,
"json_path": json_path,
"result_dir": result_dir,
}})
_emit({"type": "log", "msg": f"扫描完成! 发现 {total_findings} 条敏感信息,耗时 {scan_result.get('elapsed', 0)}s"})
def main():
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
# decompile 子命令
p_dec = subparsers.add_parser("decompile")
p_dec.add_argument("--packages-dir", required=True)
p_dec.add_argument("--appid", required=True)
p_dec.add_argument("--output-dir", required=True)
# scan 子命令
p_scan = subparsers.add_parser("scan")
p_scan.add_argument("--scan-dir", required=True)
p_scan.add_argument("--output-dir", required=True)
p_scan.add_argument("--custom-patterns", default="")
args = parser.parse_args()
if args.command == "decompile":
do_decompile(args)
elif args.command == "scan":
do_scan(args)
else:
_emit({"type": "error", "msg": "未知命令,使用 decompile 或 scan"})
sys.exit(1)
if __name__ == "__main__":
_init_stdout()
try:
main()
except Exception as e:
_emit({"type": "error", "msg": f"worker 异常: {traceback.format_exc()}"})
sys.exit(1)
+978
View File
@@ -0,0 +1,978 @@
r"""
extractor.py — 敏感信息提取引擎
直接使用 heartk_fast.py 的完整正则规则,保证提取结果一致
支持自定义正则规则,自定义类别会出现在导出报告中
用法:
from src.extractor import Extractor
ext = Extractor()
ext.add_custom_pattern("视频", r"video_url\s*=\s*['\"](.+?)['\"]")
results = ext.scan_directory("/path/to/dir")
Extractor.export_html(results, "report.html")
"""
import json
import os
import re
import sys
import time
from concurrent.futures import ProcessPoolExecutor, as_completed
from typing import Dict, List, Optional, Any, Callable
try:
import chardet
except ImportError:
chardet = None
# ============================================================
# 1. 所有正则规则 (从 heartk_fast.py 原封不动复制)
# ============================================================
STATIC_EXTENSIONS = ['.jpg', '.png', '.gif', '.css', '.svg', '.ico', '.js']
CATEGORIES = [
"ip", "ip_port", "domain", "path", "incomplete_path",
"url", "sfz", "mobile", "mail", "jwt", "algorithm", "secret", "static"
]
# ---------- extract_info 对应的正则 ----------
EXTRACT_PATTERNS: Dict[str, re.Pattern] = {
'sfz': re.compile(
r"""['"]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3})|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))['"]"""),
'mobile': re.compile(
r"""['\"](1(3([0-35-9]\d|4[1-8])|4[14-9]\d|5([\d]\d|7[1-79])|66\d|7[2-35-8]\d|8\d{2}|9[89]\d)\d{7})['\"]"""),
'mail': re.compile(
r"""['\"][a-zA-Z0-9\._\-]*@[a-zA-Z0-9\._\-]{1,63}\.((?!js|css|jpg|jpeg|png|ico)[a-zA-Z]{2,})['\"]"""),
'ip': re.compile(
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/.*?)?['\"]"""),
'ip_port': re.compile(
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}(\/.*?)?['\"]"""),
'domain': re.compile(
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?[a-zA-Z0-9\-\.]*?\.(xin|com|cn|net|com\.cn|vip|top|cc|shop|club|wang|xyz|luxe|site|news|pub|fun|online|win|red|loan|ren|mom|net\.cn|org|link|biz|bid|help|tech|date|mobi|so|me|tv|co|vc|pw|video|party|pics|website|store|ltd|ink|trade|live|wiki|space|gift|lol|work|band|info|click|photo|market|tel|social|press|game|kim|org\.cn|games|pro|men|love|studio|rocks|asia|group|science|design|software|engineer|lawyer|fit|beer|tw|我爱你|中国|公司|网络|在线|网址|网店|集团|中文网)(\:\d{1,5})?(\/)?['\"]"""),
'path': re.compile(
r"""['\"](?:\/|\.\.\/)(?:[^\/\>\< \)\(\{\}\,\'\"\\\n][^\>\< \)\(\{\}\,\'\"\\\n]*?)['\"]"""),
'incomplete_path': re.compile(
r"""['\"][^\/\>\< \)\(\{\}\,\'\"\\\n][\w\/]*?\/[\w\/]*?['\"]"""),
'url': re.compile(
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?[a-zA-Z0-9\-\.]*?\.(xin|com|cn|net|com\.cn|vip|top|cc|shop|club|wang|xyz|luxe|site|news|pub|fun|online|win|red|loan|ren|mom|net\.cn|org|link|biz|bid|help|tech|date|mobi|so|me|tv|co|vc|pw|video|party|pics|website|store|ltd|ink|trade|live|wiki|space|gift|lol|work|band|info|click|photo|market|tel|social|press|game|kim|org\.cn|games|pro|men|love|studio|rocks|asia|group|science|design|software|engineer|lawyer|fit|beer|tw|我爱你|中国|公司|网络|在线|网址|网店|集团|中文网)(\:\d{1,5})?(\/.*?)?['\"]"""),
'jwt': re.compile(
r"""['\"](ey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|ey[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})['\"]"""),
'algorithm': re.compile(
r"""\W(Base64\.encode|Base64\.decode|btoa|atob|CryptoJS\.AES|CryptoJS\.DES|JSEncrypt|rsa|KJUR|\$\.md5|md5|sha1|sha256|sha512)[\(\.]""", re.IGNORECASE),
}
# ---------- nuclei_regex (727条秘密检测规则) ----------
_NUCLEI_REGEX_SOURCES = [
"zopim[_-]?account[_-]?key", "zhuliang[_-]?gh[_-]?token",
"zensonatypepassword", "zendesk[_-]?travis[_-]?github",
"yt[_-]?server[_-]?api[_-]?key", "yt[_-]?partner[_-]?refresh[_-]?token",
"yt[_-]?partner[_-]?client[_-]?secret", "yt[_-]?client[_-]?secret",
"yt[_-]?api[_-]?key", "yt[_-]?account[_-]?refresh[_-]?token",
"yt[_-]?account[_-]?client[_-]?secret", "yangshun[_-]?gh[_-]?token",
"yangshun[_-]?gh[_-]?password", "www[_-]?googleapis[_-]?com",
"wpt[_-]?ssh[_-]?private[_-]?key[_-]?base64", "wpt[_-]?ssh[_-]?connect",
"wpt[_-]?report[_-]?api[_-]?key", "wpt[_-]?prepare[_-]?dir",
"wpt[_-]?db[_-]?user", "wpt[_-]?db[_-]?password", "wporg[_-]?password",
"wpjm[_-]?phpunit[_-]?google[_-]?geocode[_-]?api[_-]?key",
"wordpress[_-]?db[_-]?user", "wordpress[_-]?db[_-]?password",
"wincert[_-]?password", "widget[_-]?test[_-]?server",
"widget[_-]?fb[_-]?password[_-]?3", "widget[_-]?fb[_-]?password[_-]?2",
"widget[_-]?fb[_-]?password",
"widget[_-]?basic[_-]?password[_-]?5", "widget[_-]?basic[_-]?password[_-]?4",
"widget[_-]?basic[_-]?password[_-]?3", "widget[_-]?basic[_-]?password[_-]?2",
"widget[_-]?basic[_-]?password",
"watson[_-]?password", "watson[_-]?device[_-]?password",
"watson[_-]?conversation[_-]?password", "wakatime[_-]?api[_-]?key",
"vscetoken", "visual[_-]?recognition[_-]?api[_-]?key",
"virustotal[_-]?apikey", "vip[_-]?github[_-]?deploy[_-]?key[_-]?pass",
"vip[_-]?github[_-]?deploy[_-]?key",
"vip[_-]?github[_-]?build[_-]?repo[_-]?deploy[_-]?key",
"v[_-]?sfdc[_-]?password", "v[_-]?sfdc[_-]?client[_-]?secret",
"usertravis", "user[_-]?assets[_-]?secret[_-]?access[_-]?key",
"user[_-]?assets[_-]?access[_-]?key[_-]?id", "use[_-]?ssh",
"us[_-]?east[_-]?1[_-]?elb[_-]?amazonaws[_-]?com",
"urban[_-]?secret", "urban[_-]?master[_-]?secret", "urban[_-]?key",
"unity[_-]?serial", "unity[_-]?password",
"twitteroauthaccesstoken", "twitteroauthaccesssecret",
"twitter[_-]?consumer[_-]?secret", "twitter[_-]?consumer[_-]?key",
"twine[_-]?password", "twilio[_-]?token", "twilio[_-]?sid",
"twilio[_-]?configuration[_-]?sid",
"twilio[_-]?chat[_-]?account[_-]?api[_-]?service",
"twilio[_-]?api[_-]?secret", "twilio[_-]?api[_-]?key",
"trex[_-]?okta[_-]?client[_-]?token", "trex[_-]?client[_-]?token",
"travis[_-]?token", "travis[_-]?secure[_-]?env[_-]?vars",
"travis[_-]?pull[_-]?request", "travis[_-]?gh[_-]?token",
"travis[_-]?e2e[_-]?token", "travis[_-]?com[_-]?token",
"travis[_-]?branch", "travis[_-]?api[_-]?token",
"travis[_-]?access[_-]?token", "token[_-]?core[_-]?java",
"thera[_-]?oss[_-]?access[_-]?key", "tester[_-]?keys[_-]?password",
"test[_-]?test", "test[_-]?github[_-]?token", "tesco[_-]?api[_-]?key",
"svn[_-]?pass", "surge[_-]?token", "surge[_-]?login",
"stripe[_-]?public", "stripe[_-]?private",
"strip[_-]?secret[_-]?key", "strip[_-]?publishable[_-]?key",
"stormpath[_-]?api[_-]?key[_-]?secret", "stormpath[_-]?api[_-]?key[_-]?id",
"starship[_-]?auth[_-]?token", "starship[_-]?account[_-]?sid",
"star[_-]?test[_-]?secret[_-]?access[_-]?key", "star[_-]?test[_-]?location",
"star[_-]?test[_-]?bucket", "star[_-]?test[_-]?aws[_-]?access[_-]?key[_-]?id",
"staging[_-]?base[_-]?url[_-]?runscope", "ssmtp[_-]?config", "sshpass",
"srcclr[_-]?api[_-]?token",
"square[_-]?reader[_-]?sdk[_-]?repository[_-]?password",
"sqssecretkey", "sqsaccesskey", "spring[_-]?mail[_-]?password",
"spotify[_-]?api[_-]?client[_-]?secret", "spotify[_-]?api[_-]?access[_-]?token",
"spaces[_-]?secret[_-]?access[_-]?key", "spaces[_-]?access[_-]?key[_-]?id",
"soundcloud[_-]?password", "soundcloud[_-]?client[_-]?secret",
"sonatypepassword", "sonatype[_-]?token[_-]?user",
"sonatype[_-]?token[_-]?password", "sonatype[_-]?password",
"sonatype[_-]?pass", "sonatype[_-]?nexus[_-]?password",
"sonatype[_-]?gpg[_-]?passphrase", "sonatype[_-]?gpg[_-]?key[_-]?name",
"sonar[_-]?token", "sonar[_-]?project[_-]?key",
"sonar[_-]?organization[_-]?key",
"socrata[_-]?password", "socrata[_-]?app[_-]?token",
"snyk[_-]?token", "snyk[_-]?api[_-]?token",
"snoowrap[_-]?refresh[_-]?token", "snoowrap[_-]?password",
"snoowrap[_-]?client[_-]?secret", "slate[_-]?user[_-]?email",
"slash[_-]?developer[_-]?space[_-]?key", "slash[_-]?developer[_-]?space",
"signing[_-]?key[_-]?sid", "signing[_-]?key[_-]?secret",
"signing[_-]?key[_-]?password", "signing[_-]?key",
"setsecretkey", "setdstsecretkey", "setdstaccesskey",
"ses[_-]?secret[_-]?key", "ses[_-]?access[_-]?key",
"service[_-]?account[_-]?secret", "sentry[_-]?key",
"sentry[_-]?secret", "sentry[_-]?endpoint",
"sentry[_-]?default[_-]?org", "sentry[_-]?auth[_-]?token",
"sendwithus[_-]?key", "sendgrid[_-]?username", "sendgrid[_-]?user",
"sendgrid[_-]?password", "sendgrid[_-]?key", "sendgrid[_-]?api[_-]?key",
"sendgrid", "selion[_-]?selenium[_-]?host", "selion[_-]?log[_-]?level[_-]?dev",
"segment[_-]?api[_-]?key", "secretid", "secretkey", "secretaccesskey",
"secret[_-]?key[_-]?base",
"secret[_-]?9", "secret[_-]?8", "secret[_-]?7", "secret[_-]?6",
"secret[_-]?5", "secret[_-]?4", "secret[_-]?3", "secret[_-]?2",
"secret[_-]?11", "secret[_-]?10", "secret[_-]?1", "secret[_-]?0",
"sdr[_-]?token", "scrutinizer[_-]?token", "sauce[_-]?access[_-]?key",
"sandbox[_-]?aws[_-]?secret[_-]?access[_-]?key",
"sandbox[_-]?aws[_-]?access[_-]?key[_-]?id", "sandbox[_-]?access[_-]?token",
"salesforce[_-]?bulk[_-]?test[_-]?security[_-]?token",
"salesforce[_-]?bulk[_-]?test[_-]?password",
"sacloud[_-]?api", "sacloud[_-]?access[_-]?token[_-]?secret",
"sacloud[_-]?access[_-]?token",
"s3[_-]?user[_-]?secret", "s3[_-]?secret[_-]?key",
"s3[_-]?secret[_-]?assets", "s3[_-]?secret[_-]?app[_-]?logs",
"s3[_-]?key[_-]?assets", "s3[_-]?key[_-]?app[_-]?logs", "s3[_-]?key",
"s3[_-]?external[_-]?3[_-]?amazonaws[_-]?com",
"s3[_-]?bucket[_-]?name[_-]?assets", "s3[_-]?bucket[_-]?name[_-]?app[_-]?logs",
"s3[_-]?access[_-]?key[_-]?id", "s3[_-]?access[_-]?key",
"rubygems[_-]?auth[_-]?token", "rtd[_-]?store[_-]?pass", "rtd[_-]?key[_-]?pass",
"route53[_-]?access[_-]?key[_-]?id",
"ropsten[_-]?private[_-]?key", "rinkeby[_-]?private[_-]?key",
"rest[_-]?api[_-]?key", "repotoken",
"reporting[_-]?webdav[_-]?url", "reporting[_-]?webdav[_-]?pwd",
"release[_-]?token", "release[_-]?gh[_-]?token",
"registry[_-]?secure", "registry[_-]?pass", "refresh[_-]?token",
"rediscloud[_-]?url", "redis[_-]?stunnel[_-]?urls",
"randrmusicapiaccesstoken", "rabbitmq[_-]?password",
"quip[_-]?token", "qiita[_-]?token", "pypi[_-]?passowrd",
"pushover[_-]?token", "publish[_-]?secret", "publish[_-]?key",
"publish[_-]?access", "project[_-]?config",
"prod[_-]?secret[_-]?key", "prod[_-]?password", "prod[_-]?access[_-]?key[_-]?id",
"private[_-]?signing[_-]?password", "pring[_-]?mail[_-]?username",
"preferred[_-]?username", "prebuild[_-]?auth",
"postgresql[_-]?pass", "postgresql[_-]?db",
"postgres[_-]?env[_-]?postgres[_-]?password",
"postgres[_-]?env[_-]?postgres[_-]?db", "plugin[_-]?password",
"plotly[_-]?apikey", "places[_-]?apikey", "places[_-]?api[_-]?key",
"pg[_-]?host", "pg[_-]?database", "personal[_-]?secret", "personal[_-]?key",
"percy[_-]?token", "percy[_-]?project",
"paypal[_-]?client[_-]?secret", "passwordtravis", "parse[_-]?js[_-]?key",
"pagerduty[_-]?apikey", "packagecloud[_-]?token",
"ossrh[_-]?username", "ossrh[_-]?secret", "ossrh[_-]?password",
"ossrh[_-]?pass", "ossrh[_-]?jira[_-]?password",
"os[_-]?password", "os[_-]?auth[_-]?url",
"org[_-]?project[_-]?gradle[_-]?sonatype[_-]?nexus[_-]?password",
"org[_-]?gradle[_-]?project[_-]?sonatype[_-]?nexus[_-]?password",
"openwhisk[_-]?key", "open[_-]?whisk[_-]?key",
"onesignal[_-]?user[_-]?auth[_-]?key", "onesignal[_-]?api[_-]?key",
"omise[_-]?skey", "omise[_-]?pubkey", "omise[_-]?pkey", "omise[_-]?key",
"okta[_-]?oauth2[_-]?clientsecret", "okta[_-]?oauth2[_-]?client[_-]?secret",
"okta[_-]?client[_-]?token",
"ofta[_-]?secret", "ofta[_-]?region", "ofta[_-]?key",
"octest[_-]?password", "octest[_-]?app[_-]?username",
"octest[_-]?app[_-]?password", "oc[_-]?pass",
"object[_-]?store[_-]?creds", "object[_-]?store[_-]?bucket",
"object[_-]?storage[_-]?region[_-]?name", "object[_-]?storage[_-]?password",
"oauth[_-]?token", "numbers[_-]?service[_-]?pass",
"nuget[_-]?key", "nuget[_-]?apikey", "nuget[_-]?api[_-]?key",
"npm[_-]?token", "npm[_-]?secret[_-]?key", "npm[_-]?password",
"npm[_-]?email", "npm[_-]?auth[_-]?token", "npm[_-]?api[_-]?token",
"npm[_-]?api[_-]?key", "now[_-]?token", "non[_-]?token",
"node[_-]?pre[_-]?gyp[_-]?secretaccesskey",
"node[_-]?pre[_-]?gyp[_-]?github[_-]?token",
"node[_-]?pre[_-]?gyp[_-]?accesskeyid", "node[_-]?env",
"ngrok[_-]?token", "ngrok[_-]?auth[_-]?token",
"nexuspassword", "nexus[_-]?password",
"new[_-]?relic[_-]?beta[_-]?token", "netlify[_-]?api[_-]?key",
"nativeevents", "mysqlsecret", "mysqlmasteruser",
"mysql[_-]?username", "mysql[_-]?user", "mysql[_-]?root[_-]?password",
"mysql[_-]?password", "mysql[_-]?hostname", "mysql[_-]?database",
"my[_-]?secret[_-]?env",
"multi[_-]?workspace[_-]?sid", "multi[_-]?workflow[_-]?sid",
"multi[_-]?disconnect[_-]?sid", "multi[_-]?connect[_-]?sid",
"multi[_-]?bob[_-]?sid",
"minio[_-]?secret[_-]?key", "minio[_-]?access[_-]?key",
"mile[_-]?zero[_-]?key", "mh[_-]?password", "mh[_-]?apikey",
"mg[_-]?public[_-]?api[_-]?key", "mg[_-]?api[_-]?key",
"mapboxaccesstoken",
"mapbox[_-]?aws[_-]?secret[_-]?access[_-]?key",
"mapbox[_-]?aws[_-]?access[_-]?key[_-]?id",
"mapbox[_-]?api[_-]?token", "mapbox[_-]?access[_-]?token",
"manifest[_-]?app[_-]?url", "manifest[_-]?app[_-]?token",
"mandrill[_-]?api[_-]?key", "managementapiaccesstoken",
"management[_-]?token", "manage[_-]?secret", "manage[_-]?key",
"mailgun[_-]?secret[_-]?api[_-]?key", "mailgun[_-]?pub[_-]?key",
"mailgun[_-]?pub[_-]?apikey", "mailgun[_-]?priv[_-]?key",
"mailgun[_-]?password", "mailgun[_-]?apikey", "mailgun[_-]?api[_-]?key",
"mailer[_-]?password", "mailchimp[_-]?key", "mailchimp[_-]?api[_-]?key",
"mail[_-]?password", "magento[_-]?password",
"magento[_-]?auth[_-]?username", "magento[_-]?auth[_-]?password",
"lottie[_-]?upload[_-]?cert[_-]?key[_-]?store[_-]?password",
"lottie[_-]?upload[_-]?cert[_-]?key[_-]?password",
"lottie[_-]?s3[_-]?secret[_-]?key",
"lottie[_-]?happo[_-]?secret[_-]?key", "lottie[_-]?happo[_-]?api[_-]?key",
"looker[_-]?test[_-]?runner[_-]?client[_-]?secret",
"ll[_-]?shared[_-]?key", "ll[_-]?publish[_-]?url",
"linux[_-]?signing[_-]?key",
"lighthouse[_-]?api[_-]?key",
"lektor[_-]?deploy[_-]?username", "lektor[_-]?deploy[_-]?password",
"leanplum[_-]?key", "kxoltsn3vogdop92m",
"kubeconfig", "kubecfg[_-]?s3[_-]?path", "kovan[_-]?private[_-]?key",
"keystore[_-]?pass",
"kafka[_-]?rest[_-]?url", "kafka[_-]?instance[_-]?name",
"kafka[_-]?admin[_-]?url", "jwt[_-]?secret",
"jdbc[_-]?host", "jdbc[_-]?databaseurl",
"itest[_-]?gh[_-]?token", "ios[_-]?docs[_-]?deploy[_-]?token",
"internal[_-]?secrets",
"integration[_-]?test[_-]?appid", "integration[_-]?test[_-]?api[_-]?key",
"index[_-]?name", "ij[_-]?repo[_-]?username", "ij[_-]?repo[_-]?password",
"hub[_-]?dxia2[_-]?password", "homebrew[_-]?github[_-]?api[_-]?token",
"hockeyapp[_-]?token",
"heroku[_-]?token", "heroku[_-]?email", "heroku[_-]?api[_-]?key",
"hb[_-]?codesign[_-]?key[_-]?pass", "hb[_-]?codesign[_-]?gpg[_-]?pass",
"hab[_-]?key", "hab[_-]?auth[_-]?token",
"grgit[_-]?user", "gren[_-]?github[_-]?token",
"gradle[_-]?signing[_-]?password", "gradle[_-]?signing[_-]?key[_-]?id",
"gradle[_-]?publish[_-]?secret", "gradle[_-]?publish[_-]?key",
"gpg[_-]?secret[_-]?keys", "gpg[_-]?private[_-]?key",
"gpg[_-]?passphrase", "gpg[_-]?ownertrust",
"gpg[_-]?keyname", "gpg[_-]?key[_-]?name",
"google[_-]?private[_-]?key(?:[_-]?id)?",
"google[_-]?maps[_-]?api[_-]?key", "google[_-]?client[_-]?secret",
"google[_-]?client[_-]?id", "google[_-]?client[_-]?email",
"google[_-]?account[_-]?type", "gogs[_-]?password",
"gitlab[_-]?user[_-]?email",
"github[_-]?tokens", "github[_-]?token", "github[_-]?repo",
"github[_-]?release[_-]?token", "github[_-]?pwd", "github[_-]?password",
"github[_-]?oauth[_-]?token", "github[_-]?oauth", "github[_-]?key",
"github[_-]?hunter[_-]?username", "github[_-]?hunter[_-]?token",
"github[_-]?deployment[_-]?token",
"github[_-]?deploy[_-]?hb[_-]?doc[_-]?pass",
"github[_-]?client[_-]?secret", "github[_-]?auth[_-]?token",
"github[_-]?auth", "github[_-]?api[_-]?token", "github[_-]?api[_-]?key",
"github[_-]?access[_-]?token",
"git[_-]?token", "git[_-]?name", "git[_-]?email",
"git[_-]?committer[_-]?name", "git[_-]?committer[_-]?email",
"git[_-]?author[_-]?name", "git[_-]?author[_-]?email",
"ghost[_-]?api[_-]?key", "ghb[_-]?token",
"gh[_-]?unstable[_-]?oauth[_-]?client[_-]?secret",
"gh[_-]?token", "gh[_-]?repo[_-]?token", "gh[_-]?oauth[_-]?token",
"gh[_-]?oauth[_-]?client[_-]?secret",
"gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?secret",
"gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?id",
"gh[_-]?next[_-]?oauth[_-]?client[_-]?secret",
"gh[_-]?email", "gh[_-]?api[_-]?key",
"gcs[_-]?bucket", "gcr[_-]?password",
"gcloud[_-]?service[_-]?key", "gcloud[_-]?project", "gcloud[_-]?bucket",
"ftp[_-]?username", "ftp[_-]?user", "ftp[_-]?pw", "ftp[_-]?password",
"ftp[_-]?login", "ftp[_-]?host", "fossa[_-]?api[_-]?key",
"flickr[_-]?api[_-]?secret", "flickr[_-]?api[_-]?key",
"flask[_-]?secret[_-]?key", "firefox[_-]?secret",
"firebase[_-]?token", "firebase[_-]?project[_-]?develop",
"firebase[_-]?key", "firebase[_-]?api[_-]?token", "firebase[_-]?api[_-]?json",
"file[_-]?password", "exp[_-]?password", "eureka[_-]?awssecretkey",
"env[_-]?sonatype[_-]?password", "env[_-]?secret[_-]?access[_-]?key",
"env[_-]?secret", "env[_-]?key",
"env[_-]?heroku[_-]?api[_-]?key", "env[_-]?github[_-]?oauth[_-]?token",
"end[_-]?user[_-]?password", "encryption[_-]?password",
"elasticsearch[_-]?password", "elastic[_-]?cloud[_-]?auth",
"dsonar[_-]?projectkey", "dsonar[_-]?login",
"droplet[_-]?travis[_-]?password", "dropbox[_-]?oauth[_-]?bearer",
"doordash[_-]?auth[_-]?token",
"dockerhubpassword", "dockerhub[_-]?password",
"docker[_-]?token", "docker[_-]?postgres[_-]?url",
"docker[_-]?password", "docker[_-]?passwd", "docker[_-]?pass",
"docker[_-]?key", "docker[_-]?hub[_-]?password",
"digitalocean[_-]?ssh[_-]?key[_-]?ids",
"digitalocean[_-]?ssh[_-]?key[_-]?body",
"digitalocean[_-]?access[_-]?token", "dgpg[_-]?passphrase",
"deploy[_-]?user", "deploy[_-]?token", "deploy[_-]?secure",
"deploy[_-]?password",
"ddgc[_-]?github[_-]?token", "ddg[_-]?test[_-]?email[_-]?pw",
"ddg[_-]?test[_-]?email",
"db[_-]?username", "db[_-]?user", "db[_-]?pw", "db[_-]?password",
"db[_-]?host", "db[_-]?database", "db[_-]?connection",
"datadog[_-]?app[_-]?key", "datadog[_-]?api[_-]?key",
"database[_-]?username", "database[_-]?user", "database[_-]?port",
"database[_-]?password", "database[_-]?name", "database[_-]?host",
"danger[_-]?github[_-]?api[_-]?token", "cypress[_-]?record[_-]?key",
"coverity[_-]?scan[_-]?token", "coveralls[_-]?token",
"coveralls[_-]?repo[_-]?token", "coveralls[_-]?api[_-]?token",
"cos[_-]?secrets",
"conversation[_-]?username", "conversation[_-]?password",
"contentful[_-]?v2[_-]?access[_-]?token",
"contentful[_-]?test[_-]?org[_-]?cma[_-]?token",
"contentful[_-]?php[_-]?management[_-]?test[_-]?token",
"contentful[_-]?management[_-]?api[_-]?access[_-]?token[_-]?new",
"contentful[_-]?management[_-]?api[_-]?access[_-]?token",
"contentful[_-]?integration[_-]?management[_-]?token",
"contentful[_-]?cma[_-]?test[_-]?token",
"contentful[_-]?access[_-]?token",
"consumerkey", "consumer[_-]?key", "conekta[_-]?apikey",
"coding[_-]?token", "codecov[_-]?token", "codeclimate[_-]?repo[_-]?token",
"codacy[_-]?project[_-]?token",
"cocoapods[_-]?trunk[_-]?token", "cocoapods[_-]?trunk[_-]?email",
"cn[_-]?secret[_-]?access[_-]?key", "cn[_-]?access[_-]?key[_-]?id",
"clu[_-]?ssh[_-]?private[_-]?key[_-]?base64", "clu[_-]?repo[_-]?url",
"cloudinary[_-]?url[_-]?staging", "cloudinary[_-]?url",
"cloudflare[_-]?email", "cloudflare[_-]?auth[_-]?key",
"cloudflare[_-]?auth[_-]?email", "cloudflare[_-]?api[_-]?key",
"cloudant[_-]?service[_-]?database", "cloudant[_-]?processed[_-]?database",
"cloudant[_-]?password", "cloudant[_-]?parsed[_-]?database",
"cloudant[_-]?order[_-]?database", "cloudant[_-]?instance",
"cloudant[_-]?database", "cloudant[_-]?audited[_-]?database",
"cloudant[_-]?archived[_-]?database",
"cloud[_-]?api[_-]?key", "clojars[_-]?password", "client[_-]?secret",
"cli[_-]?e2e[_-]?cma[_-]?token",
"claimr[_-]?token", "claimr[_-]?superuser", "claimr[_-]?db",
"claimr[_-]?database",
"ci[_-]?user[_-]?token", "ci[_-]?server[_-]?name",
"ci[_-]?registry[_-]?user", "ci[_-]?project[_-]?url",
"ci[_-]?deploy[_-]?password",
"chrome[_-]?refresh[_-]?token", "chrome[_-]?client[_-]?secret",
"cheverny[_-]?token", "cf[_-]?password", "certificate[_-]?password",
"censys[_-]?secret",
"cattle[_-]?secret[_-]?key", "cattle[_-]?agent[_-]?instance[_-]?auth",
"cattle[_-]?access[_-]?key", "cargo[_-]?token",
"cache[_-]?s3[_-]?secret[_-]?key",
"bx[_-]?username", "bx[_-]?password",
"bundlesize[_-]?github[_-]?token", "built[_-]?branch[_-]?deploy[_-]?key",
"bucketeer[_-]?aws[_-]?secret[_-]?access[_-]?key",
"bucketeer[_-]?aws[_-]?access[_-]?key[_-]?id",
"browserstack[_-]?access[_-]?key", "browser[_-]?stack[_-]?access[_-]?key",
"brackets[_-]?repo[_-]?oauth[_-]?token",
"bluemix[_-]?username", "bluemix[_-]?pwd", "bluemix[_-]?password",
"bluemix[_-]?pass[_-]?prod", "bluemix[_-]?pass",
"bluemix[_-]?auth", "bluemix[_-]?api[_-]?key",
"bintraykey", "bintray[_-]?token", "bintray[_-]?key",
"bintray[_-]?gpg[_-]?password", "bintray[_-]?apikey", "bintray[_-]?api[_-]?key",
"b2[_-]?bucket", "b2[_-]?app[_-]?key",
"awssecretkey", "awscn[_-]?secret[_-]?access[_-]?key",
"awscn[_-]?access[_-]?key[_-]?id", "awsaccesskeyid",
"aws[_-]?ses[_-]?secret[_-]?access[_-]?key",
"aws[_-]?ses[_-]?access[_-]?key[_-]?id", "aws[_-]?secrets",
"aws[_-]?secret[_-]?key", "aws[_-]?secret[_-]?access[_-]?key",
"aws[_-]?secret", "aws[_-]?key",
"aws[_-]?config[_-]?secretaccesskey", "aws[_-]?config[_-]?accesskeyid",
"aws[_-]?access[_-]?key[_-]?id", "aws[_-]?access[_-]?key", "aws[_-]?access",
"author[_-]?npm[_-]?api[_-]?key", "author[_-]?email[_-]?addr",
"auth0[_-]?client[_-]?secret", "auth0[_-]?api[_-]?clientsecret",
"auth[_-]?token",
"assistant[_-]?iam[_-]?apikey",
"artifacts[_-]?secret", "artifacts[_-]?key", "artifacts[_-]?bucket",
"artifacts[_-]?aws[_-]?secret[_-]?access[_-]?key",
"artifacts[_-]?aws[_-]?access[_-]?key[_-]?id", "artifactory[_-]?key",
"argos[_-]?token", "apple[_-]?id[_-]?password", "appclientsecret",
"app[_-]?token", "app[_-]?secrete", "app[_-]?report[_-]?token[_-]?key",
"app[_-]?bucket[_-]?perm",
"apigw[_-]?access[_-]?token", "apiary[_-]?api[_-]?key",
"api[_-]?secret", "api[_-]?key[_-]?sid", "api[_-]?key[_-]?secret",
"api[_-]?key", "aos[_-]?sec", "aos[_-]?key",
"ansible[_-]?vault[_-]?password", "android[_-]?docs[_-]?deploy[_-]?token",
"anaconda[_-]?token",
"amazon[_-]?secret[_-]?access[_-]?key", "amazon[_-]?bucket[_-]?name",
"alicloud[_-]?secret[_-]?key", "alicloud[_-]?access[_-]?key",
"alias[_-]?pass",
"algolia[_-]?search[_-]?key[_-]?1", "algolia[_-]?search[_-]?key",
"algolia[_-]?search[_-]?api[_-]?key",
"algolia[_-]?api[_-]?key[_-]?search", "algolia[_-]?api[_-]?key[_-]?mcm",
"algolia[_-]?api[_-]?key", "algolia[_-]?admin[_-]?key[_-]?mcm",
"algolia[_-]?admin[_-]?key[_-]?2", "algolia[_-]?admin[_-]?key[_-]?1",
"adzerk[_-]?api[_-]?key", "admin[_-]?email",
"account[_-]?sid", "access[_-]?token", "access[_-]?secret",
"access[_-]?key[_-]?secret",
"account", "password", "username",
]
# 通配符型规则
_NUCLEI_WILDCARD_SOURCES = [
r"""[\w_-]*?password[\w_-]*?""",
r"""[\w_-]*?username[\w_-]*?""",
r"""[\w_-]*?accesskey[\w_-]*?""",
r"""[\w_-]*?secret[\w_-]*?""",
r"""[\w_-]*?bucket[\w_-]*?""",
r"""[\w_-]*?token[\w_-]*?""",
]
# 特殊格式的规则 (非 key=value 型)
_NUCLEI_SPECIAL_PATTERNS = [
r"""[\"']?[-]+BEGIN \w+ PRIVATE KEY[-]+""",
r"""[\"']?huawei\.oss\.(ak|sk|bucket\.name|endpoint|local\.path)[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
r"""[\"']?private[_-]?key(?:[_-]?id)?[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
r"""[\"']?account[_-]?(?:name|key)?[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
r"""LTAI[A-Za-z\d]{12,30}""",
r"""AKID[A-Za-z\d]{13,40}""",
r"""JDC_[0-9A-Z]{25,40}""",
r"""[\"']?(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}[\"']?""",
r"""(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}""",
r"""AKLT[a-zA-Z0-9\-_]{16,28}""",
r"""AIza[0-9A-Za-z_\-]{35}""",
r"""[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}""",
r"""[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}""",
r"""[\"'\[]*[Aa]uthorization[\"'\]]*\s*[:=]\s*['"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}['"]?""",
r"""(glpat-[a-zA-Z0-9\-=_]{20,22})""",
r"""((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})""",
r"""APID[a-zA-Z0-9]{32,42}""",
r"""[\"'](wx[a-z0-9]{15,18})[\"']""",
r"""[\"'](ww[a-z0-9]{15,18})[\"']""",
r"""[\"'](gh_[a-z0-9]{11,13})[\"']""",
r"""(?:admin_?pass|password|[a-z]{3,15}_?password|user_?pass|user_?pwd|admin_?pwd)\\?['\"]*\s*[:=]\s*\\?['"][a-z0-9!@#$%&*]{5,20}\\?['"]""",
r"""https:\/\/qyapi\.weixin\.qq\.com\/cgi\-bin\/webhook\/send\?key=[a-zA-Z0-9\-]{25,50}""",
r"""https:\/\/oapi\.dingtalk\.com\/robot\/send\?access_token=[a-z0-9]{50,80}""",
r"""https:\/\/open\.feishu\.cn\/open\-apis\/bot\/v2\/hook\/[a-z0-9\-]{25,50}""",
r"""https:\/\/hooks\.slack\.com\/services\/[a-zA-Z0-9\-_]{6,12}\/[a-zA-Z0-9\-_]{6,12}\/[a-zA-Z0-9\-_]{15,24}""",
r"""eyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}""",
r"""glc_[A-Za-z0-9\-_+/]{32,200}={0,2}""",
r"""glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}""",
r"""[\"']?air[-_]?table[-_]?api[-_]?key[\"']?[=:][\"']?.+[\"']""",
r"""[\"']?jdbc:mysql[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
]
def _build_nuclei_patterns() -> List[re.Pattern]:
"""编译所有nuclei正则为Python Pattern对象 (与heartk_fast.py完全一致)"""
compiled = []
all_kv_keys = list(_NUCLEI_REGEX_SOURCES) + list(_NUCLEI_WILDCARD_SOURCES)
batch_size = 120
for i in range(0, len(all_kv_keys), batch_size):
batch = all_kv_keys[i:i + batch_size]
alternation = '|'.join(batch)
pat = r"""[\"']?(?:""" + alternation + r""")[\"']?[^\S\r\n]*[=:][^\S\r\n]*[\"']?[\w-]+[\"']?"""
compiled.append(re.compile(pat, re.IGNORECASE))
for pat_str in _NUCLEI_SPECIAL_PATTERNS:
try:
compiled.append(re.compile(pat_str, re.IGNORECASE))
except re.error:
compiled.append(re.compile(pat_str))
return compiled
NUCLEI_PATTERNS = _build_nuclei_patterns()
# ============================================================
# 2. 核心分析函数 (与 heartk_fast.py 完全一致)
# ============================================================
def strip_quotes(items: List[str]) -> List[str]:
"""移除字符串两端的引号"""
result = []
for item in items:
s = 0
e = len(item)
if item and item[0] in ("'", '"'):
s = 1
if item and item[-1] in ("'", '"'):
e -= 1
result.append(item[s:e])
return result
def extract_info(data: str) -> Dict[str, Optional[List[str]]]:
"""从文本内容中提取各类敏感信息 (对应JS中的 extract_info + get_secret)"""
result: Dict[str, Optional[List[str]]] = {}
# 运行各分类正则
for key, pattern in EXTRACT_PATTERNS.items():
matches = pattern.findall(data)
if matches:
# findall 对于有组的正则返回元组,取第一个完整匹配或全匹配
if isinstance(matches[0], tuple):
# 需要完整匹配,用 finditer
matches = [m.group(0) for m in pattern.finditer(data)]
result[key] = matches if matches else None
else:
result[key] = None
# 从URL中额外提取IP/域名 (对应JS中 extract_info 末尾逻辑)
if result.get('url'):
ip_pat = re.compile(r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}""")
for url in result['url']:
ip_matches = ip_pat.findall(url)
if ip_matches:
full_matches = [m.group(0) for m in ip_pat.finditer(url)]
if result['ip'] is None:
result['ip'] = full_matches
else:
result['ip'].extend(full_matches)
# 运行nuclei秘密检测
secrets = []
for pat in NUCLEI_PATTERNS:
try:
matches = pat.findall(data)
if matches:
if isinstance(matches[0], tuple):
for m in pat.finditer(data):
secrets.append(m.group(0))
else:
secrets.extend(matches)
except Exception:
pass
result['secret'] = secrets if secrets else None
return result
def init_source(source: str) -> List[str]:
"""从HTML中提取href和src属性值 (对应JS中的 init_source)"""
target_list = []
# href="..."
for m in re.finditer(r"""href=['\"](.+?)['\"]""", source):
target_list.append(m.group(1))
# src="..."
for m in re.finditer(r"""src=['\"](.+?)['\"]""", source):
target_list.append(m.group(1))
return list(set(target_list))
def collect_static(items: List[str], static_list: List[str]) -> tuple:
"""分离静态资源"""
remaining = []
for item in items:
is_static = False
for ext in STATIC_EXTENSIONS:
if ext in item:
if ext == '.js' and '.jsp' in item:
continue
is_static = True
if item not in static_list:
static_list.append(item)
break
if not is_static:
remaining.append(item)
return remaining, static_list
def get_info(content: str) -> Dict[str, Optional[List[str]]]:
"""完整分析一个文件内容"""
tmp_data = extract_info(content)
source_links = init_source(content)
static_data: List[str] = []
if source_links:
_, static_data = collect_static(source_links, static_data)
for key in ['domain', 'path', 'url']:
if tmp_data.get(key):
_, static_data = collect_static(tmp_data[key], static_data)
for key in ['domain', 'path', 'url']:
if tmp_data.get(key):
tmp_data[key], _ = collect_static(tmp_data[key], list(static_data))
tmp_data['static'] = static_data if static_data else None
# 去引号 (secret 保留原始)
not_sub_keys = {'secret'}
for key in CATEGORIES:
if key in tmp_data and tmp_data[key] is not None and key not in not_sub_keys:
tmp_data[key] = strip_quotes(tmp_data[key])
return tmp_data
# ============================================================
# 3. 文件扫描
# ============================================================
def detect_encoding(filepath: str) -> Optional[str]:
"""检测文件编码"""
if chardet is None:
return "utf-8"
try:
with open(filepath, 'rb') as f:
raw = f.read(65536)
if not raw:
return None
result = chardet.detect(raw)
return result.get('encoding')
except Exception:
return None
def scan_single_file(filepath: str) -> Optional[Dict[str, Optional[List[str]]]]:
"""扫描单个文件,返回分析结果"""
encoding = detect_encoding(filepath)
if not encoding:
return None
try:
with open(filepath, 'r', encoding=encoding, errors='ignore') as f:
content = f.read()
if not content.strip():
return None
return get_info(content)
except Exception:
return None
def scan_single_file_with_custom(args):
"""带自定义正则的扫描 (用于进程池)"""
filepath, custom_patterns_raw = args
# 基础扫描
result = scan_single_file(filepath)
if result is None:
result = {}
# 自定义正则扫描
if custom_patterns_raw:
encoding = detect_encoding(filepath)
if not encoding:
return filepath, result if result else None
try:
with open(filepath, 'r', encoding=encoding, errors='ignore') as f:
content = f.read()
except Exception:
return filepath, result if result else None
if content.strip():
for name, pat_str in custom_patterns_raw.items():
try:
pat = re.compile(pat_str, re.IGNORECASE)
matches = [m.group(0) for m in pat.finditer(content)]
if matches:
result[f"custom_{name}"] = matches
except Exception:
pass
return filepath, result if result else None
_SCAN_EXTS = {'.js', '.html', '.htm', '.json', '.css', '.wxml', '.wxss', '.txt', '.xml', '.svg', '.ts', '.jsx', '.tsx', '.md', '.yaml', '.yml', '.cfg', '.ini', '.conf', '.env', '.properties'}
def collect_files(path: str) -> List[str]:
"""递归收集所有可扫描的文本文件"""
files = []
for root, dirs, filenames in os.walk(path):
for fn in filenames:
ext = os.path.splitext(fn)[1].lower()
if ext in _SCAN_EXTS:
files.append(os.path.join(root, fn))
return files
def merge_results(all_info: Dict, file_result: Dict):
"""合并单文件结果到总结果"""
for key, value in file_result.items():
if value is None:
continue
if all_info.get(key) is None:
all_info[key] = list(value)
else:
all_info[key].extend(value)
def dedup_and_sort(all_info: Dict):
"""去重并排序"""
for key in list(all_info.keys()):
if all_info.get(key) and isinstance(all_info[key], list):
all_info[key] = sorted(set(all_info[key]))
# ============================================================
# 4. Extractor 类 (GUI 接口)
# ============================================================
class Extractor:
"""敏感信息提取引擎 — GUI 接口"""
def __init__(self):
self.custom_patterns: Dict[str, str] = {} # {名称: 正则}
def add_custom_pattern(self, name: str, regex_str: str):
self.custom_patterns[name] = regex_str
def remove_custom_pattern(self, name: str):
self.custom_patterns.pop(name, None)
def scan_directory(self, path: str, num_workers: int = 0,
on_progress: Optional[Callable] = None) -> Dict[str, Any]:
"""扫描目录,返回合并后的结果"""
files = collect_files(path)
if not files:
return {"files_scanned": 0, "elapsed": 0, "results": {k: [] for k in CATEGORIES},
"custom_results": {}}
if num_workers <= 0:
num_workers = min(os.cpu_count() or 4, len(files))
num_workers = max(1, min(num_workers, 16))
all_info: Dict[str, Optional[List[str]]] = {k: None for k in CATEGORIES}
custom_results: Dict[str, List[str]] = {} # 自定义正则结果
done_count = 0
total = len(files)
start_time = time.time()
custom_raw = dict(self.custom_patterns) if self.custom_patterns else {}
if num_workers == 1 or total <= 3:
for fpath in files:
_, result = scan_single_file_with_custom((fpath, custom_raw))
done_count += 1
if result:
# 分离自定义结果
for key in list(result.keys()):
if key.startswith("custom_"):
cat_name = key[7:] # 去掉 "custom_" 前缀
if cat_name not in custom_results:
custom_results[cat_name] = []
custom_results[cat_name].extend(result.pop(key))
merge_results(all_info, result)
if on_progress:
on_progress(done_count, total)
else:
args_list = [(f, custom_raw) for f in files]
with ProcessPoolExecutor(max_workers=num_workers) as executor:
future_to_file = {executor.submit(scan_single_file_with_custom, a): a[0] for a in args_list}
for future in as_completed(future_to_file):
done_count += 1
try:
fpath, result = future.result()
if result:
for key in list(result.keys()):
if key.startswith("custom_"):
cat_name = key[7:]
if cat_name not in custom_results:
custom_results[cat_name] = []
custom_results[cat_name].extend(result.pop(key))
merge_results(all_info, result)
except Exception:
pass
if on_progress:
on_progress(done_count, total)
# domain 合并到 url
if all_info.get('domain'):
if all_info.get('url'):
all_info['url'].extend(all_info['domain'])
else:
all_info['url'] = list(all_info['domain'])
# 去重排序
dedup_and_sort(all_info)
for k in custom_results:
custom_results[k] = sorted(set(custom_results[k]))
elapsed = time.time() - start_time
return {
"files_scanned": total,
"elapsed": round(elapsed, 2),
"results": {k: all_info.get(k) or [] for k in CATEGORIES},
"custom_results": custom_results,
}
@staticmethod
def export_json(scan_result: Dict, output_path: str):
"""导出 JSON (包含自定义正则结果)"""
clean = {}
for key in CATEGORIES:
clean[key] = scan_result.get("results", {}).get(key, [])
# 自定义正则结果
for name, items in scan_result.get("custom_results", {}).items():
clean[name] = items
clean["_meta"] = {
"files_scanned": scan_result.get("files_scanned", 0),
"elapsed": scan_result.get("elapsed", 0),
}
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(clean, f, ensure_ascii=False, indent=2)
@staticmethod
def export_html(scan_result: Dict, output_path: str):
"""生成 HTML 报告 (包含自定义正则结果)"""
data = dict(scan_result.get("results", {}))
custom = scan_result.get("custom_results", {})
# 合并自定义到 data 以供 JS 使用
for name, items in custom.items():
data[name] = items
data_json = json.dumps(data, ensure_ascii=False)
# 所有类别 key 列表 (用于 JS)
all_keys = list(CATEGORIES) + list(custom.keys())
keys_js = json.dumps(all_keys, ensure_ascii=False)
popup_js = r"""
var key = """ + keys_js + r""";
let messages = {"popupCopy": "复制", "popupCopyurl": "复制URL"};
function getMessage(key) { return messages[key] || ""; }
function init_copy() {
var elements = document.getElementsByClassName("copy");
if(elements){
for (var i=0, len=elements.length|0; i<len; i=i+1|0) {
elements[i].textContent = getMessage("popupCopy");
let ele_name = elements[i].name;
let ele_id = elements[i].id;
if (ele_id == "popupCopyurl") elements[i].textContent = getMessage("popupCopyurl");
elements[i].onclick=function () {
var inp = document.createElement('textarea');
document.body.appendChild(inp);
var copytext = document.getElementById(ele_name).textContent;
inp.value = copytext; inp.select();
document.execCommand('copy',false); inp.remove();
}
}
}
}
function show_info(result_data) {
for (var k in key){
if (result_data[key[k]]){
let container = document.getElementById(key[k]);
if (!container) continue;
while((ele = container.firstChild)) ele.remove();
container.style.whiteSpace = "pre";
for (var i in result_data[key[k]]){
let tips = document.createElement("div");
tips.setAttribute("class", "tips");
let link = document.createElement("a");
link.appendChild(tips);
let span = document.createElement("span");
span.textContent = result_data[key[k]][i]+'\n';
container.appendChild(link);
container.appendChild(span);
}
}
}
}
init_copy();
show_info(""" + data_json + ')'
cat_labels = {
'ip': 'IP', 'ip_port': 'IP:PORT', 'domain': '域名',
'sfz': '身份证', 'mobile': '手机号', 'mail': '邮箱',
'jwt': 'JWT', 'algorithm': '加密算法', 'secret': 'Secret/密钥',
'path': 'Path', 'incomplete_path': 'IncompletePath',
'url': 'URL', 'static': 'StaticUrl'
}
left_cats = ['ip', 'ip_port', 'domain', 'sfz', 'mobile', 'mail', 'jwt', 'algorithm', 'secret']
right_cats = ['path', 'incomplete_path', 'url', 'static']
left_html = ""
for cat in left_cats:
label = cat_labels.get(cat, cat)
count = len(data.get(cat, []))
left_html += f'<div class="cat_title">{label} ({count})</div><button class="copy" name="{cat}">复制</button>\n<p id="{cat}" style="word-break:break-word;">🈚️</p>\n'
right_html = ""
for cat in right_cats:
label = cat_labels.get(cat, cat)
count = len(data.get(cat, []))
right_html += f'<div class="cat_title">{label} ({count})</div><button class="copy" name="{cat}">复制</button>\n<p id="{cat}">🈚️</p>\n'
# 自定义正则类别
custom_html = ""
for name, items in custom.items():
count = len(items)
custom_html += f'<div class="cat_title">{name} ({count})</div><button class="copy" name="{name}">复制</button>\n<p id="{name}" style="word-break:break-word;">🈚️</p>\n'
meta = scan_result.get("files_scanned", 0)
elapsed = scan_result.get("elapsed", 0)
total_findings = sum(len(data.get(k, [])) for k in all_keys)
report_html = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>敏感信息提取报告</title></head>
<body style="width:860px; font-size: 14px; font-family: 'Microsoft YaHei', sans-serif; margin: 20px auto;">
<div style="margin-bottom: 16px;">
<div style="display:inline-block; padding:6px 16px; background:#000; color:#fff; border-radius:4px; font-weight:bold;">
First 敏感信息提取报告
</div>
</div>
<div style="display:flex; gap:16px;">
<div style="flex:1; border-right:1px solid #e8e8e8; padding-right:16px;">
{left_html}
</div>
<div style="flex:1.5;">
{right_html}
</div>
</div>
{"<div style='margin-top:16px; border-top:1px solid #e8e8e8; padding-top:12px;'><div class='cat_title' style='margin-bottom:8px;'>自定义规则结果</div>" + custom_html + "</div>" if custom_html else ""}
<script>
{popup_js}
</script>
<style>
.copy {{ border:none; background:#fff; float:right; cursor:pointer; color:#16a34a; }}
.cat_title {{ font-size:15px; font-weight:bold; border-left:4px solid #16a34a; padding-left:6px; margin-top:12px; }}
.tips {{ display:inline-block; border-top:0.2px solid; border-right:0.2px solid; width:8px; height:8px; border-color:#EA6000; transform:rotate(-135deg); }}
a {{ text-decoration:none; color:#333; }}
p {{ word-break:break-all; white-space:pre-wrap; font-size:13px; }}
</style>
</html>"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(report_html)
@staticmethod
def get_summary(scan_result: Dict) -> Dict[str, int]:
"""返回各分类的计数 (含自定义)"""
summary = {}
results = scan_result.get("results", {})
for key in CATEGORIES:
summary[key] = len(results.get(key, []))
for name, items in scan_result.get("custom_results", {}).items():
summary[name] = len(items)
return summary
@staticmethod
def get_category_label(key: str) -> str:
labels = {
'sfz': '身份证', 'mobile': '手机号', 'mail': '邮箱',
'ip': 'IP地址', 'ip_port': 'IP端口', 'domain': '域名',
'path': '路径', 'incomplete_path': '不完整路径',
'url': 'URL', 'jwt': 'JWT', 'algorithm': '加密算法',
'secret': '密钥/凭证', 'static': '静态资源'
}
return labels.get(key, key)
@staticmethod
def get_all_builtin_patterns() -> Dict[str, str]:
"""获取所有内置正则的字符串表示 (供 UI 显示)"""
result = {}
# extract patterns
for key, pat in EXTRACT_PATTERNS.items():
result[f"[提取] {key}"] = pat.pattern
# nuclei kv keys count
result["[密钥] key=value 关键字"] = f"({len(_NUCLEI_REGEX_SOURCES)} 个关键字匹配)"
# nuclei wildcards
for i, pat in enumerate(_NUCLEI_WILDCARD_SOURCES):
result[f"[密钥] 通配符 #{i+1}"] = pat
# nuclei specials
for i, pat in enumerate(_NUCLEI_SPECIAL_PATTERNS):
result[f"[密钥] 特殊规则 #{i+1}"] = pat
return result
+28
View File
@@ -83,6 +83,34 @@ class MiniProgramNavigator:
await self._ensure() await self._ensure()
await self.engine.evaluate_js(f"window.nav.back({delta})", timeout=5.0) await self.engine.evaluate_js(f"window.nav.back({delta})", timeout=5.0)
async def refresh_page(self):
"""刷新当前小程序页面:获取当前路由后 reLaunch 到同一页面。"""
await self._ensure()
result = await self.engine.evaluate_js(
"(function(){"
"try{"
"var nav=window.nav;"
"if(!nav||!nav.wxFrame)return JSON.stringify({err:'no nav'});"
"var p=nav.wxFrame.getCurrentPages();"
"if(!p||!p.length)return JSON.stringify({err:'no page'});"
"var cur=p[p.length-1];"
"var route=cur.route||cur.__route__||'';"
"if(!route)return JSON.stringify({err:'no route'});"
"var url='/'+route;"
"var opts=cur.options||{};"
"var qs=Object.keys(opts).map(function(k){return k+'='+opts[k]}).join('&');"
"if(qs)url+='?'+qs;"
"nav.wxFrame.wx.reLaunch({url:url,"
"success:function(){},fail:function(e){"
"nav.wxFrame.wx.redirectTo({url:url,success:function(){},fail:function(){}})"
"}});"
"return JSON.stringify({ok:true,route:route})"
"}catch(e){return JSON.stringify({err:e.message})}"
"})()",
timeout=5.0,
)
return self._extract_value(result) or ""
async def get_current_route(self): async def get_current_route(self):
"""Get current page route via the detected wxFrame.""" """Get current page route via the detected wxFrame."""
await self._ensure() await self._ensure()
Binary file not shown.
Binary file not shown.
+242
View File
@@ -0,0 +1,242 @@
"""
wxapkg.py — 微信小程序 wxapkg 解密 + 解包 (WeChat 4.0+)
纯 Python 实现,基于 KillWxapkg 算法
加密格式:
- Magic: "V1MMWX" (6 bytes)
- AES-256-CBC 加密区: file[6:1030] → 解密取前 1023 bytes
- XOR 加密区: file[1030:] → 逐字节 XOR appID[-2]
- Key = PBKDF2(appID, "saltiest", 1000, 32, SHA1)
- IV = "the iv: 16 bytes"
解包格式:
- 0xBE marker (1 byte)
- info1 (4 bytes, big-endian uint32)
- indexInfoLength (4 bytes)
- bodyInfoLength (4 bytes)
- 0xED marker (1 byte)
- fileCount (4 bytes)
- index entries: nameLen(4) + name(nameLen) + offset(4) + size(4)
- body data
"""
import hashlib
import os
import struct
from pathlib import Path
from typing import List, Optional, Tuple
try:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
except ImportError:
try:
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import unpad
except ImportError:
AES = None
unpad = None
MAGIC = b"V1MMWX"
SALT = b"saltiest"
IV = b"the iv: 16 bytes"
PBKDF2_ITER = 1000
KEY_LEN = 32
def _derive_key(app_id: str) -> bytes:
"""PBKDF2-SHA1 派生 AES-256 密钥"""
return hashlib.pbkdf2_hmac("sha1", app_id.encode("utf-8"), SALT, PBKDF2_ITER, KEY_LEN)
def decrypt_wxapkg(data: bytes, app_id: str) -> bytes:
"""解密单个 wxapkg 文件内容,返回解密后的 bytes"""
if AES is None:
raise ImportError("需要安装 pycryptodome: pip install pycryptodome")
if len(data) < 1030:
raise ValueError("文件太小,不是有效的加密 wxapkg")
# 检查 magic header
if data[:6] != MAGIC:
# 可能是未加密的 wxapkg,直接返回
if data[0:1] == b'\xbe':
return data
raise ValueError(f"未知文件格式 (magic: {data[:6].hex()})")
key = _derive_key(app_id)
# AES-256-CBC 解密前 1024 bytes (file[6:1030])
cipher = AES.new(key, AES.MODE_CBC, IV)
decrypted_header = cipher.decrypt(data[6:1030])
# 取前 1023 bytes(去掉 PKCS7 padding 的最后一个字节)
decrypted_header = decrypted_header[:1023]
# XOR 解密剩余部分
xor_key = ord(app_id[-2]) if len(app_id) >= 2 else 0
tail = data[1030:]
xor_decrypted = bytes(b ^ xor_key for b in tail)
return decrypted_header + xor_decrypted
def unpack_wxapkg(data: bytes) -> List[Tuple[str, bytes]]:
"""解包 wxapkg,返回 [(文件路径, 文件内容), ...]"""
if len(data) < 14:
raise ValueError("数据太短,不是有效的 wxapkg")
pos = 0
# 0xBE marker
marker1 = data[pos]
if marker1 != 0xBE:
raise ValueError(f"无效的 wxapkg 格式 (marker1: 0x{marker1:02x}, 期望 0xBE)")
pos += 1
# info1 (4 bytes)
_info1 = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
# indexInfoLength (4 bytes)
index_info_length = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
# bodyInfoLength (4 bytes)
_body_info_length = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
# 0xED marker
marker2 = data[pos]
if marker2 != 0xED:
raise ValueError(f"无效的 wxapkg 格式 (marker2: 0x{marker2:02x}, 期望 0xED)")
pos += 1
# fileCount (4 bytes)
file_count = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
files = []
for _ in range(file_count):
if pos + 4 > len(data):
break
# nameLen (4 bytes)
name_len = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
if pos + name_len > len(data):
break
# name (nameLen bytes)
name = data[pos:pos + name_len].decode("utf-8", errors="replace")
pos += name_len
if pos + 8 > len(data):
break
# offset (4 bytes)
offset = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
# size (4 bytes)
size = struct.unpack(">I", data[pos:pos + 4])[0]
pos += 4
files.append((name, offset, size))
# 提取文件内容
result = []
for name, offset, size in files:
if offset + size <= len(data):
content = data[offset:offset + size]
result.append((name, content))
return result
def extract_wxapkg(wxapkg_path: str, output_dir: str, app_id: str) -> List[str]:
"""解密并解包 wxapkg 到指定目录,返回提取的文件路径列表"""
with open(wxapkg_path, "rb") as f:
raw = f.read()
decrypted = decrypt_wxapkg(raw, app_id)
files = unpack_wxapkg(decrypted)
extracted = []
for name, content in files:
# 规范化路径,防止路径穿越
name = name.lstrip("/").replace("\\", "/")
if ".." in name:
continue
out_path = os.path.join(output_dir, name)
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, "wb") as f:
f.write(content)
extracted.append(out_path)
return extracted
def find_wxapkg_files(packages_dir: str) -> List[dict]:
"""扫描 packages 目录,返回所有 wxapkg 文件信息
目录结构: packages/{appid}/ 下有 .wxapkg 文件
返回: [{"appid": str, "path": str, "name": str, "size": int}, ...]
"""
results = []
if not os.path.isdir(packages_dir):
return results
for entry in os.listdir(packages_dir):
app_dir = os.path.join(packages_dir, entry)
if not os.path.isdir(app_dir):
continue
# 检查是否像 appid (wx开头 或 纯字母数字)
appid = entry
for root, dirs, filenames in os.walk(app_dir):
for fn in filenames:
if fn.endswith(".wxapkg"):
fpath = os.path.join(root, fn)
results.append({
"appid": appid,
"path": fpath,
"name": fn,
"size": os.path.getsize(fpath),
})
return results
def get_default_packages_dir() -> Optional[str]:
"""获取默认的微信小程序包路径(Windows / macOS)"""
import sys
user_home = os.path.expanduser("~")
if sys.platform == "darwin":
# macOS: ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/radium/users/{wxid}/applet/packages
users_dir = os.path.join(
user_home, "Library", "Containers", "com.tencent.xinWeChat",
"Data", "Documents", "app_data", "radium", "users"
)
if not os.path.isdir(users_dir):
return None
# 找最近修改的用户目录
user_dirs = [
os.path.join(users_dir, d)
for d in os.listdir(users_dir)
if os.path.isdir(os.path.join(users_dir, d)) and not d.startswith(".")
]
for ud in sorted(user_dirs, key=os.path.getmtime, reverse=True):
pkg_dir = os.path.join(ud, "applet", "packages")
if os.path.isdir(pkg_dir):
return pkg_dir
return None
elif os.name == "nt":
pkg_dir = os.path.join(
user_home, "AppData", "Roaming", "Tencent",
"xwechat", "radium", "Applet", "packages"
)
if os.path.isdir(pkg_dir):
return pkg_dir
return None
-87
View File
@@ -1,87 +0,0 @@
// ==UserScript==
// @name Bypass_Debugger
// @namespace https://github.com/0xsdeo/Bypass_Debugger
// @version 2024-12-06
// @description Bypass new Function --> debugger && constructor --> debugger && eval --> debugger
// @author 0xsdeo
// @match http://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
globalThis.__INJECTED__ = true;
// eval may not exist in WeChat miniapp context
if (typeof eval !== 'undefined') {
try {
let temp_eval = eval;
window.eval = function () {
if (typeof arguments[0] == "string") {
arguments[0] = arguments[0].replaceAll(/debugger/g, '');
}
return temp_eval(...arguments);
}
} catch(e) {}
}
try {
let OrigFunction = Function;
let NewFunction = function () {
for (let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] == "string") {
arguments[i] = arguments[i].replaceAll(/debugger/g, '');
}
}
return OrigFunction(...arguments);
}
// Try to set prototype, skip if read-only (WeChat freezes Function)
try { NewFunction.prototype = OrigFunction.prototype; } catch(e) {}
try {
NewFunction.prototype.constructor = function () {
for (let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] == "string") {
arguments[i] = arguments[i].replaceAll(/debugger/g, '');
}
}
return OrigFunction(...arguments);
}
NewFunction.prototype.constructor.prototype = NewFunction.prototype;
} catch(e) {}
// Replace global Function
try { window.Function = NewFunction; } catch(e) {}
try { globalThis.Function = NewFunction; } catch(e) {}
try { Object.freeze(NewFunction); } catch(e) {}
} catch(e) {}
// Also patch setTimeout/setInterval which can take string arguments with debugger
try {
let origSetTimeout = setTimeout;
window.setTimeout = function(fn, delay) {
if (typeof fn === 'string') {
fn = fn.replaceAll(/debugger/g, '');
}
return origSetTimeout.apply(this, arguments);
};
} catch(e) {}
try {
let origSetInterval = setInterval;
window.setInterval = function(fn, delay) {
if (typeof fn === 'string') {
fn = fn.replaceAll(/debugger/g, '');
}
return origSetInterval.apply(this, arguments);
};
} catch(e) {}
console.log('[Bypass_Debugger] hooks installed');
window.__BYPASS_DEBUGGER__ = true;
Object.freeze(Function);
})();
Generated
+235
View File
@@ -0,0 +1,235 @@
version = 1
revision = 2
requires-python = ">=3.10"
[[package]]
name = "first"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "frida" },
{ name = "protobuf" },
{ name = "pycryptodome" },
{ name = "pyside6" },
{ name = "websockets" },
]
[package.metadata]
requires-dist = [
{ name = "frida", specifier = ">=17.0.0" },
{ name = "protobuf", specifier = ">=4.0.0" },
{ name = "pycryptodome" },
{ name = "pyside6", specifier = ">=6.5.0" },
{ name = "websockets", specifier = ">=12.0" },
]
[[package]]
name = "frida"
version = "17.9.3"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/c9/f4e98eaca54bf2f52dad0e6ce532e72bb556c4e54b7d2cd111ac48a9014e/frida-17.9.3.tar.gz", hash = "sha256:cf81bbb3e8901ec2dff1d74aaf2e1b2be10ca602a040fb2ef1ffd8300ffdaf3c", size = 927621, upload-time = "2026-04-28T15:55:41.574Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/e0/8c125a8ee554bc9bc2217ffcaad359402ca4e431c11da82c440214dc4186/frida-17.9.3-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:535df74837d12873d10b7c7445febea75968e7f02a84042e4c32261d7c08328c", size = 22878819, upload-time = "2026-04-28T15:55:00.169Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/b2/f0971159443fbb2acc776a1dd520b0455e4d373572917912007c98ad8834/frida-17.9.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d37eca0d3425d3de0998c5c028c64765f55c482b292dfd1fb00baf6c1326bf59", size = 32253853, upload-time = "2026-04-28T15:55:05.152Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/fd/64a5e77f1539e53f4d3d87532d43a65e6d539415ce886b0686ccdcb1d9cd/frida-17.9.3-cp37-abi3-manylinux1_i686.whl", hash = "sha256:92ebe2d351adbf1fe17f4dfeb81dd2cbdf896722e45839e8a256b37b6d4ee5c1", size = 20688476, upload-time = "2026-04-28T15:55:08.855Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/6c/807b1c03b309d0f4a4ddd31492323bf97173d67c2296477cffc34cbbe67d/frida-17.9.3-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:2f947a55e470e105c699f26f1f0a324e1747959c83b4f2f8f110a4698a83eac8", size = 32917515, upload-time = "2026-04-28T15:55:11.588Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/86/7232cdc26f7bcbb5006049996687faf89bb526b100833791bc57d5c0c355/frida-17.9.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0e3bca26c6b9accb2374f5cc8fb2b7ea7fc9be538228497ca5660111fb85ed2a", size = 21497450, upload-time = "2026-04-28T15:55:14.548Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/3a/99b55f0790cf6c4babc26eeb189a9aa83968c8b909d83540d0ce952b9390/frida-17.9.3-cp37-abi3-manylinux2014_armv7l.whl", hash = "sha256:5ae96e22dee0219b3361f37b34e5b65d23818282aa603e416451c1ff2a637f67", size = 19335314, upload-time = "2026-04-28T15:55:17.457Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/7a/770f6c54a21fc0e7858045459b953beb64234046275b1e62148fd0e07c92/frida-17.9.3-cp37-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:5843b4e1cb14e5ef8405e95d747202d2d9c6459ea1a7c78287e46d186dd8530f", size = 21497451, upload-time = "2026-04-28T15:55:20.274Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/a2/fa94990bb6df6a5bf0a4ab2c2a9d54e273934813bfb3b3c78bb6aa1b4959/frida-17.9.3-cp37-abi3-manylinux_2_17_armv7l.whl", hash = "sha256:43d7ec9d33de2837447fb13516c25683bfac19f683a52b36e42a34735eb54f7f", size = 19335314, upload-time = "2026-04-28T15:55:23.012Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/47/e26468826287b2463b0513448f32d8bbb9700ee3ed6dca41445ecdc95828/frida-17.9.3-cp37-abi3-manylinux_2_5_i686.whl", hash = "sha256:ed8af3db918918d4954e9892217fbbafef93a65ed2d41cf07aed494cc4b9f64b", size = 20688481, upload-time = "2026-04-28T15:55:25.475Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/cd/3c11f8c301bff714fbda8cb00c3578af2a1b6d8d525b5a93f7058eba592e/frida-17.9.3-cp37-abi3-manylinux_2_5_x86_64.whl", hash = "sha256:25265b0591db9ff2b8639bba7698338a4065cf2bdc82f7a14a12689731a1da91", size = 32917520, upload-time = "2026-04-28T15:55:28.22Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/c0/7948bc65b7a33f4d103d5d99ab55066c1ef65345bad0e2404a4b3d7b0a71/frida-17.9.3-cp37-abi3-win32.whl", hash = "sha256:21f5bed08c266ee1144c7bef3171f43b63daa165bbd19296dd7d84b32782e440", size = 39317027, upload-time = "2026-04-28T15:55:31.516Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/33/f65b00d07e2ac34850c26dd532713b4ad93f27d27cdb546165cfa0dade21/frida-17.9.3-cp37-abi3-win_amd64.whl", hash = "sha256:694848b1517d6304cc634516ee8228a9b40f129afb79f632bf97b0174d650b0d", size = 41961019, upload-time = "2026-04-28T15:55:35.059Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/d6/927ad688c2bfc0e4c87d63222e99dad691a4da9ed4e2184851025aa43f5e/frida-17.9.3-cp37-abi3-win_arm64.whl", hash = "sha256:3be3b85fe0817ecfc8b7cd0dee0ed8c4dee4ede25cf7ec02ff6118dbe1156ae1", size = 51849086, upload-time = "2026-04-28T15:55:38.629Z" },
]
[[package]]
name = "protobuf"
version = "7.34.1"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" },
]
[[package]]
name = "pycryptodome"
version = "3.23.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" },
]
[[package]]
name = "pyside6"
version = "6.11.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "pyside6-addons" },
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/95/f3f5a2799163b6658126d78a85bc1dec9eda88c75c26780556b26071a1d8/pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f", size = 571544, upload-time = "2026-03-23T12:47:20.263Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/89/9a1f521051714e6694ebbe2b979ded279845ec8e25cb309ca3960158d74f/pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00", size = 571725, upload-time = "2026-03-23T12:47:21.727Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/3d/f779d8bba00fcde31a7d7fb6b59347a70773c9cc8135592dea9972579877/pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e", size = 571722, upload-time = "2026-03-23T12:47:22.761Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/98/150e01a026df3e9697310236821fa825319bb4b9d6137539cb25a3032968/pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc", size = 577988, upload-time = "2026-03-23T12:47:23.795Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/e7/55960f7c6b41d058e95cb4af02652c46c48702c506c8bbf12e99550e1fb3/pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e", size = 561372, upload-time = "2026-03-23T12:47:25.073Z" },
]
[[package]]
name = "pyside6-addons"
version = "6.11.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/df/241f311c61a46b7b1195927da77b2537692ee3442aa9ccd87981164ff78d/pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182", size = 331554157, upload-time = "2026-03-23T12:40:40.497Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/b9/e81172835ccc9d8b9792cc6bf7524a252a0db9a76ddd693de230402697f9/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35", size = 174948482, upload-time = "2026-03-23T12:41:05.379Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/a4/426d9333782bf65ab2a20257d6b4b3af9b8d5d7a710da719865fab49d492/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753", size = 170430798, upload-time = "2026-03-23T12:41:38.134Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/9a/46d271fedfabad8c6dce2ebb69bb593745487ed33753a56a47c3ba4fdb1c/pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e", size = 168723088, upload-time = "2026-03-23T12:42:00.668Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/cd/1b28264f7dc9a642da2e4e7c02f67418d0949eb7ce329ae20869703c2630/pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8", size = 35698324, upload-time = "2026-03-23T12:42:13.748Z" },
]
[[package]]
name = "pyside6-essentials"
version = "6.11.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "shiboken6" },
]
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/00/8a8583d3429c737cc20e61a43eba8ab1ec13ddb101e99802c2ffeedf3b41/pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01", size = 108085251, upload-time = "2026-03-23T12:42:52.872Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/a9/07c9e5c014b871c1b19caf8f994bcd50b345559b81f81671217b49559b67/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849", size = 78316055, upload-time = "2026-03-23T12:43:04.19Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/35/f06b1b641d7600ec46374c16cd37c66fa4a22870326b4eb073a95471035f/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e", size = 77380821, upload-time = "2026-03-23T12:43:24.649Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/37/ba95c6262836d2b286b4e05a9d16a5e870995d5d2503ac6adc6312208049/pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf", size = 75793322, upload-time = "2026-03-23T12:43:35.575Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/27/d17f25e45820e633a70e6109b35991eda09a5e8000c2a306f0ab7538d48c/pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187", size = 56337457, upload-time = "2026-03-23T12:43:43.573Z" },
]
[[package]]
name = "shiboken6"
version = "6.11.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/1d/b56b7b694fbc871496435488d1f41c5068de546334850d722756511cef65/shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b", size = 476085, upload-time = "2026-03-23T12:47:05.724Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/cb/4bb0c76011166230daa7c0074aeb3fdb3935c83ac1fef3789b85fcd1a8fc/shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053", size = 271055, upload-time = "2026-03-23T12:47:07.349Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/96/771a6e2b530f725303d16d78a321fa4876b98b4f3615c9851880df8c1a43/shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9", size = 267456, upload-time = "2026-03-23T12:47:08.689Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/f7/44c0c42c3f5f29dec457fd46ea0552174bcb8aa75becf03bbd90308ba07b/shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1", size = 1222132, upload-time = "2026-03-23T12:47:10.143Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/99/6e5ee21db2d6af84bbbd7d871d441dafeb069c6de5667b1aa49891a77c66/shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4", size = 1783186, upload-time = "2026-03-23T12:47:11.832Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "websockets"
version = "16.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
]
+101 -30
View File
@@ -1,48 +1,119 @@
@echo off @echo off
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
title Launcher chcp 437 >nul
title First - Launcher
cd /d "%~dp0"
:: Skip dependency check if already installed before set "CHOICE_FILE=.launch_choice"
if exist ".deps_installed" goto :launch
:: Check if requirements.txt exists :: ══════════════════════════════════════════
if not exist "requirements.txt" ( :: Read saved choice and launch directly
echo. :: ══════════════════════════════════════════
echo [WARNING] requirements.txt not found! if exist "%CHOICE_FILE%" (
echo. set /p SAVED=<"%CHOICE_FILE%"
set /p "choice= Skip dependency install and launch directly? [Y/N]: " if "!SAVED!"=="uv" goto :RUN_UV
echo. if "!SAVED!"=="system" goto :RUN_SYSTEM
if /i "!choice!"=="Y" goto :launch
echo Cancelled.
timeout /t 2 >nul
exit /b 1
) )
:: requirements.txt found, ask to install :: ══════════════════════════════════════════
:: First run: ask user to choose environment
:: ══════════════════════════════════════════
echo. echo.
echo [OK] requirements.txt found. echo ============================================
echo First ^| Launch Configuration
echo ============================================
echo. echo.
set /p "install= Install dependencies? [Y/N]: " echo Please choose how to run First:
echo. echo.
if /i "!install!"=="Y" ( echo [1] uv virtual environment (Recommended)
echo Installing via Tsinghua mirror, please wait... echo Isolated dependencies, won't affect system Python
echo.
echo [2] System Python
echo Use system-installed Python directly
echo.
set /p CHOICE=" Enter choice (1/2): "
if "%CHOICE%"=="1" goto :SETUP_UV
if "%CHOICE%"=="2" goto :SETUP_SYSTEM
echo Invalid choice. Exiting.
pause
exit /b 1
:: ══════════════════════════════════════════
:: Branch: uv virtual environment
:: ══════════════════════════════════════════
:SETUP_UV
where uv >nul 2>nul
if errorlevel 1 (
echo. echo.
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple echo [WARNING] uv not found on this system.
echo.
set /p INSTALLUV=" Install uv now? (pip install uv) [Y/N]: "
if /i "!INSTALLUV!"=="Y" (
echo Installing uv...
pip install uv
if errorlevel 1 ( if errorlevel 1 (
echo. echo [ERROR] uv installation failed. Please run manually: pip install uv
echo [ERROR] Installation failed! Check your network or pip config.
pause pause
exit /b 1 exit /b 1
) )
echo. ) else (
echo [OK] Done! Will skip this step on next launch. echo Cancelled.
echo. > .deps_installed pause
echo. exit /b 0
) else ( )
echo. > .deps_installed
) )
:: Launch gui.py without command window :RUN_UV
:launch echo.
echo Syncing uv virtual environment...
echo.
uv sync -i https://pypi.tuna.tsinghua.edu.cn/simple
if errorlevel 1 (
echo.
echo [ERROR] uv sync failed. Check network or pyproject.toml.
pause
exit /b 1
)
echo uv>"%CHOICE_FILE%"
start "" ".venv\Scripts\pythonw.exe" gui.py
exit /b 0
:: ══════════════════════════════════════════
:: Branch: System Python
:: ══════════════════════════════════════════
:SETUP_SYSTEM
where python >nul 2>nul
if errorlevel 1 (
echo.
echo [ERROR] python not found. Please install Python 3.9+ first.
pause
exit /b 1
)
echo.
echo Checking dependencies...
python -c "import frida, websockets, google.protobuf, PySide6, Crypto" >nul 2>nul
if errorlevel 1 (
echo Some dependencies are missing.
echo.
set /p INSTALLDEPS=" Install dependencies now? (pip install -r requirements.txt) [Y/N]: "
if /i "!INSTALLDEPS!"=="Y" (
echo Installing dependencies...
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
if errorlevel 1 (
echo [ERROR] Installation failed. Try manually: pip install -r requirements.txt
pause
exit /b 1
)
) else (
echo Cancelled.
pause
exit /b 0
)
)
:RUN_SYSTEM
echo system>"%CHOICE_FILE%"
start "" pythonw gui.py start "" pythonw gui.py
exit /b 0 exit /b 0
Executable
+137
View File
@@ -0,0 +1,137 @@
#!/bin/bash
cd "$(dirname "$0")"
# ── 配置文件路径 ──
CHOICE_FILE=".launch_choice"
# ══════════════════════════════════════════
# 对话框工具(macOS 用 osascript,Linux 降级为 read)
# ══════════════════════════════════════════
dialog_choice() {
# $1=标题 $2=提示文字 $3=按钮1 $4=按钮2
# 返回 0 = 按钮1,1 = 按钮2
if [[ "$OSTYPE" == "darwin"* ]]; then
local btn
btn=$(osascript -e "button returned of (display dialog \"$2\" with title \"$1\" buttons {\"$4\", \"$3\"} default button \"$3\")" 2>/dev/null)
[[ "$btn" == "$3" ]] && return 0 || return 1
else
echo ""
echo " $1"
echo " $2"
echo " [1] $3 [2] $4"
read -rp " 请输入选择 (1/2): " sel
[[ "$sel" == "1" ]] && return 0 || return 1
fi
}
dialog_info() {
# $1=标题 $2=内容
if [[ "$OSTYPE" == "darwin"* ]]; then
osascript -e "display dialog \"$2\" with title \"$1\" buttons {\"确定\"} default button \"确定\"" >/dev/null 2>&1
else
echo ""
echo " [$1] $2"
echo ""
fi
}
# ══════════════════════════════════════════
# 读取上次选择
# ══════════════════════════════════════════
if [[ -f "$CHOICE_FILE" ]]; then
SAVED=$(cat "$CHOICE_FILE")
# 直接按上次结果启动
if [[ "$SAVED" == "uv" ]]; then
echo " [启动] 使用 uv 虚拟环境..."
uv sync -i https://pypi.tuna.tsinghua.edu.cn/simple
if [[ $? -ne 0 ]]; then
dialog_info "启动失败" "uv sync 失败,请检查网络或 uv 配置。"
exit 1
fi
.venv/bin/python gui.py &
exit 0
elif [[ "$SAVED" == "system" ]]; then
echo " [启动] 使用系统 Python..."
python3 gui.py &
exit 0
fi
fi
# ══════════════════════════════════════════
# 首次运行:让用户选择环境
# ══════════════════════════════════════════
dialog_choice "First — 启动配置" \
"请选择运行方式:\n\n ✦ uv 虚拟环境(推荐)\n 自动隔离依赖,不影响系统 Python\n\n ✦ 系统 Python\n 使用系统已安装的 Python 直接运行" \
"uv 虚拟环境" "系统 Python"
USE_UV=$?
# ══════════════════════════════════════════
# 分支:uv 虚拟环境
# ══════════════════════════════════════════
if [[ $USE_UV -eq 0 ]]; then
# 检查 uv 是否已安装
if ! command -v uv >/dev/null 2>&1; then
dialog_choice "未找到 uv" \
"系统中未检测到 uv 工具。\n是否现在安装 uv?\n\n(将执行:pip install uv)" \
"安装 uv" "取消"
if [[ $? -eq 0 ]]; then
echo " 正在安装 uv..."
pip install uv
if [[ $? -ne 0 ]]; then
dialog_info "安装失败" "uv 安装失败,请手动执行:pip install uv"
exit 1
fi
else
echo " 已取消。"
exit 0
fi
fi
echo " 正在同步 uv 虚拟环境..."
uv sync -i https://pypi.tuna.tsinghua.edu.cn/simple
if [[ $? -ne 0 ]]; then
dialog_info "同步失败" "uv sync 失败,请检查网络连接或 pyproject.toml 配置。"
exit 1
fi
echo "uv" > "$CHOICE_FILE"
.venv/bin/python gui.py &
exit 0
# ══════════════════════════════════════════
# 分支:系统 Python
# ══════════════════════════════════════════
else
# 检查 python3
if ! command -v python3 >/dev/null 2>&1; then
dialog_info "未找到 Python" "系统中未检测到 python3,请先安装 Python 3.9+。"
exit 1
fi
# 检查是否缺少依赖
MISSING=0
python3 -c "import frida, websockets, google.protobuf, PySide6, Crypto" 2>/dev/null || MISSING=1
if [[ $MISSING -eq 1 ]]; then
dialog_choice "缺少依赖库" \
"检测到部分依赖库未安装。\n是否现在安装?\n\n(将执行:pip install -r requirements.txt)" \
"立即安装" "取消"
if [[ $? -eq 0 ]]; then
echo " 正在安装依赖..."
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
if [[ $? -ne 0 ]]; then
dialog_info "安装失败" "依赖安装失败,请检查网络或手动执行:\npip install -r requirements.txt"
exit 1
fi
else
echo " 已取消。"
exit 0
fi
fi
echo "system" > "$CHOICE_FILE"
python3 gui.py &
exit 0
fi