mirror of https://github.com/jackwener/wx-cli.git
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 referencefeat/daemon-cli
parent
98933d5987
commit
d4314c4857
|
|
@ -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 + sudo,SSH 不可行
|
||||||
|
|
||||||
调用者 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: 使用 Frida(3.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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue