Compare commits
29
Commits
First-v1.0.3
...
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 |
@@ -0,0 +1,4 @@
|
|||||||
|
.venv/
|
||||||
|
.deps_installed
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
@@ -1,157 +1,100 @@
|
|||||||
# First
|
# First
|
||||||
|
|
||||||
> 微信小程序调试工具 —— 基于 Frida + CDP 代理,支持 GUI 与 CLI 双模式
|
微信小程序安全调试工具 — Frida + CDP,支持 macOS / Windows
|
||||||
---
|
|
||||||
本项目仅支持windows,macos用户使用pd虚拟机即可
|
|
||||||
## 截图预览
|
|
||||||
|
|
||||||
### 主界面 / 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 |
|
| macOS (Apple Silicon) | `First-ARM.dmg` |
|
||||||
| frida | >= 17.0.0 |
|
| macOS (Intel) | `First-Intel.dmg` |
|
||||||
| websockets | >= 12.0 |
|
| Windows | `First-Windows.zip` |
|
||||||
| protobuf | >= 4.0.0 |
|
|
||||||
| PySide6 | >= 6.5.0 |
|
|
||||||
|
|
||||||
安装依赖:
|
## 支持的微信版本
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
- WMPF 版本:11581, 11633, 13331, 13341, 13487, 13639, 13655, 13871, 13909, 14161, 14199, 14315, 16133, 16203, 16389, 16467, 16771, 16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201(实时更新),19977(微信群友“执守中一”)
|
||||||
|
- 推荐微信版本:**4.1.10**
|
||||||
|
- 下载地址:[weixin/4.1.10](https://github.com/vs-olitus/wx-version/releases/tag/4.1.10-windows)
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
- WMPF 版本:19978(微信群友“人杰提供”), 17078, 18152, 18788(实时更新)
|
||||||
|
- 推荐微信版本:**4.1.10**
|
||||||
|
- 下载地址:[weixin/4.1.10](https://github.com/vs-olitus/wx-version/releases/tag/4.1.10-mac)
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
1. 打开 First
|
||||||
|
2. 点击启动 — Frida 自动注入微信
|
||||||
|
3. 在微信中打开小程序
|
||||||
|
4. 使用 DevTools / 云捕获 / 导航等功能
|
||||||
|
|
||||||
|
## macOS 注意事项
|
||||||
|
|
||||||
|
如果 Frida 注入报错,需要解除系统对进程附加的限制,任选其一:
|
||||||
|
|
||||||
|
**方案一:关闭 SIP(系统完整性保护)**
|
||||||
|
|
||||||
|
> 关闭后 Frida 才能正常注入进程。参考教程:[macOS SIP 开启关闭教程](https://cloud.tencent.com/developer/article/1496058)
|
||||||
|
|
||||||
|
**方案二:强制重签名 WeChat**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
sudo codesign --force --deep --sign - /Applications/WeChat.app
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 参考
|
||||||
|
|
||||||
## 微信版本要求
|
|
||||||
|
|
||||||
- WMPF版本支持列表:11581, 11633, 13331, 13341, 13487, 13639, 13655, 13871, 13909, 14161, 14199, 14315, 16133, 16203, 16389, 16467, 16771,
|
|
||||||
16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201
|
|
||||||
- 推荐微信版本为4.1.0.30
|
|
||||||
- 下载地址[weixin/4.1.0.30](https://github.com/vs-olitus/wx-version/releases/tag/4.1.0.30)
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### GUI 模式(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python gui.py
|
|
||||||
```
|
|
||||||
双击启动.bat
|
|
||||||
启动后在主界面点击 **启动调试** 即可开始调试。
|
|
||||||
|
|
||||||
### 小程序页面操作
|
|
||||||
|
|
||||||
点击启动调试前请勿打开小程序, **启动调试**打开后再次启动**小程序**即可。
|
|
||||||
|
|
||||||
### CLI 模式
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 默认端口启动
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# 自定义端口
|
|
||||||
python main.py --debug-port 9421 --cdp-port 62000
|
|
||||||
|
|
||||||
# 开启详细日志
|
|
||||||
python main.py --debug-main --debug-frida
|
|
||||||
```
|
|
||||||
### 连接 Chrome DevTools
|
|
||||||
|
|
||||||
启动后,在 Chrome 地址栏输入:
|
|
||||||
|
|
||||||
```
|
|
||||||
devtools://devtools/bundled/inspector.html?ws=127.0.0.1:62000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 参数说明
|
|
||||||
|
|
||||||
| 参数 | 默认值 | 说明 |
|
|
||||||
|------|--------|------|
|
|
||||||
| `--debug-port` | `9421` | 远程调试服务端口 |
|
|
||||||
| `--cdp-port` | `62000` | CDP 代理监听端口 |
|
|
||||||
| `--debug-main` | 关闭 | 输出主进程调试日志 |
|
|
||||||
| `--debug-frida` | 关闭 | 输出 Frida 客户端日志 |
|
|
||||||
| `--scripts-dir` | `./userscripts` | UserScript 目录路径 |
|
|
||||||
| `--script` | — | 指定单个 .js 文件注入(可多次使用) |
|
|
||||||
|
|
||||||
### UserScript 注入
|
|
||||||
|
|
||||||
将 `.js` 脚本放入 `userscripts/` 目录,工具启动时自动加载并按规则注入。
|
|
||||||
|
|
||||||
也可手动指定文件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main.py --script ./my_hook.js --script ./another.js
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 打包为可执行文件
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pyinstaller WMPFDebugger.spec
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常见问题解决办法
|
|
||||||
|
|
||||||
1. Frida 已显示连接,但小程序端显示未连接或无法断点调试
|
|
||||||
|
|
||||||
若操作顺序无误,建议先彻底卸载微信并重启电脑(如有重要聊天记录请提前备份)。
|
|
||||||
删除路径C:\Users\用户名\AppData\Roaming\Tencent\xwechat\XPlugin\Plugins\RadiumWMPF下所有以数字命名的文件夹,再次重启电脑后,安装微信 4.1.0.30 版本。安装完成后检查上述路径,确认文件夹编号为 16389。
|
|
||||||
|
|
||||||
## 参考项目
|
|
||||||
|
|
||||||
本项目参考并学习了以下优秀工具的实现思路:
|
|
||||||
|
|
||||||
- [evi0s/WMPFDebugger](https://github.com/evi0s/WMPFDebugger)
|
- [evi0s/WMPFDebugger](https://github.com/evi0s/WMPFDebugger)
|
||||||
- [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK)
|
- [0xsdeo/HeartK](https://github.com/0xsdeo/HeartK)
|
||||||
- [残笑/FindSomething](https://github.com/momosecurity/FindSomething)
|
- [残笑/FindSomething](https://github.com/momosecurity/FindSomething)
|
||||||
- [进击的HACK/JSRPC与调用wx.cloud](https://mp.weixin.qq.com/s/hTlekrCPiMJCvsHYx7CAxw)
|
- [进击的HACK / JSRPC 与调用 wx.cloud](https://mp.weixin.qq.com/s/hTlekrCPiMJCvsHYx7CAxw)
|
||||||
---
|
- [linguo2625469/WMPFDebugger-mac](https://github.com/linguo2625469/WMPFDebugger-mac)
|
||||||
|
|
||||||
## 致谢
|
## 致谢
|
||||||
|
|
||||||
感谢 **0xsdeo** 师傅的大力支持与思路提供。
|
感谢 **0xsdeo** 师傅的大力支持与思路提供。
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 交流群
|
## 交流群
|
||||||
|
|
||||||

|
加不了群加我进群:
|
||||||
|
|
||||||
## 免责声明
|
<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,
|
"Version": 11581,
|
||||||
"LoadStartHookOffset": "0x28E9190",
|
"LoadStartHookOffset": "0x28E9190",
|
||||||
"CDPFilterHookOffset": "0x38C4350",
|
"CDPFilterHookOffset": "0x38C4350",
|
||||||
"SceneOffsets": [1208, 1160, 488]
|
"SceneOffsets": [56, 1208, 8, 1160, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 11633,
|
"Version": 11633,
|
||||||
"LoadStartHookOffset": "0x28F22A0",
|
"LoadStartHookOffset": "0x28F22A0",
|
||||||
"CDPFilterHookOffset": "0x38D41E0",
|
"CDPFilterHookOffset": "0x38D41E0",
|
||||||
"SceneOffsets": [1208, 1160, 488]
|
"SceneOffsets": [56, 1208, 8, 1160, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13331,
|
"Version": 13331,
|
||||||
"LoadStartHookOffset": "0x0FFC200",
|
"LoadStartHookOffset": "0x0FFC200",
|
||||||
"CDPFilterHookOffset": "0x2420100",
|
"CDPFilterHookOffset": "0x2420100",
|
||||||
"SceneOffsets": [1272, 1224, 488]
|
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13341,
|
"Version": 13341,
|
||||||
"LoadStartHookOffset": "0x10009E0",
|
"LoadStartHookOffset": "0x10009E0",
|
||||||
"CDPFilterHookOffset": "0x242E8E0",
|
"CDPFilterHookOffset": "0x242E8E0",
|
||||||
"SceneOffsets": [1272, 1224, 488]
|
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13487,
|
"Version": 13487,
|
||||||
"LoadStartHookOffset": "0x0FFB600",
|
"LoadStartHookOffset": "0x0FFB600",
|
||||||
"CDPFilterHookOffset": "0x241FEB0",
|
"CDPFilterHookOffset": "0x241FEB0",
|
||||||
"SceneOffsets": [1272, 1224, 488]
|
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13639,
|
"Version": 13639,
|
||||||
"LoadStartHookOffset": "0x1000990",
|
"LoadStartHookOffset": "0x1000990",
|
||||||
"CDPFilterHookOffset": "0x2424DE0",
|
"CDPFilterHookOffset": "0x2424DE0",
|
||||||
"SceneOffsets": [1272, 1224, 488]
|
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13655,
|
"Version": 13655,
|
||||||
"LoadStartHookOffset": "0x100F4B0",
|
"LoadStartHookOffset": "0x100F4B0",
|
||||||
"CDPFilterHookOffset": "0x244A9E0",
|
"CDPFilterHookOffset": "0x244A9E0",
|
||||||
"SceneOffsets": [1280, 1232, 488]
|
"SceneOffsets": [56, 1280, 8, 1232, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13871,
|
"Version": 13871,
|
||||||
"LoadStartHookOffset": "0x101F160",
|
"LoadStartHookOffset": "0x101F160",
|
||||||
"CDPFilterHookOffset": "0x246FC40",
|
"CDPFilterHookOffset": "0x246FC40",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13909,
|
"Version": 13909,
|
||||||
"LoadStartHookOffset": "0x101F0E0",
|
"LoadStartHookOffset": "0x101F0E0",
|
||||||
"CDPFilterHookOffset": "0x246FDC0",
|
"CDPFilterHookOffset": "0x246FDC0",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14161,
|
"Version": 14161,
|
||||||
"LoadStartHookOffset": "0x10246C0",
|
"LoadStartHookOffset": "0x10246C0",
|
||||||
"CDPFilterHookOffset": "0x24839B0",
|
"CDPFilterHookOffset": "0x24839B0",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14199,
|
"Version": 14199,
|
||||||
"LoadStartHookOffset": "0x10246F0",
|
"LoadStartHookOffset": "0x10246F0",
|
||||||
"CDPFilterHookOffset": "0x24839E0",
|
"CDPFilterHookOffset": "0x24839E0",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14315,
|
"Version": 14315,
|
||||||
"LoadStartHookOffset": "0x10004C0",
|
"LoadStartHookOffset": "0x10004C0",
|
||||||
"CDPFilterHookOffset": "0x2424B50",
|
"CDPFilterHookOffset": "0x2424B50",
|
||||||
"SceneOffsets": [1272, 1224, 488]
|
"SceneOffsets": [56, 1272, 8, 1224, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16133,
|
"Version": 16133,
|
||||||
"LoadStartHookOffset": "0x470FAD0",
|
"LoadStartHookOffset": "0x470FAD0",
|
||||||
"CDPFilterHookOffset": "0x90FC7E0",
|
"CDPFilterHookOffset": "0x90FC7E0",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16203,
|
"Version": 16203,
|
||||||
"LoadStartHookOffset": "0x4710890",
|
"LoadStartHookOffset": "0x4710890",
|
||||||
"CDPFilterHookOffset": "0x90FD640",
|
"CDPFilterHookOffset": "0x90FD640",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16389,
|
"Version": 16389,
|
||||||
"LoadStartHookOffset": "0x24E4830",
|
"LoadStartHookOffset": "0x24E4830",
|
||||||
"CDPFilterHookOffset": "0x2E2A880",
|
"CDPFilterHookOffset": "0x2E2A880",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16467,
|
"Version": 16467,
|
||||||
"LoadStartHookOffset": "0x24E4FD0",
|
"LoadStartHookOffset": "0x24E4FD0",
|
||||||
"CDPFilterHookOffset": "0x2E2CC90",
|
"CDPFilterHookOffset": "0x2E2CC90",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16771,
|
"Version": 16771,
|
||||||
"LoadStartHookOffset": "0x24E9130",
|
"LoadStartHookOffset": "0x24E9130",
|
||||||
"CDPFilterHookOffset": "0x2E3C470",
|
"CDPFilterHookOffset": "0x2E3C470",
|
||||||
"SceneOffsets": [1360, 1312, 488]
|
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16815,
|
"Version": 16815,
|
||||||
"LoadStartHookOffset": "0x2509690",
|
"LoadStartHookOffset": "0x2509690",
|
||||||
"CDPFilterHookOffset": "0x2E768D0",
|
"CDPFilterHookOffset": "0x2E768D0",
|
||||||
"SceneOffsets": [1416, 1360, 488]
|
"SceneOffsets": [56, 1416, 8, 1360, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16965,
|
"Version": 16965,
|
||||||
"LoadStartHookOffset": "0x2570220",
|
"LoadStartHookOffset": "0x2570220",
|
||||||
"CDPFilterHookOffset": "0x2F844A0",
|
"CDPFilterHookOffset": "0x2F844A0",
|
||||||
"SceneOffsets": [1416, 1360, 488]
|
"SceneOffsets": [56, 1416, 8, 1360, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17037,
|
"Version": 17037,
|
||||||
"LoadStartHookOffset": "0x257D0A0",
|
"LoadStartHookOffset": "0x257D0A0",
|
||||||
"CDPFilterHookOffset": "0x2FB2310",
|
"CDPFilterHookOffset": "0x2FB2310",
|
||||||
"SceneOffsets": [1408, 1352, 488]
|
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17071,
|
"Version": 17071,
|
||||||
"LoadStartHookOffset": "0x258F370",
|
"LoadStartHookOffset": "0x258F370",
|
||||||
"CDPFilterHookOffset": "0x2FD3080",
|
"CDPFilterHookOffset": "0x2FD3080",
|
||||||
"SceneOffsets": [1408, 1352, 488]
|
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17127,
|
"Version": 17127,
|
||||||
"LoadStartHookOffset": "0x2590910",
|
"LoadStartHookOffset": "0x2590910",
|
||||||
"CDPFilterHookOffset": "0x2FD4040",
|
"CDPFilterHookOffset": "0x2FD4040",
|
||||||
"SceneOffsets": [1408, 1352, 488]
|
"SceneOffsets": [56, 1408, 8, 1352, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18055,
|
"Version": 18055,
|
||||||
"LoadStartHookOffset": "0x25A1040",
|
"LoadStartHookOffset": "0x25A1040",
|
||||||
"CDPFilterHookOffset": "0x30031E0",
|
"CDPFilterHookOffset": "0x30031E0",
|
||||||
"SceneOffsets": [1416, 1352, 488]
|
"SceneOffsets": [56, 1416, 8, 1352, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18151,
|
"Version": 18151,
|
||||||
"LoadStartHookOffset": "0x25A2E20",
|
"LoadStartHookOffset": "0x25A2E20",
|
||||||
"CDPFilterHookOffset": "0x3013D20",
|
"CDPFilterHookOffset": "0x3013D20",
|
||||||
"SceneOffsets": [1416, 1352, 488]
|
"SceneOffsets": [56, 1416, 8, 1352, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18787,
|
"Version": 18787,
|
||||||
"LoadStartHookOffset": "0x25B2870",
|
"LoadStartHookOffset": "0x25B2870",
|
||||||
"CDPFilterHookOffset": "0x3028AD0",
|
"CDPFilterHookOffset": "0x3028AD0",
|
||||||
"SceneOffsets": [1408, 1344, 488]
|
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18891,
|
"Version": 18891,
|
||||||
"LoadStartHookOffset": "0x25B50C0",
|
"LoadStartHookOffset": "0x25B50C0",
|
||||||
"CDPFilterHookOffset": "0x30245E0",
|
"CDPFilterHookOffset": "0x30245E0",
|
||||||
"SceneOffsets": [1408, 1344, 488]
|
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18955,
|
"Version": 18955,
|
||||||
"LoadStartHookOffset": "0x25B52C0",
|
"LoadStartHookOffset": "0x25B52C0",
|
||||||
"CDPFilterHookOffset": "0x30248B0",
|
"CDPFilterHookOffset": "0x30248B0",
|
||||||
"SceneOffsets": [1408, 1344, 488]
|
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 19027,
|
"Version": 19027,
|
||||||
"LoadStartHookOffset": "0x25B52D0",
|
"LoadStartHookOffset": "0x25B52D0",
|
||||||
"CDPFilterHookOffset": "0x3024AD0",
|
"CDPFilterHookOffset": "0x3024AD0",
|
||||||
"SceneOffsets": [1408, 1344, 488]
|
"SceneOffsets": [56, 1408, 8, 1344, 16, 488]
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 19201,
|
"Version": 19201,
|
||||||
"LoadStartHookOffset": "0x25B5DD0",
|
"LoadStartHookOffset": "0x25B5DD0",
|
||||||
"CDPFilterHookOffset": "0x301B3C0",
|
"CDPFilterHookOffset": "0x301B3C0",
|
||||||
"SceneOffsets": [1376, 1312, 456]
|
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
|
||||||
}
|
}
|
||||||
@@ -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) => {
|
const getMainModule = (version) => {
|
||||||
|
if (PLATFORM === "darwin") {
|
||||||
|
return Process.findModuleByName("WeChatAppEx Framework");
|
||||||
|
}
|
||||||
|
// Windows: flue.dll for newer versions
|
||||||
if (version >= 13331) {
|
if (version >= 13331) {
|
||||||
return Process.findModuleByName("flue.dll");
|
return Process.findModuleByName("flue.dll");
|
||||||
}
|
}
|
||||||
return Process.findModuleByName("WeChatAppEx.exe");
|
return Process.findModuleByName("WeChatAppEx.exe");
|
||||||
};
|
};
|
||||||
|
|
||||||
const patchCDPFilter = (base, config) => {
|
// ── Scene Values ──
|
||||||
// xref: SendToClientFilter OR devtools_message_filter_applet_webview.cc
|
|
||||||
const offset = config.CDPFilterHookOffset;
|
const SCENE_WHITELIST = [
|
||||||
|
1005, 1007, 1008, 1012, 1027, 1035, 1053, 1074,
|
||||||
|
1145, 1168, 1178, 1256, 1260, 1302, 1308,
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── CDP Filter Patch (shared logic, platform-specific pointer access) ──
|
||||||
|
|
||||||
|
const patchCDPFilter = (base, offset) => {
|
||||||
|
// xref: SendToClientFilter / devtools_message_filter_applet_webview.cc
|
||||||
Interceptor.attach(base.add(offset), {
|
Interceptor.attach(base.add(offset), {
|
||||||
onEnter(args) {
|
onEnter(args) {
|
||||||
send(
|
if (PLATFORM === "windows") {
|
||||||
`[patch] CDP filter on enter, original value of input: ${args[0].readPointer()}`,
|
this.inputValue = args[0];
|
||||||
);
|
}
|
||||||
this.inputValue = args[0];
|
|
||||||
},
|
},
|
||||||
onLeave(retval) {
|
onLeave(retval) {
|
||||||
const inputValue = this.inputValue.readPointer();
|
let target;
|
||||||
if (inputValue.isNull() || inputValue.add(8).isNull()) {
|
if (PLATFORM === "darwin") {
|
||||||
// there's a chance the value could be null
|
// macOS: retval points directly to the struct
|
||||||
// return here to avoid crash
|
if (!retval || retval.isNull()) return;
|
||||||
return;
|
target = retval.add(8);
|
||||||
|
} else {
|
||||||
|
// Windows: dereference args[0] to get the struct pointer
|
||||||
|
const ptr = this.inputValue.readPointer();
|
||||||
|
if (ptr.isNull() || ptr.add(8).isNull()) return;
|
||||||
|
target = ptr.add(8);
|
||||||
}
|
}
|
||||||
|
VERBOSE && console.log(`[patch] CDP filter v8[2]: ${target.readU32()}`);
|
||||||
send(
|
if (target.readU32() === 6) {
|
||||||
`[patch] CDP filter on leave, patch input, now value: ${inputValue}; ` +
|
target.writeU32(0x0);
|
||||||
`*(input + 8) = ${inputValue.add(8).readU32()}`,
|
|
||||||
);
|
|
||||||
if (inputValue.add(8).readU32() == 6) {
|
|
||||||
inputValue.add(8).writeU32(0x0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hookOnLoadScene = (a1, sceneOffsets) => {
|
// ── Resource Cache Policy Patch (darwin only) ──
|
||||||
const miniappConfigPtr = a1
|
|
||||||
.add(56)
|
|
||||||
.readPointer()
|
|
||||||
.add(sceneOffsets[0])
|
|
||||||
.readPointer();
|
|
||||||
const miniappScenePtr = miniappConfigPtr
|
|
||||||
.add(8)
|
|
||||||
.readPointer()
|
|
||||||
.add(sceneOffsets[1])
|
|
||||||
.readPointer()
|
|
||||||
.add(16)
|
|
||||||
.readPointer()
|
|
||||||
.add(488);
|
|
||||||
send(`[hook] scene: ${miniappScenePtr.readInt()}`);
|
|
||||||
|
|
||||||
// 1000: from issue #83 <-- will crash the process
|
const patchResourceCachePolicy = (base, offset) => {
|
||||||
// 1007: from issue #80
|
// xref: WAPCAdapterAppIndex.js
|
||||||
// 1008: from issue #53
|
Interceptor.attach(base.add(offset), {
|
||||||
// 1027: from issue #78
|
onLeave(retval) {
|
||||||
// 1035: from issue #78
|
VERBOSE && console.log(`[patch] cache policy retval: ${retval.toInt32()} -> 0`);
|
||||||
// 1053: from issue #25
|
retval.replace(0x0);
|
||||||
// 1074: from issue #32
|
},
|
||||||
// 1145: from search
|
});
|
||||||
// 1178: from phone (issue #117)
|
|
||||||
// 1256: from recent
|
|
||||||
// 1260: from frequently used
|
|
||||||
// 1302: from services
|
|
||||||
// 1308: minigame?
|
|
||||||
const sceneNumberArray = [
|
|
||||||
1005, 1007, 1008, 1027, 1035, 1053, 1074, 1145, 1178, 1256, 1260, 1302,
|
|
||||||
1308,
|
|
||||||
];
|
|
||||||
if (!sceneNumberArray.includes(miniappScenePtr.readInt())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
send("[hook] hook scene condition -> 1101");
|
|
||||||
miniappScenePtr.writeInt(1101);
|
|
||||||
|
|
||||||
// TODO: customize debugging endpoint
|
|
||||||
// const websocketServerStringPtr = passArgs.add(8).readPointer().add(520);
|
|
||||||
// VERBOSE && console.log("[hook] hook websocket server, original: ", websocketServerStringPtr.readUtf8String());
|
|
||||||
// websocketServerStringPtr.writeUtf8String("ws://127.0.0.1:8189/");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const patchOnLoadStart = (base, config) => {
|
// ── OnLoadStart: Darwin ──
|
||||||
// xref: AppletIndexContainer::OnLoadStart
|
|
||||||
Interceptor.attach(base.add(config.LoadStartHookOffset), {
|
const darwinPatchLoadStart = (base, offset) => {
|
||||||
|
// xref: AppletIndexContainer::OnLoadStart (first match)
|
||||||
|
// Force debug mode by setting second param to true
|
||||||
|
Interceptor.attach(base.add(offset), {
|
||||||
|
onEnter() {
|
||||||
|
console.log("[hook] AppletIndexContainer::OnLoadStart onEnter");
|
||||||
|
if (ARCH === "arm64") {
|
||||||
|
this.context.x1 = (this.context.x1 & ~0xff) | 0x1;
|
||||||
|
} else {
|
||||||
|
// x64 / x86_64: System V ABI, second param = RSI
|
||||||
|
this.context.rsi = (this.context.rsi & ~0xff) | 0x1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLeave() {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const darwinPatchLoadStart2 = (base, offset, structOffset) => {
|
||||||
|
// xref: AppletIndexContainer::OnLoadStart (last function)
|
||||||
|
// Hijack scene value via struct pointer chain
|
||||||
|
Interceptor.attach(base.add(offset), {
|
||||||
onEnter(args) {
|
onEnter(args) {
|
||||||
send(
|
try {
|
||||||
`[inteceptor] AppletIndexContainer::OnLoadStart onEnter, ` +
|
const v4 = args[0].add(8).readPointer();
|
||||||
`indexContainer.this: ${this.context.rcx}`,
|
if (!v4 || v4.isNull()) return;
|
||||||
);
|
const q1 = v4.add(structOffset).readPointer();
|
||||||
// write dl to 0x1
|
if (!q1 || q1.isNull()) return;
|
||||||
|
const q2 = q1.add(16).readPointer();
|
||||||
|
if (!q2 || q2.isNull()) return;
|
||||||
|
const scenePtr = q2.add(488);
|
||||||
|
const scene = scenePtr.readInt();
|
||||||
|
console.log(`[hook] scene: ${scene}`);
|
||||||
|
if (SCENE_WHITELIST.includes(scene)) {
|
||||||
|
console.log("[hook] hook scene -> 1101");
|
||||||
|
scenePtr.writeInt(1101);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[hook] onLoadStart2 error: ${e}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLeave() {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── OnLoadStart: Windows ──
|
||||||
|
|
||||||
|
const winPatchLoadStart = (base, config) => {
|
||||||
|
// xref: AppletIndexContainer::OnLoadStart
|
||||||
|
// Windows combines debug flag + scene hijack in one hook
|
||||||
|
Interceptor.attach(base.add(config.LoadStartHookOffset), {
|
||||||
|
onEnter() {
|
||||||
|
// Microsoft x64 ABI: second param = RDX, force debug mode
|
||||||
if ((this.context.rdx & 0xff) !== 1) {
|
if ((this.context.rdx & 0xff) !== 1) {
|
||||||
this.context.rdx = (this.context.rdx & ~0xff) | 0x1;
|
this.context.rdx = (this.context.rdx & ~0xff) | 0x1;
|
||||||
}
|
}
|
||||||
// handle onLoad scene
|
// Scene hijack via SceneOffsets pointer chain
|
||||||
hookOnLoadScene(this.context.rcx, config.SceneOffsets);
|
try {
|
||||||
},
|
const offsets = config.SceneOffsets;
|
||||||
onLeave(retval) {
|
const miniappConfigPtr = this.context.rcx
|
||||||
// do nothing
|
.add(offsets[0])
|
||||||
|
.readPointer()
|
||||||
|
.add(offsets[1])
|
||||||
|
.readPointer();
|
||||||
|
const scenePtr = miniappConfigPtr
|
||||||
|
.add(offsets[2])
|
||||||
|
.readPointer()
|
||||||
|
.add(offsets[3])
|
||||||
|
.readPointer()
|
||||||
|
.add(offsets[4])
|
||||||
|
.readPointer()
|
||||||
|
.add(offsets[5]);
|
||||||
|
const scene = scenePtr.readInt();
|
||||||
|
send(`[hook] scene: ${scene}`);
|
||||||
|
if (SCENE_WHITELIST.includes(scene)) {
|
||||||
|
send("[hook] hook scene -> 1101");
|
||||||
|
scenePtr.writeInt(1101);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
send(`[hook] scene hook error: ${e}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
onLeave() {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Config Parsing ──
|
||||||
|
|
||||||
const parseConfig = () => {
|
const parseConfig = () => {
|
||||||
const rawConfig = `@@CONFIG@@`;
|
const rawConfig = `@@CONFIG@@`;
|
||||||
if (rawConfig.includes("@@")) {
|
if (rawConfig.includes("@@")) {
|
||||||
// test addresses
|
// Fallback test config
|
||||||
|
if (PLATFORM === "darwin") {
|
||||||
|
return {
|
||||||
|
Version: 17078,
|
||||||
|
Arch: {
|
||||||
|
arm64: {
|
||||||
|
LoadStartHookOffset: "0x4F0620C",
|
||||||
|
LoadStartHookOffset2: "0x81CEC08",
|
||||||
|
CDPFilterHookOffset: "0x81BFC04",
|
||||||
|
ResourceCachePolicyHookOffset: "0x4F699E8",
|
||||||
|
StructOffset: 1376,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
Version: 18955,
|
Version: 18955,
|
||||||
LoadStartHookOffset: "0x25B52C0",
|
LoadStartHookOffset: "0x25B52C0",
|
||||||
CDPFilterHookOffset: "0x30248B0",
|
CDPFilterHookOffset: "0x30248B0",
|
||||||
SceneOffsets: [1408, 1344],
|
SceneOffsets: [56, 1408, 8, 1344, 16, 488],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return JSON.parse(rawConfig);
|
return JSON.parse(rawConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resolveArchConfig = (config) => {
|
||||||
|
// Windows configs have offsets at top level — return directly
|
||||||
|
if (config && config.LoadStartHookOffset) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
// Mac configs nest under Arch.{arm64|x64}
|
||||||
|
const table = config?.Arch || config?.arch;
|
||||||
|
if (!table) return null;
|
||||||
|
|
||||||
|
const candidates = [ARCH];
|
||||||
|
if (ARCH === "x86_64") candidates.push("x64");
|
||||||
|
if (ARCH === "amd64") candidates.push("x64");
|
||||||
|
|
||||||
|
for (const key of candidates) {
|
||||||
|
const picked = table[key];
|
||||||
|
if (picked && picked.LoadStartHookOffset) {
|
||||||
|
return { ...picked, Version: config.Version, __arch: key };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Main ──
|
||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
const config = parseConfig();
|
const rawConfig = parseConfig();
|
||||||
|
const config = resolveArchConfig(rawConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
console.error(`[frida] no config for platform=${PLATFORM} arch=${ARCH}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mainModule = getMainModule(config.Version);
|
const mainModule = getMainModule(config.Version);
|
||||||
patchOnLoadStart(mainModule.base, config);
|
if (!mainModule) {
|
||||||
patchCDPFilter(mainModule.base, config);
|
const expected = PLATFORM === "darwin" ? "WeChatAppEx Framework" : "flue.dll / WeChatAppEx.exe";
|
||||||
|
console.error(`[frida] module not found: ${expected}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[frida] version=${config.Version} platform=${PLATFORM} arch=${config.__arch || ARCH}`);
|
||||||
|
console.log(`[frida] module base: ${mainModule.base}`);
|
||||||
|
|
||||||
|
const base = mainModule.base;
|
||||||
|
|
||||||
|
// CDP filter patch — both platforms
|
||||||
|
patchCDPFilter(base, config.CDPFilterHookOffset);
|
||||||
|
|
||||||
|
if (PLATFORM === "darwin") {
|
||||||
|
darwinPatchLoadStart(base, config.LoadStartHookOffset);
|
||||||
|
darwinPatchLoadStart2(base, config.LoadStartHookOffset2, config.StructOffset);
|
||||||
|
if (config.ResourceCachePolicyHookOffset) {
|
||||||
|
patchResourceCachePolicy(base, config.ResourceCachePolicyHookOffset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
winPatchLoadStart(base, config);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
+9
-2
@@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"debug_port": "9421",
|
|
||||||
"cdp_port": "62000",
|
"cdp_port": "62000",
|
||||||
"debug_main": false,
|
"debug_main": false,
|
||||||
"debug_frida": false,
|
"debug_frida": false,
|
||||||
"selected_preload_scripts": []
|
"extract_packages_dir": "",
|
||||||
|
"extract_custom_patterns": {},
|
||||||
|
"auto_decompile": false,
|
||||||
|
"auto_scan": false,
|
||||||
|
"global_hook_scripts": [],
|
||||||
|
"font_ui": "PingFang SC",
|
||||||
|
"font_ui_size": 12,
|
||||||
|
"font_mono": "Menlo",
|
||||||
|
"font_mono_size": 15
|
||||||
}
|
}
|
||||||
+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
|
websockets>=12.0
|
||||||
protobuf>=4.0.0
|
protobuf>=4.0.0
|
||||||
PySide6>=6.5.0
|
PySide6>=6.5.0
|
||||||
|
pycryptodome
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
-7
@@ -7,9 +7,9 @@ CDP_PORT = 62000
|
|||||||
|
|
||||||
|
|
||||||
class CliOptions:
|
class CliOptions:
|
||||||
def __init__(self, debug_port: int, cdp_port: int, debug_main: bool, debug_frida: bool,
|
def __init__(self, cdp_port: int, debug_main: bool, debug_frida: bool,
|
||||||
scripts_dir: str = "", script_files: list = None):
|
scripts_dir: str = "", script_files: list = None):
|
||||||
self.debug_port = debug_port
|
self.debug_port = DEBUG_PORT # 小程序硬编码 9421,不可修改
|
||||||
self.cdp_port = cdp_port
|
self.cdp_port = cdp_port
|
||||||
self.debug_main = debug_main
|
self.debug_main = debug_main
|
||||||
self.debug_frida = debug_frida
|
self.debug_frida = debug_frida
|
||||||
@@ -35,18 +35,16 @@ def parse_cli_options() -> CliOptions:
|
|||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog=f"""Examples:
|
epilog=f"""Examples:
|
||||||
python main.py
|
python main.py
|
||||||
python main.py --debug-port 9421 --cdp-port 62000
|
python main.py --cdp-port 62000
|
||||||
python main.py --debug-main --debug-frida
|
python main.py --debug-main --debug-frida
|
||||||
|
|
||||||
Default ports:
|
Default ports:
|
||||||
Debug server: {DEBUG_PORT}
|
Debug server: {DEBUG_PORT} (fixed)
|
||||||
CDP proxy: {CDP_PORT}
|
CDP proxy: {CDP_PORT}
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
Connect Chrome DevTools to: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:<cdp-port>"""
|
Connect Chrome DevTools to: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:<cdp-port>"""
|
||||||
)
|
)
|
||||||
parser.add_argument("--debug-port", type=str, default=None,
|
|
||||||
help=f"Remote debug server port (default: {DEBUG_PORT})")
|
|
||||||
parser.add_argument("--cdp-port", type=str, default=None,
|
parser.add_argument("--cdp-port", type=str, default=None,
|
||||||
help=f"CDP proxy server port (default: {CDP_PORT})")
|
help=f"CDP proxy server port (default: {CDP_PORT})")
|
||||||
parser.add_argument("--debug-main", action="store_true", default=False,
|
parser.add_argument("--debug-main", action="store_true", default=False,
|
||||||
@@ -72,7 +70,6 @@ Usage:
|
|||||||
script_files.append(os.path.abspath(f))
|
script_files.append(os.path.abspath(f))
|
||||||
|
|
||||||
return CliOptions(
|
return CliOptions(
|
||||||
debug_port=parse_port("--debug-port", args.debug_port, DEBUG_PORT),
|
|
||||||
cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT),
|
cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT),
|
||||||
debug_main=args.debug_main,
|
debug_main=args.debug_main,
|
||||||
debug_frida=args.debug_frida,
|
debug_frida=args.debug_frida,
|
||||||
|
|||||||
+14
-1
@@ -25,6 +25,7 @@ class CloudAuditor:
|
|||||||
"""启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame"""
|
"""启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame"""
|
||||||
if self._enabled:
|
if self._enabled:
|
||||||
return {"ok": True, "already": True}
|
return {"ok": True, "already": True}
|
||||||
|
self._injected = False # 强制重新注入(连接可能已更换)
|
||||||
await self.inject()
|
await self.inject()
|
||||||
result = await self.engine.evaluate_js(
|
result = await self.engine.evaluate_js(
|
||||||
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
||||||
@@ -52,11 +53,23 @@ class CloudAuditor:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def poll(self):
|
async def poll(self):
|
||||||
"""拉取新捕获的云函数调用,只返回增量"""
|
"""拉取新捕获的云函数调用,只返回增量。
|
||||||
|
自动检测 Hook 是否丢失(页面切换后),并重新注入。
|
||||||
|
"""
|
||||||
if not self._enabled:
|
if not self._enabled:
|
||||||
return []
|
return []
|
||||||
try:
|
try:
|
||||||
await self.inject()
|
await self.inject()
|
||||||
|
# 检查 Hook 是否仍然存活(页面导航后可能丢失)
|
||||||
|
alive_result = await self.engine.evaluate_js(
|
||||||
|
"(function(){try{return window.cloudAudit&&window.cloudAudit._hooked?'1':'0'}catch(e){return '0'}})()",
|
||||||
|
timeout=3.0)
|
||||||
|
alive = self._extract_value(alive_result)
|
||||||
|
if alive != '1':
|
||||||
|
# Hook 丢失(页面切换/刷新),重新安装
|
||||||
|
await self.engine.evaluate_js(
|
||||||
|
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
||||||
|
self._seen_count = 0 # 重置计数器,新上下文从 0 开始
|
||||||
result = await self.engine.evaluate_js(
|
result = await self.engine.evaluate_js(
|
||||||
"JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0)
|
"JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0)
|
||||||
value = self._extract_value(result)
|
value = self._extract_value(result)
|
||||||
|
|||||||
+86
-43
@@ -4,7 +4,9 @@ DebugEngine - wraps debug_server, proxy_server, frida_server into a reusable cla
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -434,66 +436,95 @@ class DebugEngine:
|
|||||||
|
|
||||||
# ── Frida ──
|
# ── Frida ──
|
||||||
|
|
||||||
async def _start_frida(self):
|
# ── Frida: platform helpers ──
|
||||||
logger = self.logger
|
|
||||||
|
def _get_project_root(self):
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
return Path(getattr(sys, '_MEIPASS', Path(sys.executable).parent)).resolve()
|
||||||
|
return Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
|
def _find_wmpf_pids_win(self):
|
||||||
|
"""Windows: find main WeChatAppEx.exe PID via ppid frequency."""
|
||||||
device = frida.get_local_device()
|
device = frida.get_local_device()
|
||||||
processes = device.enumerate_processes(scope="metadata")
|
processes = device.enumerate_processes(scope="metadata")
|
||||||
wmpf_processes = [p for p in processes if p.name == "WeChatAppEx.exe"]
|
wmpf_procs = [p for p in processes if p.name == "WeChatAppEx.exe"]
|
||||||
if not wmpf_processes:
|
if not wmpf_procs:
|
||||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
||||||
|
|
||||||
wmpf_ppids = []
|
ppids = [p.parameters.get("ppid", 0) or 0 for p in wmpf_procs]
|
||||||
for p in wmpf_processes:
|
main_pid = Counter(ppids).most_common(1)[0][0]
|
||||||
ppid = p.parameters.get("ppid", 0)
|
if main_pid == 0:
|
||||||
wmpf_ppids.append(ppid if ppid else 0)
|
raise RuntimeError("[frida] WeChatAppEx.exe main process not found")
|
||||||
|
|
||||||
if not wmpf_ppids:
|
main_proc = next((p for p in processes if p.pid == main_pid), None)
|
||||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
if main_proc is None:
|
||||||
|
raise RuntimeError("[frida] could not locate main WMPF process")
|
||||||
|
|
||||||
pid_counts = Counter(wmpf_ppids)
|
proc_path = main_proc.parameters.get("path", "")
|
||||||
wmpf_pid = pid_counts.most_common(1)[0][0]
|
nums = re.findall(r"\d+", proc_path)
|
||||||
|
if not nums:
|
||||||
|
raise RuntimeError("[frida] cannot detect WMPF version from path")
|
||||||
|
version = int(nums[-1])
|
||||||
|
if version == 0:
|
||||||
|
raise RuntimeError("[frida] invalid WMPF version")
|
||||||
|
|
||||||
if wmpf_pid == 0:
|
return [main_pid], version
|
||||||
raise RuntimeError("[frida] WeChatAppEx.exe process not found")
|
|
||||||
|
|
||||||
wmpf_process = None
|
def _find_wmpf_pids_darwin(self):
|
||||||
for p in processes:
|
"""macOS: find WeChatAppEx PIDs via pgrep, version via Info.plist."""
|
||||||
if p.pid == wmpf_pid:
|
try:
|
||||||
wmpf_process = p
|
out = subprocess.check_output(
|
||||||
break
|
["pgrep", "-f", "/MacOS/WeChatAppEx.app/Contents/MacOS/WeChatAppEx"],
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
pids = [int(p) for p in out.splitlines() if p.strip()]
|
||||||
|
except (subprocess.CalledProcessError, ValueError):
|
||||||
|
pids = []
|
||||||
|
if not pids:
|
||||||
|
raise RuntimeError("[frida] WeChatAppEx process not found on macOS")
|
||||||
|
|
||||||
if wmpf_process is None:
|
try:
|
||||||
raise RuntimeError("[frida] Could not find main WMPF process")
|
ver_out = subprocess.check_output(
|
||||||
|
["defaults", "read",
|
||||||
|
"/Applications/WeChat.app/Contents/MacOS/WeChatAppEx.app/Contents/Info.plist",
|
||||||
|
"CFBundleVersion"],
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
version = int(ver_out.split(".")[1])
|
||||||
|
except Exception:
|
||||||
|
raise RuntimeError("[frida] cannot detect WeChatAppEx version from Info.plist")
|
||||||
|
|
||||||
wmpf_process_path = wmpf_process.parameters.get("path", "")
|
return pids, version
|
||||||
version_matches = re.findall(r"\d+", wmpf_process_path)
|
|
||||||
if not version_matches:
|
|
||||||
raise RuntimeError("[frida] error in find wmpf version")
|
|
||||||
wmpf_version = int(version_matches[-1])
|
|
||||||
if wmpf_version == 0:
|
|
||||||
raise RuntimeError("[frida] error in find wmpf version")
|
|
||||||
|
|
||||||
session = device.attach(wmpf_pid)
|
async def _start_frida(self):
|
||||||
|
logger = self.logger
|
||||||
|
is_mac = sys.platform == "darwin"
|
||||||
|
platform_dir = "mac" if is_mac else "win"
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
# Find PIDs and version
|
||||||
# PyInstaller onefile: resources in _MEIPASS; onedir: next to exe
|
if is_mac:
|
||||||
project_root = Path(getattr(sys, '_MEIPASS', Path(sys.executable).parent)).resolve()
|
pids, wmpf_version = self._find_wmpf_pids_darwin()
|
||||||
else:
|
else:
|
||||||
project_root = Path(__file__).parent.parent.resolve()
|
pids, wmpf_version = self._find_wmpf_pids_win()
|
||||||
|
|
||||||
|
# Load hook script and config
|
||||||
|
project_root = self._get_project_root()
|
||||||
hook_path = project_root / "frida" / "hook.js"
|
hook_path = project_root / "frida" / "hook.js"
|
||||||
if not hook_path.exists():
|
if not hook_path.exists():
|
||||||
raise RuntimeError("[frida] hook script not found")
|
raise RuntimeError("[frida] hook script not found")
|
||||||
script_content = hook_path.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
config_path = project_root / "frida" / "config" / f"addresses.{wmpf_version}.json"
|
config_path = project_root / "frida" / "config" / platform_dir / f"addresses.{wmpf_version}.json"
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
raise RuntimeError(f"[frida] version config not found: {wmpf_version}")
|
raise RuntimeError(f"[frida] version config not found: {platform_dir}/{wmpf_version}")
|
||||||
config_content = config_path.read_text(encoding="utf-8")
|
|
||||||
config_content = json.dumps(json.loads(config_content))
|
|
||||||
|
|
||||||
|
script_content = hook_path.read_text(encoding="utf-8")
|
||||||
|
config_content = json.dumps(json.loads(config_path.read_text(encoding="utf-8")))
|
||||||
final_script = script_content.replace("@@CONFIG@@", config_content)
|
final_script = script_content.replace("@@CONFIG@@", config_content)
|
||||||
script = session.create_script(final_script)
|
|
||||||
|
# Inject into all target PIDs
|
||||||
|
device = frida.get_local_device()
|
||||||
|
session = None
|
||||||
|
script = None
|
||||||
|
|
||||||
def on_message(message, data):
|
def on_message(message, data):
|
||||||
if message.get("type") == "error":
|
if message.get("type") == "error":
|
||||||
@@ -501,10 +532,22 @@ class DebugEngine:
|
|||||||
return
|
return
|
||||||
logger.frida_debug("[frida client]", message.get("payload", ""))
|
logger.frida_debug("[frida client]", message.get("payload", ""))
|
||||||
|
|
||||||
script.on("message", on_message)
|
for pid in pids:
|
||||||
script.load()
|
try:
|
||||||
|
s = device.attach(pid)
|
||||||
|
sc = s.create_script(final_script)
|
||||||
|
sc.on("message", on_message)
|
||||||
|
sc.load()
|
||||||
|
logger.info(f"[frida] injected pid={pid}, version={wmpf_version}")
|
||||||
|
# Keep first successful session/script as primary
|
||||||
|
if session is None:
|
||||||
|
session, script = s, sc
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[frida] failed to inject pid={pid}: {e}")
|
||||||
|
|
||||||
logger.info(f"[frida] script loaded, WMPF version: {wmpf_version}, pid: {wmpf_pid}")
|
if session is None:
|
||||||
|
raise RuntimeError("[frida] failed to inject any WeChatAppEx process")
|
||||||
|
|
||||||
|
logger.info(f"[frida] ready ({platform_dir}), version={wmpf_version}, {len(pids)} process(es)")
|
||||||
logger.info("[frida] you can now open any miniapps")
|
logger.info("[frida] you can now open any miniapps")
|
||||||
|
|
||||||
return session, script
|
return session, script
|
||||||
|
|||||||
@@ -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._ensure()
|
||||||
await self.engine.evaluate_js(f"window.nav.back({delta})", timeout=5.0)
|
await self.engine.evaluate_js(f"window.nav.back({delta})", timeout=5.0)
|
||||||
|
|
||||||
|
async def refresh_page(self):
|
||||||
|
"""刷新当前小程序页面:获取当前路由后 reLaunch 到同一页面。"""
|
||||||
|
await self._ensure()
|
||||||
|
result = await self.engine.evaluate_js(
|
||||||
|
"(function(){"
|
||||||
|
"try{"
|
||||||
|
"var nav=window.nav;"
|
||||||
|
"if(!nav||!nav.wxFrame)return JSON.stringify({err:'no nav'});"
|
||||||
|
"var p=nav.wxFrame.getCurrentPages();"
|
||||||
|
"if(!p||!p.length)return JSON.stringify({err:'no page'});"
|
||||||
|
"var cur=p[p.length-1];"
|
||||||
|
"var route=cur.route||cur.__route__||'';"
|
||||||
|
"if(!route)return JSON.stringify({err:'no route'});"
|
||||||
|
"var url='/'+route;"
|
||||||
|
"var opts=cur.options||{};"
|
||||||
|
"var qs=Object.keys(opts).map(function(k){return k+'='+opts[k]}).join('&');"
|
||||||
|
"if(qs)url+='?'+qs;"
|
||||||
|
"nav.wxFrame.wx.reLaunch({url:url,"
|
||||||
|
"success:function(){},fail:function(e){"
|
||||||
|
"nav.wxFrame.wx.redirectTo({url:url,success:function(){},fail:function(){}})"
|
||||||
|
"}});"
|
||||||
|
"return JSON.stringify({ok:true,route:route})"
|
||||||
|
"}catch(e){return JSON.stringify({err:e.message})}"
|
||||||
|
"})()",
|
||||||
|
timeout=5.0,
|
||||||
|
)
|
||||||
|
return self._extract_value(result) or ""
|
||||||
|
|
||||||
async def get_current_route(self):
|
async def get_current_route(self):
|
||||||
"""Get current page route via the detected wxFrame."""
|
"""Get current page route via the detected wxFrame."""
|
||||||
await self._ensure()
|
await self._ensure()
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+242
@@ -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
|
@echo off
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
title Launcher
|
chcp 437 >nul
|
||||||
|
title First - Launcher
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
:: Skip dependency check if already installed before
|
set "CHOICE_FILE=.launch_choice"
|
||||||
if exist ".deps_installed" goto :launch
|
|
||||||
|
|
||||||
:: Check if requirements.txt exists
|
:: ══════════════════════════════════════════
|
||||||
if not exist "requirements.txt" (
|
:: Read saved choice and launch directly
|
||||||
|
:: ══════════════════════════════════════════
|
||||||
|
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.
|
||||||
echo [WARNING] requirements.txt not found!
|
echo [WARNING] uv not found on this system.
|
||||||
echo.
|
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.
|
echo.
|
||||||
if /i "!choice!"=="Y" goto :launch
|
echo [ERROR] uv sync failed. Check network or pyproject.toml.
|
||||||
echo Cancelled.
|
pause
|
||||||
timeout /t 2 >nul
|
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
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
:: requirements.txt found, ask to install
|
|
||||||
echo.
|
echo.
|
||||||
echo [OK] requirements.txt found.
|
echo Checking dependencies...
|
||||||
echo.
|
python -c "import frida, websockets, google.protobuf, PySide6, Crypto" >nul 2>nul
|
||||||
set /p "install= Install dependencies? [Y/N]: "
|
if errorlevel 1 (
|
||||||
echo.
|
echo Some dependencies are missing.
|
||||||
if /i "!install!"=="Y" (
|
|
||||||
echo Installing via Tsinghua mirror, please wait...
|
|
||||||
echo.
|
echo.
|
||||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
set /p INSTALLDEPS=" Install dependencies now? (pip install -r requirements.txt) [Y/N]: "
|
||||||
if errorlevel 1 (
|
if /i "!INSTALLDEPS!"=="Y" (
|
||||||
echo.
|
echo Installing dependencies...
|
||||||
echo [ERROR] Installation failed! Check your network or pip config.
|
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
|
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
|
:RUN_SYSTEM
|
||||||
:launch
|
echo system>"%CHOICE_FILE%"
|
||||||
start "" pythonw gui.py
|
start "" pythonw gui.py
|
||||||
exit /b 0
|
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