Compare commits
33
Commits
First-v1.0.2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc5604958 | ||
|
|
6a52cf87a2 | ||
|
|
676f1836c1 | ||
|
|
f9ec893a1e | ||
|
|
8a431e61dd | ||
|
|
c348c12900 | ||
|
|
e6d2706e2b | ||
|
|
a31a7d4d03 | ||
|
|
2f77fabd76 | ||
|
|
43abf4fe8c | ||
|
|
c71c22f7d1 | ||
|
|
970c0bad7d | ||
|
|
69983bfbf0 | ||
|
|
e419752a5c | ||
|
|
68598ed26d | ||
|
|
e7d33319c8 | ||
|
|
6a94b31be7 | ||
|
|
c7653cc698 | ||
|
|
02b58948a0 | ||
|
|
797004582f | ||
|
|
6e4a3b2464 | ||
|
|
c2f668b9db | ||
|
|
2940e15105 | ||
|
|
884d882eb7 | ||
|
|
b8bfac4641 | ||
|
|
4a64e54935 | ||
|
|
285b893d07 | ||
|
|
136a312b6c | ||
|
|
572eca8eaa | ||
|
|
f4bee68919 | ||
|
|
37cd7ed615 | ||
|
|
0edf3a97ff | ||
|
|
54b2db72f2 |
@@ -0,0 +1,4 @@
|
||||
.venv/
|
||||
.deps_installed
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -1,156 +1,100 @@
|
||||
# First
|
||||
|
||||
> 微信小程序调试工具 —— 基于 Frida + CDP 代理,支持 GUI 与 CLI 双模式
|
||||
---
|
||||
本项目仅支持windows,macos用户使用dp虚拟机即可
|
||||
## 截图预览
|
||||
微信小程序安全调试工具 — Frida + CDP,支持 macOS / Windows
|
||||
|
||||
### 主界面 / Control Panel
|
||||
> 自v1.1.0版本后开始闭源,往后版本更新只上传编译好的版本,下载最新版请去release下载。
|
||||
|
||||

|
||||
## 免责声明
|
||||
|
||||
### 路由导航
|
||||

|
||||
本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
|
||||
|
||||
### 云函数分析
|
||||

|
||||
## 功能
|
||||
|
||||
### 调试开关(JSRPC 与调用 wx.cloud)
|
||||

|
||||
- Frida 注入微信,CDP 代理桥接 Chrome DevTools
|
||||
- 云函数动态捕获(Hook wx.cloud.callFunction,参数修改重放)
|
||||
- wx.* API 捕获(login / request / getUserProfile 等)
|
||||
- 路由枚举与导航,跳转守卫检测
|
||||
- wxapkg 解密解包 + 敏感信息扫描
|
||||
- MCP Server 集成(AI 辅助分析)
|
||||
- 偏移量 / Skills 在线更新
|
||||
|
||||
### 安全扫描
|
||||
待优化
|
||||
## 截图
|
||||
### 主页
|
||||

|
||||
### 云函数捕获
|
||||

|
||||
### wx.api捕获
|
||||

|
||||
### 反编译文件编辑搜索
|
||||

|
||||
### Frist-MCP
|
||||

|
||||

|
||||
### 自动更新
|
||||

