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`
3. 管理员密码sudo 权限)
### macOS 权限模型详解
### macOS 权限要求
Frida 附加到微信进程需要调用 `task_for_pid()` 这个 Mach 内核接口。macOS 的 `taskgated` 守护进程控制谁能调用它,核心逻辑取决于**目标进程的代码签名状态**
密钥提取需要调用 `task_for_pid()`,能否成功取决于**微信 App 的代码签名状态**
```
taskgated 授权检查流程:
- **Ad-hoc 签名**(如安装了防撤回补丁):`sudo` 即可SSH 也行
- **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
# 在目标机器上执行SSH 即可):
# 1. 去掉 hardened runtime用 ad-hoc 重签名
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 2. 重启微信 (需要用户在 GUI 操作,或用 kill + open)
kill $(pgrep -x WeChat)
# 用户需要在 GUI 上重新打开微信并登录
# 3. 现在 SSH sudo 就能 task_for_pid 了!
sudo ./find_all_keys_macos
# 检查微信签名状态
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
# Ad-hoc: flags=0x2(adhoc) → sudo 直接可用
# Apple: flags=0x10000(runtime) → 需本机 Terminal 或先重签名
```
⚠️ **副作用**
- 重签名后微信可能无法自动更新
- 某些 iCloud/Keychain 功能可能受影响
- 微信小程序可能报安全错误
- 需要重新登录微信
如果需要 SSH 远程操作,可以重签名微信去掉 Hardened Runtime
```bash
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 重启微信后 SSH sudo 即可提取密钥
```
**方法 2: 关闭 SIP (不推荐)**
- 需要重启进入恢复模式
- 安全风险大,影响整个系统
#### 常见误区
| 误区 | 真相 |
|------|------|
| "需要给终端完全磁盘访问才能调试" | ❌ 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)
> 📖 完整的权限模型、SSH 配置、常见误区详见 [macOS 权限完全指南](macos-permission-guide.md)
### 新手操作步骤
@ -188,40 +111,22 @@ sudo codesign --force --deep --sign - /Applications/WeChat.app
### 实际操作步骤
#### 步骤 1: 找到微信进程 PID
#### 方法 A: 使用 C 版扫描器推荐4.x
```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`
```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 注入
#### 方法 B: 使用 Frida3.x / 通用)
```bash
# 方法 A: 用自己的脚本 (如果你已知内存地址)
sudo frida -p $(pgrep -x WeChat) --debug -l debug.js
# 方法 B: 用扫描脚本自动搜索
# 附加到微信进程,手动 dump 内存搜索 32 字节密钥
sudo frida -p $(pgrep -x WeChat) -l scan_keys.js
```
@ -302,7 +207,7 @@ HMAC_LEN = 64
#!/usr/bin/env python3
"""WeChat 3.x macOS 数据库解密器"""
import hashlib, hmac, struct, os, shutil
import hashlib, hmac, struct, shutil
from Crypto.Cipher import AES
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填零
return b'SQLite format 3\x00' + decrypted + b'\x00' * reserve
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):
@ -359,22 +265,21 @@ def decrypt_db(db_path, raw_key_hex, output_path):
salt = data[:16]
# 尝试的参数组合: (page_size, use_pbkdf2)
# 尝试的参数组合: (page_size, use_pbkdf2, reserve)
# SQLCipher 3 reserve = 48: IV(16) + HMAC-SHA1(20) + padding(12)
configs = [
(1024, False), # 大部分 DB
(4096, False), # WebTemplate
(1024, True), # FTS 索引
(4096, True), # mediaData
(1024, False, 48), # 大部分 DB
(4096, False, 48), # WebTemplate
(1024, True, 48), # FTS 索引
(4096, True, 48), # mediaData
]
for page_size, use_pbkdf2 in configs:
for page_size, use_pbkdf2, reserve in configs:
if use_pbkdf2:
enc_key = hashlib.pbkdf2_hmac('sha1', raw_key, salt, 64000, dklen=32)
else:
enc_key = raw_key
reserve = 48 # SQLCipher 3 固定
if verify_hmac_page1(data, enc_key, page_size, reserve):
# HMAC 验证通过,开始解密
num_pages = len(data) // page_size