fix: address review feedback on docs

- decrypt_page: zero-fill reserve for all pages (consistency)
- Move reserve into configs tuple for clarity
- Remove unused import os
- Trim duplicated permission content, reference permission guide
- Replace empty scan_keys.js shell with find_all_keys_macos reference
feat/daemon-cli
bbingz 2026-03-05 21:55:10 +08:00
parent 98933d5987
commit d4314c4857
1 changed files with 33 additions and 128 deletions

View File

@ -58,104 +58,27 @@
2. 安装 Frida`pip3 install frida-tools` 或 `brew install frida` 2. 安装 Frida`pip3 install frida-tools` 或 `brew install frida`
3. 管理员密码sudo 权限) 3. 管理员密码sudo 权限)
### macOS 权限模型详解 ### macOS 权限要求
Frida 附加到微信进程需要调用 `task_for_pid()` 这个 Mach 内核接口。macOS 的 `taskgated` 守护进程控制谁能调用它,核心逻辑取决于**目标进程的代码签名状态** 密钥提取需要调用 `task_for_pid()`,能否成功取决于**微信 App 的代码签名状态**
``` - **Ad-hoc 签名**(如安装了防撤回补丁):`sudo` 即可SSH 也行
taskgated 授权检查流程: - **Apple 官方签名**(有 Hardened Runtime需要本机 Terminal + sudoSSH 不可行
调用者 sudo task_for_pid(target_pid)
┌─ 目标进程有 Hardened Runtime 吗?──────────────────────────┐
│ │
│ YES (flags 包含 0x10000 runtime) │
│ → Apple 官方签名的 App, 如 App Store 下载的微信 │
│ → taskgated 严格模式: │
│ 1. 调用者必须是 root (sudo) │
│ 2. 调用者的"负责应用"必须有 TCC DeveloperTool 授权 │
│ 3. SSH 上下文的负责应用是 sshd → 无法获得 TCC 授权 │
│ 4. 本机 Terminal 可以弹窗获得 TCC 授权 ✅ │
│ → SSH 永远失败,本机 Terminal + sudo 可以 │
│ │
│ NO (Ad-hoc 签名, 或无 hardened runtime) │
│ → 自签名/防撤回补丁修改过的 App │
│ → taskgated 宽松模式: │
│ 1. 调用者是 root (sudo) 即可 ✅ │
│ 2. 不检查 TCC DeveloperTool │
│ 3. SSH sudo 也能成功! ✅ │
│ │
└────────────────────────────────────────────────────────────┘
```
#### 🔑 关键发现:决定因素是目标 App 的签名,不是终端的权限!
我们实测发现:
| 场景 | WeChat 签名 | codesign flags | sudo task_for_pid | SSH 可行? |
|------|------------|----------------|-------------------|-----------|
| MacBook (macOS 15.x) | **Ad-hoc** (防撤回补丁自签名) | `0x2(adhoc)` | ✅ 直接成功 | ✅ **可行** |
| Mac mini (Catalina) | Apple 官方签名 | `runtime` | ✅ 本机 Terminal | ❌ SSH 失败 |
| MacBook Pro (Big Sur) | Apple 官方签名 | `runtime` | ✅ 本机 Terminal | ❌ SSH 失败 |
本机 WeChat 的签名信息(安装了防撤回补丁后):
```
Signature=adhoc
TeamIdentifier=not set
flags=0x2(adhoc) ← 没有 hardened runtime!
Internal requirements=0 ← 没有签名要求
Entitlements=无 ← 没有 entitlements
```
正常 Apple 签名的 WeChat
```
Signature=Apple Developer
TeamIdentifier=5A4RE8SF68
flags=0x10002(adhoc,runtime) ← 有 hardened runtime!
```
#### 这意味着什么?
如果你想通过 SSH 远程提取密钥,有两条路:
**方法 1: 对微信 App 重新签名 (推荐,不需要关 SIP)**
```bash ```bash
# 在目标机器上执行SSH 即可): # 检查微信签名状态
# 1. 去掉 hardened runtime用 ad-hoc 重签名 codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
sudo codesign --force --deep --sign - /Applications/WeChat.app # Ad-hoc: flags=0x2(adhoc) → sudo 直接可用
# Apple: flags=0x10000(runtime) → 需本机 Terminal 或先重签名
# 2. 重启微信 (需要用户在 GUI 操作,或用 kill + open)
kill $(pgrep -x WeChat)
# 用户需要在 GUI 上重新打开微信并登录
# 3. 现在 SSH sudo 就能 task_for_pid 了!
sudo ./find_all_keys_macos
``` ```
⚠️ **副作用** 如果需要 SSH 远程操作,可以重签名微信去掉 Hardened Runtime
- 重签名后微信可能无法自动更新 ```bash
- 某些 iCloud/Keychain 功能可能受影响 sudo codesign --force --deep --sign - /Applications/WeChat.app
- 微信小程序可能报安全错误 # 重启微信后 SSH sudo 即可提取密钥
- 需要重新登录微信 ```
**方法 2: 关闭 SIP (不推荐)** > 📖 完整的权限模型、SSH 配置、常见误区详见 [macOS 权限完全指南](macos-permission-guide.md)
- 需要重启进入恢复模式
- 安全风险大,影响整个系统
#### 常见误区
| 误区 | 真相 |
|------|------|
| "需要给终端完全磁盘访问才能调试" | ❌ FDA 控制文件访问,不控制进程调试。但 SSH 重签名 App 时需要 FDA |
| "需要给终端开发者工具权限" | ⚠️ 仅当目标 App 有 hardened runtime 时才需要 |
| "SSH 下永远无法 task_for_pid" | ❌ 如果目标 App 是 ad-hoc 签名的SSH sudo 可以 |
| "macOS 版本决定了能否 SSH 调试" | ❌ 主要取决于目标 App 的签名状态 |
| "SIP 阻止了调试微信" | ❌ SIP 只保护系统进程,微信不受 SIP 保护 |
| "加了 sshd 到 FDA 就行" | ❌ 还需要加 `/usr/libexec/sshd-keygen-wrapper`,且要重连 SSH |
| "微信开着也能重签名" | ❌ 运行中的 binary/dylib 被占用codesign 会失败 |
> 📖 详细权限指南见 [macOS 权限完全指南](macos-permission-guide.md)
### 新手操作步骤 ### 新手操作步骤
@ -188,40 +111,22 @@ sudo codesign --force --deep --sign - /Applications/WeChat.app
### 实际操作步骤 ### 实际操作步骤
#### 步骤 1: 找到微信进程 PID #### 方法 A: 使用 C 版扫描器推荐4.x
```bash ```bash
pgrep -x WeChat # 编译
# 输出例如: 51051 cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
# 运行(自动查找微信进程、扫描内存、匹配 DB salt
sudo ./find_all_keys_macos
``` ```
#### 步骤 2: 准备 Frida 扫描脚本 扫描器会在内存中搜索 `x'<64hex_key><32hex_salt>'` 格式的密钥,自动匹配 DB 文件的 salt输出 `all_keys.json`
创建 `scan_keys.js` #### 方法 B: 使用 Frida3.x / 通用)
```javascript
// 扫描内存中所有看起来像加密密钥的 hex 字符串
// WeChat 3.x: 64字符hex (32字节key)
// WeChat 4.x: 也可能是64字符hex或96字符hex (48字节)
var ranges = Process.enumerateRanges('r--');
var pattern_64 = /^[0-9a-f]{64}$/;
ranges.forEach(function(range) {
try {
var buf = range.base.readByteArray(range.size);
// 实际扫描逻辑...扫描连续的hex字符
} catch(e) {}
});
```
#### 步骤 3: 在本机 Terminal 用 Frida 注入
```bash ```bash
# 方法 A: 用自己的脚本 (如果你已知内存地址) # 附加到微信进程,手动 dump 内存搜索 32 字节密钥
sudo frida -p $(pgrep -x WeChat) --debug -l debug.js
# 方法 B: 用扫描脚本自动搜索
sudo frida -p $(pgrep -x WeChat) -l scan_keys.js sudo frida -p $(pgrep -x WeChat) -l scan_keys.js
``` ```
@ -302,7 +207,7 @@ HMAC_LEN = 64
#!/usr/bin/env python3 #!/usr/bin/env python3
"""WeChat 3.x macOS 数据库解密器""" """WeChat 3.x macOS 数据库解密器"""
import hashlib, hmac, struct, os, shutil import hashlib, hmac, struct, shutil
from Crypto.Cipher import AES from Crypto.Cipher import AES
def decrypt_page(page_data, enc_key, page_no, page_size, reserve): def decrypt_page(page_data, enc_key, page_no, page_size, reserve):
@ -323,7 +228,8 @@ def decrypt_page(page_data, enc_key, page_no, page_size, reserve):
# 拼回 SQLite 头: "SQLite format 3\0" + 解密内容 + reserve填零 # 拼回 SQLite 头: "SQLite format 3\0" + 解密内容 + reserve填零
return b'SQLite format 3\x00' + decrypted + b'\x00' * reserve return b'SQLite format 3\x00' + decrypted + b'\x00' * reserve
else: else:
return decrypted + page_data[page_size - reserve:] # Reserve 区填零SQLite 不读取该区域,清零保持输出干净)
return decrypted + b'\x00' * reserve
def verify_hmac_page1(page_data, enc_key, page_size, reserve): def verify_hmac_page1(page_data, enc_key, page_size, reserve):
@ -359,22 +265,21 @@ def decrypt_db(db_path, raw_key_hex, output_path):
salt = data[:16] salt = data[:16]
# 尝试的参数组合: (page_size, use_pbkdf2) # 尝试的参数组合: (page_size, use_pbkdf2, reserve)
# SQLCipher 3 reserve = 48: IV(16) + HMAC-SHA1(20) + padding(12)
configs = [ configs = [
(1024, False), # 大部分 DB (1024, False, 48), # 大部分 DB
(4096, False), # WebTemplate (4096, False, 48), # WebTemplate
(1024, True), # FTS 索引 (1024, True, 48), # FTS 索引
(4096, True), # mediaData (4096, True, 48), # mediaData
] ]
for page_size, use_pbkdf2 in configs: for page_size, use_pbkdf2, reserve in configs:
if use_pbkdf2: if use_pbkdf2:
enc_key = hashlib.pbkdf2_hmac('sha1', raw_key, salt, 64000, dklen=32) enc_key = hashlib.pbkdf2_hmac('sha1', raw_key, salt, 64000, dklen=32)
else: else:
enc_key = raw_key enc_key = raw_key
reserve = 48 # SQLCipher 3 固定
if verify_hmac_page1(data, enc_key, page_size, reserve): if verify_hmac_page1(data, enc_key, page_size, reserve):
# HMAC 验证通过,开始解密 # HMAC 验证通过,开始解密
num_pages = len(data) // page_size num_pages = len(data) // page_size