Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
136a312b6c | ||
|
|
572eca8eaa | ||
|
|
f4bee68919 | ||
|
|
37cd7ed615 | ||
|
|
0edf3a97ff | ||
|
|
54b2db72f2 | ||
|
|
27f8bd58a1 | ||
|
|
b995a8da72 | ||
|
|
88b44692fb | ||
|
|
efc9c4fcd3 | ||
|
|
d438911cc0 | ||
|
|
d35c3df491 | ||
|
|
4c4f62c0aa | ||
|
|
8f1a891e82 | ||
|
|
9049c47d01 | ||
|
|
abc92a61c5 | ||
|
|
bfae468f24 | ||
|
|
691ba674ba | ||
|
|
fe972acab0 |
@@ -2,20 +2,29 @@
|
|||||||
|
|
||||||
> 微信小程序调试工具 —— 基于 Frida + CDP 代理,支持 GUI 与 CLI 双模式
|
> 微信小程序调试工具 —— 基于 Frida + CDP 代理,支持 GUI 与 CLI 双模式
|
||||||
---
|
---
|
||||||
改项目仅支持windows,macos用户使用dp虚拟机即可
|
本项目仅支持windows,macos用户使用pd虚拟机即可
|
||||||
## 截图预览
|
## 截图预览
|
||||||
|
|
||||||
### 主界面 / Control Panel
|
### 主界面 / Control Panel
|
||||||

|
|
||||||
|

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

|

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

|

|
||||||
|
|
||||||
### 安全扫描
|
### 调试开关(JSRPC 与调用 wx.cloud)
|
||||||
待优化
|

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

|
||||||
|
|
||||||
|
<img src="https://s1.galgame.fun/imgb/u55/20260416_69e0be0c38e39.png" alt="PixPin_2026-04-16_18-45-59" />
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -50,6 +59,13 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 微信版本要求
|
||||||
|
|
||||||
|
- 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 模式(推荐)
|
### GUI 模式(推荐)
|
||||||
@@ -57,8 +73,12 @@ pip install -r requirements.txt
|
|||||||
```bash
|
```bash
|
||||||
python gui.py
|
python gui.py
|
||||||
```
|
```
|
||||||
|
双击启动.bat
|
||||||
|
启动后在主界面点击 **启动调试** 即可开始调试。
|
||||||
|
|
||||||
启动后在主界面点击 **Start** 即可开始调试。
|
### 小程序页面操作
|
||||||
|
|
||||||
|
点击启动调试前请勿打开小程序, **启动调试**打开后再次启动**小程序**即可。
|
||||||
|
|
||||||
### CLI 模式
|
### CLI 模式
|
||||||
|
|
||||||
@@ -67,12 +87,11 @@ python gui.py
|
|||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
### 连接 Chrome DevTools
|
### 连接 Chrome DevTools
|
||||||
|
|
||||||
启动后,在 Chrome 地址栏输入:
|
启动后,在 Chrome 地址栏输入:
|
||||||
@@ -112,6 +131,13 @@ pyinstaller WMPFDebugger.spec
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 常见问题解决办法
|
||||||
|
|
||||||
|
1. Frida 已显示连接,但小程序端显示未连接或无法断点调试
|
||||||
|
|
||||||
|
若操作顺序无误,建议先彻底卸载微信并重启电脑(如有重要聊天记录请提前备份)。
|
||||||
|
删除路径C:\Users\用户名\AppData\Roaming\Tencent\xwechat\XPlugin\Plugins\RadiumWMPF下所有以数字命名的文件夹,再次重启电脑后,安装微信 4.1.0.30 版本。安装完成后检查上述路径,确认文件夹编号为 16389。
|
||||||
|
|
||||||
## 参考项目
|
## 参考项目
|
||||||
|
|
||||||
本项目参考并学习了以下优秀工具的实现思路:
|
本项目参考并学习了以下优秀工具的实现思路:
|
||||||
@@ -119,6 +145,7 @@ pyinstaller WMPFDebugger.spec
|
|||||||
- [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)
|
||||||
---
|
---
|
||||||
|
|
||||||
## 致谢
|
## 致谢
|
||||||
@@ -126,6 +153,9 @@ pyinstaller WMPFDebugger.spec
|
|||||||
感谢 **0xsdeo** 师傅的大力支持与思路提供。
|
感谢 **0xsdeo** 师傅的大力支持与思路提供。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 交流群
|
||||||
|
群满200变成手动挡了。加我拉
|
||||||
|