|
||||
|
||||
---
|
||||
## 下载
|
||||
|
||||
## 功能特性
|
||||
[Releases](https://github.com/Spade-sec/First/releases)
|
||||
|
||||
- 通过 Frida 注入微信客户端,转发小程序调试协议
|
||||
- CDP 代理桥接,支持 Chrome DevTools 直接连接调试
|
||||
- 路由枚举与一键跳转导航
|
||||
- 云函数调用监控与参数分析
|
||||
- UserScript 自动注入(支持按 URL 匹配、run-at 时机控制)
|
||||
- 内置安全扫描模块,生成扫描报告
|
||||
- 深色 / 浅色主题切换
|
||||
- GUI(PySide6)与 CLI 双模式运行
|
||||
|
||||
---
|
||||
|
||||
## 环境要求
|
||||
|
||||
| 依赖 | 版本 |
|
||||
| 平台 | 文件 |
|
||||
|------|------|
|
||||
| Python | >= 3.10 |
|
||||
| frida | >= 17.0.0 |
|
||||
| websockets | >= 12.0 |
|
||||
| protobuf | >= 4.0.0 |
|
||||
| PySide6 | >= 6.5.0 |
|
||||
| macOS (Apple Silicon) | `First-ARM.dmg` |
|
||||
| macOS (Intel) | `First-Intel.dmg` |
|
||||
| Windows | `First-Windows.zip` |
|
||||
|
||||
安装依赖:
|
||||
## 支持的微信版本
|
||||
|
||||
### 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
|
||||
pip install -r requirements.txt
|
||||
sudo codesign --force --deep --sign - /Applications/WeChat.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 微信版本要求
|
||||
|
||||
- WMPF版本区间:19201-11581
|
||||
- 推荐微信版本为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)
|
||||
- [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK)
|
||||
- [残笑/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** 师傅的大力支持与思路提供。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 交流群
|
||||
|
||||

|
||||
加不了群加我进群:
|
||||
|
||||
## 免责声明
|
||||
|
||||
本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
|
||||
<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" />
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
"LoadStartHookOffset": "0x28E9190",
|
||||
"CDPFilterHookOffset": "0x38C4350",
|
||||
"SceneOffsets": [1208, 1160]
|
||||
"SceneOffsets": [56, 1208, 8, 1160, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 11633,
|
||||
"LoadStartHookOffset": "0x28F22A0",
|
||||
"CDPFilterHookOffset": "0x38D41E0",
|
||||
"SceneOffsets": [1208, 1160]
|
||||
"SceneOffsets": [56, 1208, 8, 1160, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13331,
|
||||
"LoadStartHookOffset": "0x0FFC200",
|
||||
"CDPFilterHookOffset": "0x2420100",
|
||||
"SceneOffsets": [1272, 1224]
|
||||
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13341,
|
||||
"LoadStartHookOffset": "0x10009E0",
|
||||
"CDPFilterHookOffset": "0x242E8E0",
|
||||
"SceneOffsets": [1272, 1224]
|
||||
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13487,
|
||||
"LoadStartHookOffset": "0x0FFB600",
|
||||
"CDPFilterHookOffset": "0x241FEB0",
|
||||
"SceneOffsets": [1272, 1224]
|
||||
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13639,
|
||||
"LoadStartHookOffset": "0x1000990",
|
||||
"CDPFilterHookOffset": "0x2424DE0",
|
||||
"SceneOffsets": [1272, 1224]
|
||||
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13655,
|
||||
"LoadStartHookOffset": "0x100F4B0",
|
||||
"CDPFilterHookOffset": "0x244A9E0",
|
||||
"SceneOffsets": [1280, 1232]
|
||||
"SceneOffsets": [56, 1280, 8, 1232, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13871,
|
||||
"LoadStartHookOffset": "0x101F160",
|
||||
"CDPFilterHookOffset": "0x246FC40",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 13909,
|
||||
"LoadStartHookOffset": "0x101F0E0",
|
||||
"CDPFilterHookOffset": "0x246FDC0",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 14161,
|
||||
"LoadStartHookOffset": "0x10246C0",
|
||||
"CDPFilterHookOffset": "0x24839B0",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 14199,
|
||||
"LoadStartHookOffset": "0x10246F0",
|
||||
"CDPFilterHookOffset": "0x24839E0",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 14315,
|
||||
"LoadStartHookOffset": "0x10004C0",
|
||||
"CDPFilterHookOffset": "0x2424B50",
|
||||
"SceneOffsets": [1272, 1224]
|
||||
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16133,
|
||||
"LoadStartHookOffset": "0x470FAD0",
|
||||
"CDPFilterHookOffset": "0x90FC7E0",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16203,
|
||||
"LoadStartHookOffset": "0x4710890",
|
||||
"CDPFilterHookOffset": "0x90FD640",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16389,
|
||||
"LoadStartHookOffset": "0x24E4830",
|
||||
"CDPFilterHookOffset": "0x2E2A880",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16467,
|
||||
"LoadStartHookOffset": "0x24E4FD0",
|
||||
"CDPFilterHookOffset": "0x2E2CC90",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16771,
|
||||
"LoadStartHookOffset": "0x24E9130",
|
||||
"CDPFilterHookOffset": "0x2E3C470",
|
||||
"SceneOffsets": [1360, 1312]
|
||||
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16815,
|
||||
"LoadStartHookOffset": "0x2509690",
|
||||
"CDPFilterHookOffset": "0x2E768D0",
|
||||
"SceneOffsets": [1416, 1360]
|
||||
"SceneOffsets": [56, 1416, 8, 1360, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 16965,
|
||||
"LoadStartHookOffset": "0x2570220",
|
||||
"CDPFilterHookOffset": "0x2F844A0",
|
||||
"SceneOffsets": [1416, 1360]
|
||||
"SceneOffsets": [56, 1416, 8, 1360, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 17037,
|
||||
"LoadStartHookOffset": "0x257D0A0",
|
||||
"CDPFilterHookOffset": "0x2FB2310",
|
||||
"SceneOffsets": [1408, 1352]
|
||||
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 17071,
|
||||
"LoadStartHookOffset": "0x258F370",
|
||||
"CDPFilterHookOffset": "0x2FD3080",
|
||||
"SceneOffsets": [1408, 1352]
|
||||
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 17127,
|
||||
"LoadStartHookOffset": "0x2590910",
|
||||
"CDPFilterHookOffset": "0x2FD4040",
|
||||
"SceneOffsets": [1408, 1352]
|
||||
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 18055,
|
||||
"LoadStartHookOffset": "0x25A1040",
|
||||
"CDPFilterHookOffset": "0x30031E0",
|
||||
"SceneOffsets": [1416, 1352]
|
||||
"SceneOffsets": [56, 1416, 8, 1352, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 18151,
|
||||
"LoadStartHookOffset": "0x25A2E20",
|
||||
"CDPFilterHookOffset": "0x3013D20",
|
||||
"SceneOffsets": [1416, 1352]
|
||||
"SceneOffsets": [56, 1416, 8, 1352, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 18787,
|
||||
"LoadStartHookOffset": "0x25B2870",
|
||||
"CDPFilterHookOffset": "0x3028AD0",
|
||||
"SceneOffsets": [1408, 1344]
|
||||
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 18891,
|
||||
"LoadStartHookOffset": "0x25B50C0",
|
||||
"CDPFilterHookOffset": "0x30245E0",
|
||||
"SceneOffsets": [1408, 1344]
|
||||
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 18955,
|
||||
"LoadStartHookOffset": "0x25B52C0",
|
||||
"CDPFilterHookOffset": "0x30248B0",
|
||||
"SceneOffsets": [1408, 1344]
|
||||
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"Version": 19027,
|
||||
"LoadStartHookOffset": "0x25B52D0",
|
||||
"CDPFilterHookOffset": "0x3024AD0",
|
||||
"SceneOffsets": [1408, 1344]
|
||||
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19201,
|
||||
"LoadStartHookOffset": "0x25B5DD0",
|
||||
"CDPFilterHookOffset": "0x301B3C0",
|
||||
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19339,
|
||||
"LoadStartHookOffset": "0x25B5DD0",
|
||||
"CDPFilterHookOffset": "0x301BA00",
|
||||
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19459,
|
||||
"LoadStartHookOffset": "0x25BB580",
|
||||
"CDPFilterHookOffset": "0x3022F20",
|
||||
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19481,
|
||||
"LoadStartHookOffset": "0x25BBA80",
|
||||
"CDPFilterHookOffset": "0x3023420",
|
||||
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19749,
|
||||
"LoadStartHookOffset": "0x25E77C0",
|
||||
"CDPFilterHookOffset": "0x30ABC60",
|
||||
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19769,
|
||||
"LoadStartHookOffset": "0x25E7990",
|
||||
"CDPFilterHookOffset": "0x30AC0A0",
|
||||
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Version": 19823,
|
||||
"LoadStartHookOffset": "0x25ED1C0",
|
||||
"CDPFilterHookOffset": "0x30B3320",
|
||||
"SceneOffsets": [64, 1408, 8, 1344, 16, 456]
|
||||
}
|
||||
+198
-79
@@ -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) => {
|
||||
if (PLATFORM === "darwin") {
|
||||
return Process.findModuleByName("WeChatAppEx Framework");
|
||||
}
|
||||
// Windows: flue.dll for newer versions
|
||||
if (version >= 13331) {
|
||||
return Process.findModuleByName("flue.dll");
|
||||
}
|
||||
return Process.findModuleByName("WeChatAppEx.exe");
|
||||
};
|
||||
|
||||
const patchCDPFilter = (base, config) => {
|
||||
// xref: SendToClientFilter OR devtools_message_filter_applet_webview.cc
|
||||
const offset = config.CDPFilterHookOffset;
|
||||
// ── Scene Values ──
|
||||
|
||||
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), {
|
||||
onEnter(args) {
|
||||
send(
|
||||
`[patch] CDP filter on enter, original value of input: ${args[0].readPointer()}`,
|
||||
);
|
||||
this.inputValue = args[0];
|
||||
if (PLATFORM === "windows") {
|
||||
this.inputValue = args[0];
|
||||
}
|
||||
},
|
||||
onLeave(retval) {
|
||||
const inputValue = this.inputValue.readPointer();
|
||||
if (inputValue.isNull() || inputValue.add(8).isNull()) {
|
||||
// there's a chance the value could be null
|
||||
// return here to avoid crash
|
||||
return;
|
||||
let target;
|
||||
if (PLATFORM === "darwin") {
|
||||
// macOS: retval points directly to the struct
|
||||
if (!retval || retval.isNull()) 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);
|
||||
}
|
||||
|
||||
send(
|
||||
`[patch] CDP filter on leave, patch input, now value: ${inputValue}; ` +
|
||||
`*(input + 8) = ${inputValue.add(8).readU32()}`,
|
||||
);
|
||||
if (inputValue.add(8).readU32() == 6) {
|
||||
inputValue.add(8).writeU32(0x0);
|
||||
VERBOSE && console.log(`[patch] CDP filter v8[2]: ${target.readU32()}`);
|
||||
if (target.readU32() === 6) {
|
||||
target.writeU32(0x0);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const hookOnLoadScene = (a1, sceneOffsets) => {
|
||||
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()}`);
|
||||
// ── Resource Cache Policy Patch (darwin only) ──
|
||||
|
||||
// 1000: from issue #83 <-- will crash the process
|
||||
// 1007: from issue #80
|
||||
// 1008: from issue #53
|
||||
// 1027: from issue #78
|
||||
// 1035: from issue #78
|
||||
// 1053: from issue #25
|
||||
// 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 patchResourceCachePolicy = (base, offset) => {
|
||||
// xref: WAPCAdapterAppIndex.js
|
||||
Interceptor.attach(base.add(offset), {
|
||||
onLeave(retval) {
|
||||
VERBOSE && console.log(`[patch] cache policy retval: ${retval.toInt32()} -> 0`);
|
||||
retval.replace(0x0);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const patchOnLoadStart = (base, config) => {
|
||||
// xref: AppletIndexContainer::OnLoadStart
|
||||
Interceptor.attach(base.add(config.LoadStartHookOffset), {
|
||||
// ── OnLoadStart: Darwin ──
|
||||
|
||||
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) {
|
||||
send(
|
||||
`[inteceptor] AppletIndexContainer::OnLoadStart onEnter, ` +
|
||||
`indexContainer.this: ${this.context.rcx}`,
|
||||
);
|
||||
// write dl to 0x1
|
||||
try {
|
||||
const v4 = args[0].add(8).readPointer();
|
||||
if (!v4 || v4.isNull()) return;
|
||||
const q1 = v4.add(structOffset).readPointer();
|
||||
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) {
|
||||
this.context.rdx = (this.context.rdx & ~0xff) | 0x1;
|
||||
}
|
||||
// handle onLoad scene
|
||||
hookOnLoadScene(this.context.rcx, config.SceneOffsets);
|
||||
},
|
||||
onLeave(retval) {
|
||||
// do nothing
|
||||
// Scene hijack via SceneOffsets pointer chain
|
||||
try {
|
||||
const offsets = config.SceneOffsets;
|
||||
const miniappConfigPtr = this.context.rcx
|
||||
.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 rawConfig = `@@CONFIG@@`;
|
||||
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 {
|
||||
Version: 18955,
|
||||
LoadStartHookOffset: "0x25B52C0",
|
||||
CDPFilterHookOffset: "0x30248B0",
|
||||
SceneOffsets: [1408, 1344],
|
||||
SceneOffsets: [56, 1408, 8, 1344, 16, 488],
|
||||
};
|
||||
}
|
||||
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 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);
|
||||
patchOnLoadStart(mainModule.base, config);
|
||||
patchCDPFilter(mainModule.base, config);
|
||||
if (!mainModule) {
|
||||
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();
|
||||
|
||||
+9
-2
@@ -1,8 +1,15 @@
|
||||
{
|
||||
"theme": "dark",
|
||||
"debug_port": "9421",
|
||||
"cdp_port": "62000",
|
||||
"debug_main": 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
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
# First
|
||||
|
||||
> 微信小程序安全调试工具 —— 基于 Frida + CDP 代理,支持 Windows / macOS 双平台,GUI 与 CLI 双模式
|
||||
|
||||
---
|
||||
|
||||
## 截图预览
|
||||
|
||||
### 主界面
|
||||
|
||||

|
||||
|
||||
### 路由导航
|
||||
|
||||

|
||||
|
||||
### 云函数分析
|
||||
|
||||

|
||||
|
||||
### 调试开关(JSRPC / wx.cloud 调用)
|
||||
|
||||

|
||||
|
||||
### 敏感信息提取
|
||||
|
||||

|
||||
|
||||
<img src="https://s1.galgame.fun/imgb/u55/20260416_69e0be0c38e39.png" alt="敏感信息提取-2" />
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 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 网页)
|
||||
|
||||

|
||||
|
||||
3. 在工具栏「服务」目标列表中找到目标网页,双击后切换至调试视图即可开始调试
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
## 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 人后需要手动邀请,请加我拉群:
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 免责声明
|
||||
|
||||
本工具仅供安全研究与学习使用,请勿用于未授权的目标,使用者须自行承担相关法律责任。
|
||||
@@ -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
|
||||
@@ -2,3 +2,4 @@ frida>=17.0.0
|
||||
websockets>=12.0
|
||||
protobuf>=4.0.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
@@ -7,9 +7,9 @@ CDP_PORT = 62000
|
||||
|
||||
|
||||
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):
|
||||
self.debug_port = debug_port
|
||||
self.debug_port = DEBUG_PORT # 小程序硬编码 9421,不可修改
|
||||
self.cdp_port = cdp_port
|
||||
self.debug_main = debug_main
|
||||
self.debug_frida = debug_frida
|
||||
@@ -35,18 +35,16 @@ def parse_cli_options() -> CliOptions:
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=f"""Examples:
|
||||
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
|
||||
|
||||
Default ports:
|
||||
Debug server: {DEBUG_PORT}
|
||||
Debug server: {DEBUG_PORT} (fixed)
|
||||
CDP proxy: {CDP_PORT}
|
||||
|
||||
Usage:
|
||||
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,
|
||||
help=f"CDP proxy server port (default: {CDP_PORT})")
|
||||
parser.add_argument("--debug-main", action="store_true", default=False,
|
||||
@@ -72,7 +70,6 @@ Usage:
|
||||
script_files.append(os.path.abspath(f))
|
||||
|
||||
return CliOptions(
|
||||
debug_port=parse_port("--debug-port", args.debug_port, DEBUG_PORT),
|
||||
cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT),
|
||||
debug_main=args.debug_main,
|
||||
debug_frida=args.debug_frida,
|
||||
|
||||
+14
-1
@@ -25,6 +25,7 @@ class CloudAuditor:
|
||||
"""启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame"""
|
||||
if self._enabled:
|
||||
return {"ok": True, "already": True}
|
||||
self._injected = False # 强制重新注入(连接可能已更换)
|
||||
await self.inject()
|
||||
result = await self.engine.evaluate_js(
|
||||
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
||||
@@ -52,11 +53,23 @@ class CloudAuditor:
|
||||
pass
|
||||
|
||||
async def poll(self):
|
||||
"""拉取新捕获的云函数调用,只返回增量"""
|
||||
"""拉取新捕获的云函数调用,只返回增量。
|
||||
自动检测 Hook 是否丢失(页面切换后),并重新注入。
|
||||
"""
|
||||
if not self._enabled:
|
||||
return []
|
||||
try:
|
||||
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(
|
||||
"JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0)
|
||||
value = self._extract_value(result)
|
||||
|
||||
+86
-43
@@ -4,7 +4,9 @@ DebugEngine - wraps debug_server, proxy_server, frida_server into a reusable cla
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
@@ -434,66 +436,95 @@ class DebugEngine:
|
||||
|
||||
# ── Frida ──
|
||||
|
||||
async def _start_frida(self):
|
||||
logger = self.logger
|
||||
# ── Frida: platform helpers ──
|
||||
|
||||
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()
|
||||
processes = device.enumerate_processes(scope="metadata")
|
||||
wmpf_processes = [p for p in processes if p.name == "WeChatAppEx.exe"]
|
||||
if not wmpf_processes:
|
||||
wmpf_procs = [p for p in processes if p.name == "WeChatAppEx.exe"]
|
||||
if not wmpf_procs:
|
||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
||||
|
||||
wmpf_ppids = []
|
||||
for p in wmpf_processes:
|
||||
ppid = p.parameters.get("ppid", 0)
|
||||
wmpf_ppids.append(ppid if ppid else 0)
|
||||
ppids = [p.parameters.get("ppid", 0) or 0 for p in wmpf_procs]
|
||||
main_pid = Counter(ppids).most_common(1)[0][0]
|
||||
if main_pid == 0:
|
||||
raise RuntimeError("[frida] WeChatAppEx.exe main process not found")
|
||||
|
||||
if not wmpf_ppids:
|
||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
||||
main_proc = next((p for p in processes if p.pid == main_pid), None)
|
||||
if main_proc is None:
|
||||
raise RuntimeError("[frida] could not locate main WMPF process")
|
||||
|
||||
pid_counts = Counter(wmpf_ppids)
|
||||
wmpf_pid = pid_counts.most_common(1)[0][0]
|
||||
proc_path = main_proc.parameters.get("path", "")
|
||||
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:
|
||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
||||
return [main_pid], version
|
||||
|
||||
wmpf_process = None
|
||||
for p in processes:
|
||||
if p.pid == wmpf_pid:
|
||||
wmpf_process = p
|
||||
break
|
||||
def _find_wmpf_pids_darwin(self):
|
||||
"""macOS: find WeChatAppEx PIDs via pgrep, version via Info.plist."""
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
["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:
|
||||
raise RuntimeError("[frida] Could not find main WMPF process")
|
||||
try:
|
||||
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", "")
|
||||
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")
|
||||
return pids, 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):
|
||||
# PyInstaller onefile: resources in _MEIPASS; onedir: next to exe
|
||||
project_root = Path(getattr(sys, '_MEIPASS', Path(sys.executable).parent)).resolve()
|
||||
# Find PIDs and version
|
||||
if is_mac:
|
||||
pids, wmpf_version = self._find_wmpf_pids_darwin()
|
||||
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"
|
||||
if not hook_path.exists():
|
||||
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():
|
||||
raise RuntimeError(f"[frida] version config not found: {wmpf_version}")
|
||||
config_content = config_path.read_text(encoding="utf-8")
|
||||
config_content = json.dumps(json.loads(config_content))
|
||||
raise RuntimeError(f"[frida] version config not found: {platform_dir}/{wmpf_version}")
|
||||
|
||||
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)
|
||||
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):
|
||||
if message.get("type") == "error":
|
||||
@@ -501,10 +532,22 @@ class DebugEngine:
|
||||
return
|
||||
logger.frida_debug("[frida client]", message.get("payload", ""))
|
||||
|
||||
script.on("message", on_message)
|
||||
script.load()
|
||||
for pid in pids:
|
||||
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")
|
||||
|
||||
return session, script
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -83,6 +83,34 @@ class MiniProgramNavigator:
|
||||
await self._ensure()
|
||||
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):
|
||||
"""Get current page route via the detected wxFrame."""
|
||||
await self._ensure()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+242
@@ -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
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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" },
|
||||
]
|
||||
@@ -1,48 +1,119 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
title Launcher
|
||||
chcp 437 >nul
|
||||
title First - Launcher
|
||||
cd /d "%~dp0"
|
||||
|
||||
:: Skip dependency check if already installed before
|
||||
if exist ".deps_installed" goto :launch
|
||||
set "CHOICE_FILE=.launch_choice"
|
||||
|
||||
:: Check if requirements.txt exists
|
||||
if not exist "requirements.txt" (
|
||||
:: ══════════════════════════════════════════
|
||||
:: Read saved choice and launch directly
|
||||
:: ══════════════════════════════════════════
|
||||
if exist "%CHOICE_FILE%" (
|
||||
set /p SAVED=<"%CHOICE_FILE%"
|
||||
if "!SAVED!"=="uv" goto :RUN_UV
|
||||
if "!SAVED!"=="system" goto :RUN_SYSTEM
|
||||
)
|
||||
|
||||
:: ══════════════════════════════════════════
|
||||
:: First run: ask user to choose environment
|
||||
:: ══════════════════════════════════════════
|
||||
echo.
|
||||
echo ============================================
|
||||
echo First ^| Launch Configuration
|
||||
echo ============================================
|
||||
echo.
|
||||
echo Please choose how to run First:
|
||||
echo.
|
||||
echo [1] uv virtual environment (Recommended)
|
||||
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 [WARNING] requirements.txt not found!
|
||||
echo [WARNING] uv not found on this system.
|
||||
echo.
|
||||
set /p "choice= Skip dependency install and launch directly? [Y/N]: "
|
||||
set /p INSTALLUV=" Install uv now? (pip install uv) [Y/N]: "
|
||||
if /i "!INSTALLUV!"=="Y" (
|
||||
echo Installing uv...
|
||||
pip install uv
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] uv installation failed. Please run manually: pip install uv
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo Cancelled.
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
|
||||
:RUN_UV
|
||||
echo.
|
||||
echo Syncing uv virtual environment...
|
||||
echo.
|
||||
uv sync -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
if /i "!choice!"=="Y" goto :launch
|
||||
echo Cancelled.
|
||||
timeout /t 2 >nul
|
||||
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
|
||||
)
|
||||
|
||||
:: requirements.txt found, ask to install
|
||||
echo.
|
||||
echo [OK] requirements.txt found.
|
||||
echo.
|
||||
set /p "install= Install dependencies? [Y/N]: "
|
||||
echo.
|
||||
if /i "!install!"=="Y" (
|
||||
echo Installing via Tsinghua mirror, please wait...
|
||||
echo Checking dependencies...
|
||||
python -c "import frida, websockets, google.protobuf, PySide6, Crypto" >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Some dependencies are missing.
|
||||
echo.
|
||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo [ERROR] Installation failed! Check your network or pip config.
|
||||
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 1
|
||||
exit /b 0
|
||||
)
|
||||
echo.
|
||||
echo [OK] Done! Will skip this step on next launch.
|
||||
echo. > .deps_installed
|
||||
echo.
|
||||
) else (
|
||||
echo. > .deps_installed
|
||||
)
|
||||
|
||||
:: Launch gui.py without command window
|
||||
:launch
|
||||
:RUN_SYSTEM
|
||||
echo system>"%CHOICE_FILE%"
|
||||
start "" pythonw gui.py
|
||||
exit /b 0
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user