|
||||||
|
|
||||||
## 免责声明
|
## 免责声明
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['gui.py'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=[
|
|
||||||
('src/nav_inject.js', 'src'),
|
|
||||||
('src/cloud_audit_inject.js', 'src'),
|
|
||||||
('frida/hook.js', 'frida'),
|
|
||||||
('frida/config', 'frida/config'),
|
|
||||||
('icon.ico', '.'),
|
|
||||||
],
|
|
||||||
hiddenimports=[
|
|
||||||
'frida',
|
|
||||||
'websockets',
|
|
||||||
'google.protobuf',
|
|
||||||
'PySide6.QtCore',
|
|
||||||
'PySide6.QtGui',
|
|
||||||
'PySide6.QtWidgets',
|
|
||||||
],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
noarchive=False,
|
|
||||||
optimize=0,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.datas,
|
|
||||||
[],
|
|
||||||
name='First',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
runtime_tmpdir=None,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
icon=['icon.ico'],
|
|
||||||
)
|
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 11581,
|
"Version": 11581,
|
||||||
"LoadStartHookOffset": "0x28E9190",
|
"LoadStartHookOffset": "0x28E9190",
|
||||||
"CDPFilterHookOffset": "0x38C4350",
|
"CDPFilterHookOffset": "0x38C4350",
|
||||||
"SceneOffsets": [1208, 1160]
|
"SceneOffsets": [1208, 1160, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 11633,
|
"Version": 11633,
|
||||||
"LoadStartHookOffset": "0x28F22A0",
|
"LoadStartHookOffset": "0x28F22A0",
|
||||||
"CDPFilterHookOffset": "0x38D41E0",
|
"CDPFilterHookOffset": "0x38D41E0",
|
||||||
"SceneOffsets": [1208, 1160]
|
"SceneOffsets": [1208, 1160, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13331,
|
"Version": 13331,
|
||||||
"LoadStartHookOffset": "0x0FFC200",
|
"LoadStartHookOffset": "0x0FFC200",
|
||||||
"CDPFilterHookOffset": "0x2420100",
|
"CDPFilterHookOffset": "0x2420100",
|
||||||
"SceneOffsets": [1272, 1224]
|
"SceneOffsets": [1272, 1224, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13341,
|
"Version": 13341,
|
||||||
"LoadStartHookOffset": "0x10009E0",
|
"LoadStartHookOffset": "0x10009E0",
|
||||||
"CDPFilterHookOffset": "0x242E8E0",
|
"CDPFilterHookOffset": "0x242E8E0",
|
||||||
"SceneOffsets": [1272, 1224]
|
"SceneOffsets": [1272, 1224, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13487,
|
"Version": 13487,
|
||||||
"LoadStartHookOffset": "0x0FFB600",
|
"LoadStartHookOffset": "0x0FFB600",
|
||||||
"CDPFilterHookOffset": "0x241FEB0",
|
"CDPFilterHookOffset": "0x241FEB0",
|
||||||
"SceneOffsets": [1272, 1224]
|
"SceneOffsets": [1272, 1224, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13639,
|
"Version": 13639,
|
||||||
"LoadStartHookOffset": "0x1000990",
|
"LoadStartHookOffset": "0x1000990",
|
||||||
"CDPFilterHookOffset": "0x2424DE0",
|
"CDPFilterHookOffset": "0x2424DE0",
|
||||||
"SceneOffsets": [1272, 1224]
|
"SceneOffsets": [1272, 1224, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13655,
|
"Version": 13655,
|
||||||
"LoadStartHookOffset": "0x100F4B0",
|
"LoadStartHookOffset": "0x100F4B0",
|
||||||
"CDPFilterHookOffset": "0x244A9E0",
|
"CDPFilterHookOffset": "0x244A9E0",
|
||||||
"SceneOffsets": [1280, 1232]
|
"SceneOffsets": [1280, 1232, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13871,
|
"Version": 13871,
|
||||||
"LoadStartHookOffset": "0x101F160",
|
"LoadStartHookOffset": "0x101F160",
|
||||||
"CDPFilterHookOffset": "0x246FC40",
|
"CDPFilterHookOffset": "0x246FC40",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 13909,
|
"Version": 13909,
|
||||||
"LoadStartHookOffset": "0x101F0E0",
|
"LoadStartHookOffset": "0x101F0E0",
|
||||||
"CDPFilterHookOffset": "0x246FDC0",
|
"CDPFilterHookOffset": "0x246FDC0",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14161,
|
"Version": 14161,
|
||||||
"LoadStartHookOffset": "0x10246C0",
|
"LoadStartHookOffset": "0x10246C0",
|
||||||
"CDPFilterHookOffset": "0x24839B0",
|
"CDPFilterHookOffset": "0x24839B0",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14199,
|
"Version": 14199,
|
||||||
"LoadStartHookOffset": "0x10246F0",
|
"LoadStartHookOffset": "0x10246F0",
|
||||||
"CDPFilterHookOffset": "0x24839E0",
|
"CDPFilterHookOffset": "0x24839E0",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 14315,
|
"Version": 14315,
|
||||||
"LoadStartHookOffset": "0x10004C0",
|
"LoadStartHookOffset": "0x10004C0",
|
||||||
"CDPFilterHookOffset": "0x2424B50",
|
"CDPFilterHookOffset": "0x2424B50",
|
||||||
"SceneOffsets": [1272, 1224]
|
"SceneOffsets": [1272, 1224, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16133,
|
"Version": 16133,
|
||||||
"LoadStartHookOffset": "0x470FAD0",
|
"LoadStartHookOffset": "0x470FAD0",
|
||||||
"CDPFilterHookOffset": "0x90FC7E0",
|
"CDPFilterHookOffset": "0x90FC7E0",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16203,
|
"Version": 16203,
|
||||||
"LoadStartHookOffset": "0x4710890",
|
"LoadStartHookOffset": "0x4710890",
|
||||||
"CDPFilterHookOffset": "0x90FD640",
|
"CDPFilterHookOffset": "0x90FD640",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16389,
|
"Version": 16389,
|
||||||
"LoadStartHookOffset": "0x24E4830",
|
"LoadStartHookOffset": "0x24E4830",
|
||||||
"CDPFilterHookOffset": "0x2E2A880",
|
"CDPFilterHookOffset": "0x2E2A880",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16467,
|
"Version": 16467,
|
||||||
"LoadStartHookOffset": "0x24E4FD0",
|
"LoadStartHookOffset": "0x24E4FD0",
|
||||||
"CDPFilterHookOffset": "0x2E2CC90",
|
"CDPFilterHookOffset": "0x2E2CC90",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16771,
|
"Version": 16771,
|
||||||
"LoadStartHookOffset": "0x24E9130",
|
"LoadStartHookOffset": "0x24E9130",
|
||||||
"CDPFilterHookOffset": "0x2E3C470",
|
"CDPFilterHookOffset": "0x2E3C470",
|
||||||
"SceneOffsets": [1360, 1312]
|
"SceneOffsets": [1360, 1312, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16815,
|
"Version": 16815,
|
||||||
"LoadStartHookOffset": "0x2509690",
|
"LoadStartHookOffset": "0x2509690",
|
||||||
"CDPFilterHookOffset": "0x2E768D0",
|
"CDPFilterHookOffset": "0x2E768D0",
|
||||||
"SceneOffsets": [1416, 1360]
|
"SceneOffsets": [1416, 1360, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 16965,
|
"Version": 16965,
|
||||||
"LoadStartHookOffset": "0x2570220",
|
"LoadStartHookOffset": "0x2570220",
|
||||||
"CDPFilterHookOffset": "0x2F844A0",
|
"CDPFilterHookOffset": "0x2F844A0",
|
||||||
"SceneOffsets": [1416, 1360]
|
"SceneOffsets": [1416, 1360, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17037,
|
"Version": 17037,
|
||||||
"LoadStartHookOffset": "0x257D0A0",
|
"LoadStartHookOffset": "0x257D0A0",
|
||||||
"CDPFilterHookOffset": "0x2FB2310",
|
"CDPFilterHookOffset": "0x2FB2310",
|
||||||
"SceneOffsets": [1408, 1352]
|
"SceneOffsets": [1408, 1352, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17071,
|
"Version": 17071,
|
||||||
"LoadStartHookOffset": "0x258F370",
|
"LoadStartHookOffset": "0x258F370",
|
||||||
"CDPFilterHookOffset": "0x2FD3080",
|
"CDPFilterHookOffset": "0x2FD3080",
|
||||||
"SceneOffsets": [1408, 1352]
|
"SceneOffsets": [1408, 1352, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 17127,
|
"Version": 17127,
|
||||||
"LoadStartHookOffset": "0x2590910",
|
"LoadStartHookOffset": "0x2590910",
|
||||||
"CDPFilterHookOffset": "0x2FD4040",
|
"CDPFilterHookOffset": "0x2FD4040",
|
||||||
"SceneOffsets": [1408, 1352]
|
"SceneOffsets": [1408, 1352, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18055,
|
"Version": 18055,
|
||||||
"LoadStartHookOffset": "0x25A1040",
|
"LoadStartHookOffset": "0x25A1040",
|
||||||
"CDPFilterHookOffset": "0x30031E0",
|
"CDPFilterHookOffset": "0x30031E0",
|
||||||
"SceneOffsets": [1416, 1352]
|
"SceneOffsets": [1416, 1352, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18151,
|
"Version": 18151,
|
||||||
"LoadStartHookOffset": "0x25A2E20",
|
"LoadStartHookOffset": "0x25A2E20",
|
||||||
"CDPFilterHookOffset": "0x3013D20",
|
"CDPFilterHookOffset": "0x3013D20",
|
||||||
"SceneOffsets": [1416, 1352]
|
"SceneOffsets": [1416, 1352, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18787,
|
"Version": 18787,
|
||||||
"LoadStartHookOffset": "0x25B2870",
|
"LoadStartHookOffset": "0x25B2870",
|
||||||
"CDPFilterHookOffset": "0x3028AD0",
|
"CDPFilterHookOffset": "0x3028AD0",
|
||||||
"SceneOffsets": [1408, 1344]
|
"SceneOffsets": [1408, 1344, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18891,
|
"Version": 18891,
|
||||||
"LoadStartHookOffset": "0x25B50C0",
|
"LoadStartHookOffset": "0x25B50C0",
|
||||||
"CDPFilterHookOffset": "0x30245E0",
|
"CDPFilterHookOffset": "0x30245E0",
|
||||||
"SceneOffsets": [1408, 1344]
|
"SceneOffsets": [1408, 1344, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 18955,
|
"Version": 18955,
|
||||||
"LoadStartHookOffset": "0x25B52C0",
|
"LoadStartHookOffset": "0x25B52C0",
|
||||||
"CDPFilterHookOffset": "0x30248B0",
|
"CDPFilterHookOffset": "0x30248B0",
|
||||||
"SceneOffsets": [1408, 1344]
|
"SceneOffsets": [1408, 1344, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"Version": 19027,
|
"Version": 19027,
|
||||||
"LoadStartHookOffset": "0x25B52D0",
|
"LoadStartHookOffset": "0x25B52D0",
|
||||||
"CDPFilterHookOffset": "0x3024AD0",
|
"CDPFilterHookOffset": "0x3024AD0",
|
||||||
"SceneOffsets": [1408, 1344]
|
"SceneOffsets": [1408, 1344, 488]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Version": 19201,
|
||||||
|
"LoadStartHookOffset": "0x25B5DD0",
|
||||||
|
"CDPFilterHookOffset": "0x301B3C0",
|
||||||
|
"SceneOffsets": [1376, 1312, 456]
|
||||||
|
}
|
||||||
@@ -72,11 +72,6 @@ const hookOnLoadScene = (a1, sceneOffsets) => {
|
|||||||
}
|
}
|
||||||
send("[hook] hook scene condition -> 1101");
|
send("[hook] hook scene condition -> 1101");
|
||||||
miniappScenePtr.writeInt(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) => {
|
const patchOnLoadStart = (base, config) => {
|
||||||
|
|||||||
+1
-3
@@ -4,7 +4,5 @@
|
|||||||
"cdp_port": "62000",
|
"cdp_port": "62000",
|
||||||
"debug_main": false,
|
"debug_main": false,
|
||||||
"debug_frida": false,
|
"debug_frida": false,
|
||||||
"selected_preload_scripts": [
|
"selected_preload_scripts": []
|
||||||
"bypass_debugger.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
syntax = "proto2";
|
|
||||||
|
|
||||||
package mmbizwxadevremote;
|
|
||||||
|
|
||||||
message WARemoteDebug_DataFormat {
|
|
||||||
optional uint32 cmd = 1;
|
|
||||||
optional string uuid = 2;
|
|
||||||
optional bytes data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_BaseReq {
|
|
||||||
optional uint32 clientVersion = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_BaseResp {
|
|
||||||
optional int32 errcode = 1;
|
|
||||||
optional string errmsg = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_CommReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_CommResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DebugMessage {
|
|
||||||
optional uint32 seq = 1;
|
|
||||||
optional uint32 after = 2;
|
|
||||||
optional string category = 3;
|
|
||||||
optional bytes data = 4;
|
|
||||||
optional uint32 compressAlgo = 5;
|
|
||||||
optional uint32 originalSize = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_SendDebugMessageReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
repeated WARemoteDebug_DebugMessage debugMessageList = 2;
|
|
||||||
optional uint32 recvAck = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_SendDebugMessageResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
optional uint32 sendAck = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_NewSendDebugMessageReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
repeated WARemoteDebug_DebugMessage debugMessageList = 2;
|
|
||||||
optional uint32 recvAck = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_NewSendDebugMessageResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
optional uint32 minAck = 2;
|
|
||||||
optional uint32 maxAck = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_MessageNotify {
|
|
||||||
repeated WARemoteDebug_DebugMessage debugMessageList = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_MessageNotifyResp {
|
|
||||||
optional uint32 recvAck = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_EventNotify {
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_EventNotifyResp {
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxHeartBeatReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional uint32 recvAck = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxHeartBeatResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevHeartBeatReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional uint32 recvAck = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevHeartBeatResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_RoomInfo {
|
|
||||||
optional bool joinRoom = 1;
|
|
||||||
optional string roomId = 2;
|
|
||||||
optional string originalMd5 = 3;
|
|
||||||
optional uint32 roomStatus = 4;
|
|
||||||
optional uint32 wxConnStatus = 5;
|
|
||||||
optional uint32 devConnStatus = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxLoginReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional string loginTicket = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxLoginResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
optional WARemoteDebug_RoomInfo roomInfo = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevLoginReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional string newticket = 2;
|
|
||||||
optional uint32 autodev = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevLoginResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
optional WARemoteDebug_RoomInfo roomInfo = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxJoinRoomReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional string username = 2;
|
|
||||||
optional string roomId = 3;
|
|
||||||
optional string wxpkgInfo = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxJoinRoomResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevJoinRoomReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional string appid = 2;
|
|
||||||
optional string roomId = 3;
|
|
||||||
optional string wxpkgInfo = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevJoinRoomResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxQuitRoomReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxQuitRoomResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevQuitRoomReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevQuitRoomResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxSyncMessageReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional uint32 minSeq = 2;
|
|
||||||
optional uint32 maxSeq = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_WxSyncMessageResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
repeated WARemoteDebug_DebugMessage debugMessageList = 2;
|
|
||||||
optional uint32 sendAck = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevSyncMessageReq {
|
|
||||||
optional WARemoteDebug_BaseReq baseRequest = 1;
|
|
||||||
optional uint32 minSeq = 2;
|
|
||||||
optional uint32 maxSeq = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DevSyncMessageResp {
|
|
||||||
optional WARemoteDebug_BaseResp baseResponse = 1;
|
|
||||||
repeated WARemoteDebug_DebugMessage debugMessageList = 2;
|
|
||||||
optional uint32 sendAck = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_MethodWithArgs {
|
|
||||||
optional string methodName = 1;
|
|
||||||
repeated string methodArgList = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_RegisterInterface {
|
|
||||||
optional string objName = 1;
|
|
||||||
repeated WARemoteDebug_MethodWithArgs objMethodList = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DeviceInfo {
|
|
||||||
optional string deviceName = 1;
|
|
||||||
optional string deviceModel = 2;
|
|
||||||
optional string systemVersion = 3;
|
|
||||||
optional string wechatVersion = 4;
|
|
||||||
optional uint32 publibVersion = 5;
|
|
||||||
optional float screenWidth = 6;
|
|
||||||
optional float pixelRatio = 7;
|
|
||||||
optional string userAgent = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_SetupContext {
|
|
||||||
optional WARemoteDebug_RegisterInterface registerInterface = 1;
|
|
||||||
optional WARemoteDebug_DeviceInfo deviceInfo = 2;
|
|
||||||
optional string configureJs = 3;
|
|
||||||
optional string publicJsMd5 = 4;
|
|
||||||
optional string threeJsMd5 = 5;
|
|
||||||
optional uint32 supportCompressAlgo = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_CallInterface {
|
|
||||||
optional string objName = 1;
|
|
||||||
optional string methodName = 2;
|
|
||||||
repeated string methodArgList = 3;
|
|
||||||
optional uint32 callId = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_CallInterfaceResult {
|
|
||||||
optional string ret = 1;
|
|
||||||
optional uint32 callId = 2;
|
|
||||||
optional string debugInfo = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_EvaluateJavascript {
|
|
||||||
optional string script = 1;
|
|
||||||
optional uint32 evaluateId = 2;
|
|
||||||
optional string debugInfo = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_EvaluateJavascriptResult {
|
|
||||||
optional string ret = 1;
|
|
||||||
optional uint32 evaluateId = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_Breakpoint {
|
|
||||||
optional bool isHit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_Ping {
|
|
||||||
optional uint64 pingId = 1;
|
|
||||||
optional string payload = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_Pong {
|
|
||||||
optional uint64 pingId = 1;
|
|
||||||
optional uint32 networkType = 2;
|
|
||||||
optional string payload = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DomOp {
|
|
||||||
optional string params = 1;
|
|
||||||
optional uint32 webviewId = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_DomEvent {
|
|
||||||
optional string params = 1;
|
|
||||||
optional uint32 webviewId = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_NetworkDebugAPI {
|
|
||||||
optional string apiName = 1;
|
|
||||||
optional string taskId = 2;
|
|
||||||
optional string requestHeaders = 3;
|
|
||||||
optional uint64 timestamp = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_ChromeDevtools {
|
|
||||||
optional uint64 opId = 1;
|
|
||||||
optional string payload = 2;
|
|
||||||
optional string jscontextId = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_ChromeDevtoolsResult {
|
|
||||||
optional uint64 opId = 1;
|
|
||||||
optional string payload = 2;
|
|
||||||
optional string jscontextId = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_AddJsContext {
|
|
||||||
optional string jscontextId = 1;
|
|
||||||
optional string jscontextName = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_RemoveJsContext {
|
|
||||||
optional string jscontextId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_ConnectJsContext {
|
|
||||||
optional string jscontextId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WARemoteDebug_CustomMessage {
|
|
||||||
optional string method = 1;
|
|
||||||
optional string payload = 2;
|
|
||||||
optional bytes raw = 3;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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)
|
||||||
|
|||||||
+6
-1
@@ -340,7 +340,7 @@ class DebugEngine:
|
|||||||
logger.info(f"[userscript] immediate inject (Runtime.evaluate): {script.name}")
|
logger.info(f"[userscript] immediate inject (Runtime.evaluate): {script.name}")
|
||||||
engine.message_counter = seq
|
engine.message_counter = seq
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[userscript] immediate inject error: {e}")
|
logger.warn(f"[userscript] immediate inject warning: {e}")
|
||||||
except websockets.exceptions.ConnectionClosed:
|
except websockets.exceptions.ConnectionClosed:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -349,6 +349,11 @@ class DebugEngine:
|
|||||||
engine.miniapp_clients.discard(websocket)
|
engine.miniapp_clients.discard(websocket)
|
||||||
if not engine.miniapp_clients:
|
if not engine.miniapp_clients:
|
||||||
engine._notify_status("miniapp", False)
|
engine._notify_status("miniapp", False)
|
||||||
|
# 清理所有挂起的 CDP 响应,防止 future 泄漏
|
||||||
|
for fid, fut in list(engine._pending_responses.items()):
|
||||||
|
if not fut.done():
|
||||||
|
fut.set_exception(RuntimeError("miniapp disconnected"))
|
||||||
|
engine._pending_responses.clear()
|
||||||
logger.info("[miniapp] miniapp client disconnected")
|
logger.info("[miniapp] miniapp client disconnected")
|
||||||
|
|
||||||
server = await websockets.server.serve(
|
server = await websockets.server.serve(
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
+32
-1
@@ -32,7 +32,10 @@ class MiniProgramNavigator:
|
|||||||
"tabBar:window.nav?window.nav.tabBarPages:[],"
|
"tabBar:window.nav?window.nav.tabBarPages:[],"
|
||||||
"appid:window.nav&&window.nav.config?(window.nav.config.appid||''):'',"
|
"appid:window.nav&&window.nav.config?(window.nav.config.appid||''):'',"
|
||||||
"entry:window.nav&&window.nav.config?(window.nav.config.entryPagePath||''):'',"
|
"entry:window.nav&&window.nav.config?(window.nav.config.entryPagePath||''):'',"
|
||||||
"name:(function(){try{var a=window.__wxConfig||window.wx&&window.wx.__wxConfig||{};"
|
"name:(function(){try{"
|
||||||
|
"var f=window.nav&&window.nav.wxFrame;"
|
||||||
|
"var a=(f&&f.__wxConfig)||window.__wxConfig||"
|
||||||
|
"(window.wx&&window.wx.__wxConfig)||{};"
|
||||||
"var b=a.accountInfo&&a.accountInfo.appAccount;"
|
"var b=a.accountInfo&&a.accountInfo.appAccount;"
|
||||||
"return b&&b.nickname||a.appname||''}catch(e){return ''}})()"
|
"return b&&b.nickname||a.appname||''}catch(e){return ''}})()"
|
||||||
"})",
|
"})",
|
||||||
@@ -80,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.
Binary file not shown.
Binary file not shown.
+223
@@ -0,0 +1,223 @@
|
|||||||
|
"""
|
||||||
|
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)"""
|
||||||
|
if os.name != "nt":
|
||||||
|
return None
|
||||||
|
user_home = os.path.expanduser("~")
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user