mirror of https://github.com/jackwener/wx-cli.git
docs: add macOS permission guide and 3.x vs 4.x decryption comparison
- macOS permission guide: SIP, task_for_pid, codesign requirements - 3.x vs 4.x decryption guide: SQLCipher parameter differences, multi-config DB handling, complete Python decryption examplesfeat/daemon-cli
parent
24ae180669
commit
98933d5987
|
|
@ -0,0 +1,471 @@
|
|||
# WeChat macOS 数据库解密指南:3.x vs 4.x 完整对比
|
||||
|
||||
## 一、背景
|
||||
|
||||
微信 macOS 版使用 SQLCipher 加密本地数据库。不同大版本的加密参数完全不同,解密方法不能混用。
|
||||
|
||||
| 项目 | WeChat 3.x (≤3.8.x) | WeChat 4.x (≥4.0.x) |
|
||||
|------|---------------------|---------------------|
|
||||
| SQLCipher 版本 | **3** | **4** |
|
||||
| 默认 page_size | **1024** | **4096** |
|
||||
| HMAC 算法 | HMAC-**SHA1** (20 bytes) | HMAC-**SHA512** (64 bytes) |
|
||||
| Reserve 区大小 | **48** bytes (IV16 + HMAC20 + pad12) | **80** bytes (IV16 + HMAC64) |
|
||||
| KDF 迭代次数 | **64,000** | **256,000** |
|
||||
| KDF 算法 | PBKDF2-SHA1 | PBKDF2-SHA512 |
|
||||
| 密钥使用方式 | 32字节 raw key **直接使用** | 32字节 raw key **直接使用** |
|
||||
|
||||
---
|
||||
|
||||
## 二、数据存放位置
|
||||
|
||||
### WeChat 3.x
|
||||
|
||||
```
|
||||
~/Library/Containers/com.tencent.xinWeChat/Data/
|
||||
Library/Application Support/com.tencent.xinWeChat/
|
||||
2.0b4.0.9/<account_md5_hash>/
|
||||
Message/msg_0.db ~ msg_9.db ← 聊天消息 (按hash分片)
|
||||
Contact/wccontact_new2.db ← 联系人
|
||||
Session/session_new.db ← 会话列表
|
||||
Group/group_new.db ← 群信息
|
||||
Favorites/favorites.db ← 收藏
|
||||
...共约 34 个 DB
|
||||
```
|
||||
|
||||
### WeChat 4.x
|
||||
|
||||
```
|
||||
~/Library/Containers/com.tencent.xinWeChat/Data/
|
||||
Documents/xwechat_files/<account_id>/
|
||||
db_storage/
|
||||
message/message_0.db ~ message_5.db ← 聊天消息
|
||||
contact/contact.db ← 联系人
|
||||
session/session.db ← 会话列表
|
||||
...
|
||||
```
|
||||
|
||||
**关键区别**: 3.x 用 MD5 hash 做账号目录名(看不出是谁),4.x 用微信ID做目录名。
|
||||
|
||||
---
|
||||
|
||||
## 三、密钥提取(核心步骤)
|
||||
|
||||
两个版本的密钥提取方式完全一样:**从微信进程内存中读取 32 字节 raw key**。
|
||||
|
||||
### 前提条件
|
||||
|
||||
1. 微信已登录且正在运行
|
||||
2. 安装 Frida:`pip3 install frida-tools` 或 `brew install frida`
|
||||
3. 管理员密码(sudo 权限)
|
||||
|
||||
### macOS 权限模型详解
|
||||
|
||||
Frida 附加到微信进程需要调用 `task_for_pid()` 这个 Mach 内核接口。macOS 的 `taskgated` 守护进程控制谁能调用它,核心逻辑取决于**目标进程的代码签名状态**:
|
||||
|
||||
```
|
||||
taskgated 授权检查流程:
|
||||
|
||||
调用者 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
|
||||
```
|
||||
|
||||
⚠️ **副作用**:
|
||||
- 重签名后微信可能无法自动更新
|
||||
- 某些 iCloud/Keychain 功能可能受影响
|
||||
- 微信小程序可能报安全错误
|
||||
- 需要重新登录微信
|
||||
|
||||
**方法 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)
|
||||
|
||||
### 新手操作步骤
|
||||
|
||||
根据你的微信签名状态,选择对应方案:
|
||||
|
||||
```bash
|
||||
# 首先检查你的微信签名状态
|
||||
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
|
||||
|
||||
# 如果显示 Signature=adhoc, flags=0x2(adhoc)
|
||||
# → 恭喜!直接 sudo 即可,SSH 也行
|
||||
sudo ./find_all_keys_macos
|
||||
|
||||
# 如果显示 Authority=..Apple.., flags 包含 runtime
|
||||
# → 需要本机 Terminal 操作,或者先重签名:
|
||||
sudo codesign --force --deep --sign - /Applications/WeChat.app
|
||||
# 然后重启微信,再用 sudo 提取密钥
|
||||
```
|
||||
|
||||
### SSH 远程提取方案(需 ad-hoc 签名)
|
||||
|
||||
以下方法全部在 Apple 官方签名的微信上失败(经多台机器穷举验证):
|
||||
- `sudo frida -p <pid>` → "unable to access process"
|
||||
- `lldb -p <pid>` → "non-interactive debug session"
|
||||
- `sudo gcore <pid>` → "insufficient privilege"
|
||||
- 自编译带 `com.apple.security.cs.debugger` entitlement 的 C 程序 → KERN_FAILURE=5
|
||||
- `vmmap`/`heap` → 只能看元数据,无法读内存内容
|
||||
- LaunchDaemon (root) / LaunchAgent (Aqua) / `launchctl asuser` → 全部失败
|
||||
- 修改 TCC.db → SIP 保护,`restricted` 标志,只读
|
||||
|
||||
### 实际操作步骤
|
||||
|
||||
#### 步骤 1: 找到微信进程 PID
|
||||
|
||||
```bash
|
||||
pgrep -x WeChat
|
||||
# 输出例如: 51051
|
||||
```
|
||||
|
||||
#### 步骤 2: 准备 Frida 扫描脚本
|
||||
|
||||
创建 `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 注入
|
||||
|
||||
```bash
|
||||
# 方法 A: 用自己的脚本 (如果你已知内存地址)
|
||||
sudo frida -p $(pgrep -x WeChat) --debug -l debug.js
|
||||
|
||||
# 方法 B: 用扫描脚本自动搜索
|
||||
sudo frida -p $(pgrep -x WeChat) -l scan_keys.js
|
||||
```
|
||||
|
||||
输出示例(3.x 实际结果):
|
||||
|
||||
```
|
||||
600000d8d930 72 8e 8e dd 26 68 48 37 92 89 2c 7b 24 10 58 9d r...&hH7..,{$.X.
|
||||
600000d8d940 3e 64 1e e7 ef b3 47 c9 9f 17 3d 58 bf 9d 38 05 >d....G...=X..8.
|
||||
```
|
||||
|
||||
这 32 字节就是密钥:`728e8edd2668483792892c7b2410589d3e641ee7efb347c99f173d58bf9d3805`
|
||||
|
||||
---
|
||||
|
||||
## 四、解密实现
|
||||
|
||||
### 核心原理
|
||||
|
||||
SQLCipher 加密的每一页(page)结构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 第 1 页 (特殊) │
|
||||
├──────────┬──────────────────────┬───────────────────┤
|
||||
│ Salt │ 加密的数据 │ Reserve区 │
|
||||
│ 16 bytes │ (page_size-16-rsv) │ IV+HMAC+padding │
|
||||
├──────────┴──────────────────────┴───────────────────┤
|
||||
│ │
|
||||
│ 第 2~N 页 (普通页) │
|
||||
├────────────────────────────────┬────────────────────┤
|
||||
│ 加密的数据 │ Reserve区 │
|
||||
│ (page_size - reserve) │ IV + HMAC + padding │
|
||||
└────────────────────────────────┴────────────────────┘
|
||||
```
|
||||
|
||||
**第 1 页特殊处理**:前 16 字节是明文 salt(不加密),解密后需要拼回 `SQLite format 3\0` 头。
|
||||
|
||||
### WeChat 3.x 解密参数
|
||||
|
||||
```python
|
||||
# SQLCipher 3 参数
|
||||
PAGE_SIZE = 1024
|
||||
RESERVE = 48 # 16(IV) + 20(HMAC-SHA1) + 12(padding)
|
||||
KDF_ITER = 64000
|
||||
HMAC_ALGO = 'sha1'
|
||||
HMAC_LEN = 20
|
||||
```
|
||||
|
||||
### WeChat 4.x 解密参数
|
||||
|
||||
```python
|
||||
# SQLCipher 4 参数
|
||||
PAGE_SIZE = 4096
|
||||
RESERVE = 80 # 16(IV) + 64(HMAC-SHA512)
|
||||
KDF_ITER = 256000
|
||||
HMAC_ALGO = 'sha512'
|
||||
HMAC_LEN = 64
|
||||
```
|
||||
|
||||
### 3.x 的特殊陷阱:同一账号的 DB 使用不同参数!
|
||||
|
||||
这是 3.x 最坑的地方。我们实测发现同一个账号的 34 个 DB 居然用了 **4 种不同的 SQLCipher 配置**:
|
||||
|
||||
| DB 类别 | page_size | key 模式 |
|
||||
|---------|-----------|---------|
|
||||
| 大部分 DB (msg, contact, session...) | 1024 | raw key **直接使用** |
|
||||
| WebTemplate/webtemplate.db | 4096 | raw key **直接使用** |
|
||||
| FTS 索引 (ftsmessage, ftsfilemessage) | 1024 | PBKDF2(raw_key, salt, 64000) |
|
||||
| mediaData.db | 4096 | PBKDF2(raw_key, salt, 64000) |
|
||||
|
||||
还有 3 个 DB 根本没加密(kv_config, solitaire_chat, multiTalk),直接复制即可。
|
||||
|
||||
所以解密脚本必须自动判断并尝试多种组合。
|
||||
|
||||
### 完整解密代码(Python, 3.x)
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""WeChat 3.x macOS 数据库解密器"""
|
||||
|
||||
import hashlib, hmac, struct, os, shutil
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
def decrypt_page(page_data, enc_key, page_no, page_size, reserve):
|
||||
"""解密单个 page"""
|
||||
if page_no == 1:
|
||||
# 第1页: 前16字节是salt(明文), 后面才是加密数据
|
||||
salt = page_data[:16]
|
||||
encrypted = page_data[16:page_size - reserve]
|
||||
iv = page_data[page_size - reserve:page_size - reserve + 16]
|
||||
else:
|
||||
encrypted = page_data[:page_size - reserve]
|
||||
iv = page_data[page_size - reserve:page_size - reserve + 16]
|
||||
|
||||
cipher = AES.new(enc_key, AES.MODE_CBC, iv)
|
||||
decrypted = cipher.decrypt(encrypted)
|
||||
|
||||
if page_no == 1:
|
||||
# 拼回 SQLite 头: "SQLite format 3\0" + 解密内容 + reserve填零
|
||||
return b'SQLite format 3\x00' + decrypted + b'\x00' * reserve
|
||||
else:
|
||||
return decrypted + page_data[page_size - reserve:]
|
||||
|
||||
|
||||
def verify_hmac_page1(page_data, enc_key, page_size, reserve):
|
||||
"""验证第1页的 HMAC-SHA1 (SQLCipher 3)"""
|
||||
salt = page_data[:16]
|
||||
mac_salt = bytes([b ^ 0x3a for b in salt])
|
||||
mac_key = hashlib.pbkdf2_hmac('sha1', enc_key, mac_salt, 2, dklen=32)
|
||||
|
||||
content = page_data[16:page_size - reserve]
|
||||
iv = page_data[page_size - reserve:page_size - reserve + 16]
|
||||
stored_hmac = page_data[page_size - reserve + 16:page_size - reserve + 36]
|
||||
|
||||
msg = content + iv + struct.pack('<I', 1)
|
||||
calc_hmac = hmac.new(mac_key, msg, hashlib.sha1).digest()
|
||||
|
||||
return calc_hmac == stored_hmac
|
||||
|
||||
|
||||
def decrypt_db(db_path, raw_key_hex, output_path):
|
||||
"""
|
||||
解密单个数据库文件
|
||||
自动尝试多种 SQLCipher 参数组合
|
||||
"""
|
||||
raw_key = bytes.fromhex(raw_key_hex)
|
||||
|
||||
with open(db_path, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
# 检查是否已经是 SQLite (未加密)
|
||||
if data[:15] == b'SQLite format 3':
|
||||
shutil.copy2(db_path, output_path)
|
||||
return 'unencrypted'
|
||||
|
||||
salt = data[:16]
|
||||
|
||||
# 尝试的参数组合: (page_size, use_pbkdf2)
|
||||
configs = [
|
||||
(1024, False), # 大部分 DB
|
||||
(4096, False), # WebTemplate
|
||||
(1024, True), # FTS 索引
|
||||
(4096, True), # mediaData
|
||||
]
|
||||
|
||||
for page_size, use_pbkdf2 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
|
||||
output = b''
|
||||
for i in range(num_pages):
|
||||
page = data[i * page_size:(i + 1) * page_size]
|
||||
output += decrypt_page(page, enc_key, i + 1, page_size, reserve)
|
||||
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(output)
|
||||
|
||||
mode = 'pbkdf2' if use_pbkdf2 else 'direct'
|
||||
return f'ok (page={page_size}, {mode})'
|
||||
|
||||
return 'failed'
|
||||
```
|
||||
|
||||
**依赖安装**: `pip3 install pycryptodome`
|
||||
|
||||
### 4.x 的解密差异
|
||||
|
||||
4.x 的代码逻辑相同,只需改参数:
|
||||
- `reserve = 80`, HMAC 用 SHA512, `mac_key` 的 PBKDF2 也用 SHA512
|
||||
- `verify_hmac` 中 `stored_hmac` 长度为 64 字节
|
||||
- 4.x 中所有 DB 使用统一的参数(不像 3.x 那样混用多种配置)
|
||||
|
||||
---
|
||||
|
||||
## 五、新手操作清单
|
||||
|
||||
### 你需要准备什么
|
||||
|
||||
- [x] macOS 电脑,微信已登录
|
||||
- [x] Python 3 + pycryptodome (`pip3 install pycryptodome`)
|
||||
- [x] Frida (`pip3 install frida-tools`)
|
||||
- [x] 管理员密码(sudo 权限)
|
||||
|
||||
### 一步步操作
|
||||
|
||||
```bash
|
||||
# 1. 确认微信版本
|
||||
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/
|
||||
# 如果看到 2.0b4.0.9 → 3.x 版本
|
||||
# 如果看到其他 / Documents/xwechat_files → 4.x 版本
|
||||
|
||||
# 2. 找到你的账号目录
|
||||
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/2.0b4.0.9/
|
||||
# 最大的那个目录就是你的主账号
|
||||
|
||||
# 3. 确认数据库是加密的
|
||||
file ~/.../<account>/Message/msg_0.db
|
||||
# 应该显示 "data" 而不是 "SQLite 3.x database"
|
||||
|
||||
# 4. 提取密钥 (必须在本机 Terminal!)
|
||||
sudo frida -p $(pgrep -x WeChat) -l scan_keys.js
|
||||
# 记下输出的 64 字符 hex 字符串
|
||||
|
||||
# 5. 运行解密
|
||||
python3 decrypt_db.py
|
||||
|
||||
# 6. 验证
|
||||
file decrypted/Message/msg_0.db
|
||||
# 应该显示 "SQLite 3.x database"
|
||||
sqlite3 decrypted/Message/msg_0.db "SELECT COUNT(*) FROM (SELECT name FROM sqlite_master WHERE type='table')"
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
|
||||
| 问题 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| Frida 报 "unable to access process" | SSH 下运行 / TCC 未授权 | 必须在本机 Terminal 运行 |
|
||||
| 解密后文件打不开 | 参数不匹配 | 脚本会自动尝试4种配置 |
|
||||
| 部分 DB 用不同密钥 | ChatSync.db 等特殊 DB | 非关键数据,可跳过 |
|
||||
| "No module named Crypto" | 未安装 pycryptodome | `pip3 install pycryptodome` |
|
||||
| 3.x 和 4.x 混用参数 | 版本判断错误 | 先确认微信版本号 |
|
||||
|
||||
---
|
||||
|
||||
## 六、总结对比
|
||||
|
||||
```
|
||||
WeChat 3.x WeChat 4.x
|
||||
────────── ──────────
|
||||
SQLCipher 3 SQLCipher 4
|
||||
page 1024 (混用4096) page 4096 (统一)
|
||||
HMAC-SHA1, reserve 48 HMAC-SHA512, reserve 80
|
||||
KDF 64000 迭代 KDF 256000 迭代
|
||||
4种参数组合混用 (坑!) 统一参数 (简单)
|
||||
msg_0~msg_9.db message_0~message_5.db
|
||||
Chat_<hash> 表名 不同表结构
|
||||
密钥提取方式相同: Frida dump 32字节 密钥提取方式相同
|
||||
```
|
||||
|
||||
**核心经验**: 密钥提取是最难的一步(受 macOS TCC 限制),解密算法本身是确定的。3.x 比 4.x 更复杂,因为同一账号内的数据库使用了不同的加密参数组合。
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
# macOS WeChat 密钥提取:权限与签名完全指南
|
||||
|
||||
> 基于多台机器 (macOS 10.15 ~ 15.x, Intel + Apple Silicon) 的实测经验总结。
|
||||
|
||||
## 核心结论
|
||||
|
||||
能否从微信进程提取加密密钥,取决于 **两个独立问题**:
|
||||
|
||||
| 问题 | 控制什么 | 关键因素 |
|
||||
|------|---------|---------|
|
||||
| `task_for_pid()` 能否成功 | 读取进程内存 | **目标 App 的代码签名** |
|
||||
| `codesign` 能否重签名 | 修改 App 文件 | **调用者的完全磁盘访问** |
|
||||
|
||||
---
|
||||
|
||||
## 一、task_for_pid 权限(读取微信内存)
|
||||
|
||||
### 决定因素:微信 App 的 Hardened Runtime
|
||||
|
||||
```bash
|
||||
# 检查微信签名状态
|
||||
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
|
||||
```
|
||||
|
||||
#### 情况 A:Ad-hoc 签名(无 Hardened Runtime)
|
||||
|
||||
```
|
||||
flags=0x2(adhoc)
|
||||
Signature=adhoc
|
||||
TeamIdentifier=not set
|
||||
```
|
||||
|
||||
**原因**: 安装过防撤回补丁等第三方修改工具,App 被重新签名。
|
||||
|
||||
**权限要求**: 只需 `sudo`,任何上下文(Terminal、SSH、cron)都能成功。
|
||||
|
||||
```bash
|
||||
# SSH 远程直接可用
|
||||
sudo ./find_all_keys_macos
|
||||
```
|
||||
|
||||
#### 情况 B:Apple 官方签名(有 Hardened Runtime)
|
||||
|
||||
```
|
||||
flags=0x10000(runtime)
|
||||
Signature size=9092
|
||||
Authority=...Apple...
|
||||
```
|
||||
|
||||
**原因**: App Store 下载或官方 DMG 安装,未经修改。
|
||||
|
||||
**权限要求**: `sudo` + 本机 GUI 终端 + TCC "开发者工具"授权。SSH **不可行**。
|
||||
|
||||
```
|
||||
taskgated 检查流程:
|
||||
目标有 hardened runtime?
|
||||
YES → 检查调用者的"负责应用"是否有 TCC DeveloperTool 授权
|
||||
SSH 的负责应用是 sshd → 无法获得 TCC 授权 → 拒绝
|
||||
Terminal.app 可以弹窗获得授权 → 允许
|
||||
NO → root (sudo) 即可 → 允许
|
||||
```
|
||||
|
||||
### 实测数据
|
||||
|
||||
| 机器 | macOS | WeChat 签名 | 本机 Terminal sudo | SSH sudo |
|
||||
|------|-------|------------|-------------------|---------|
|
||||
| MacBook (macOS 15.x) | 15.x | **ad-hoc** (防撤回补丁) | ✅ | ✅ |
|
||||
| Mac mini (Catalina) | 10.15.8 | Apple 官方 runtime | ✅ | ❌ |
|
||||
| MacBook Pro (Big Sur) | 11.1 | Apple 官方 runtime | ✅ | ❌ |
|
||||
|
||||
### SSH 下穷举过的所有方法(Apple 签名时全部失败)
|
||||
|
||||
| 方法 | 结果 | 错误信息 |
|
||||
|------|------|---------|
|
||||
| `sudo frida -p <pid>` | ❌ | unable to access process |
|
||||
| `lldb -p <pid>` | ❌ | non-interactive debug session |
|
||||
| `sudo gcore <pid>` | ❌ | insufficient privilege |
|
||||
| 带 debugger entitlement 的 C 程序 | ❌ | KERN_FAILURE=5 |
|
||||
| `launchctl asuser` (用户会话) | ❌ | task_for_pid=5 |
|
||||
| LaunchAgent (Aqua GUI 会话) | ❌ | 非 root,需要 sudo |
|
||||
| LaunchDaemon (root) | ❌ | 系统域无 GUI 上下文 |
|
||||
| `launchctl submit` (root) | ❌ | 同上 |
|
||||
| `osascript` 操控 Terminal.app | ❌ | 需要辅助功能权限/挂起 |
|
||||
| 修改 TCC.db 给 sshd 授权 | ❌ | SIP 保护,restricted 只读 |
|
||||
| `vmmap` / `heap` | ⚠️ | 只能看元数据,无法读内存 |
|
||||
|
||||
---
|
||||
|
||||
## 二、codesign 权限(重签名微信 App)
|
||||
|
||||
如果微信是 Apple 官方签名,需要重签名为 ad-hoc 来解锁 SSH 提取。
|
||||
|
||||
### 问题:SSH 下 codesign 可能失败
|
||||
|
||||
```
|
||||
$ sudo codesign --force --deep --sign - /Applications/WeChat.app
|
||||
/Applications/WeChat.app: Operation not permitted
|
||||
In subcomponent: /Applications/WeChat.app/Contents/MacOS/WeChatAppEx.app
|
||||
```
|
||||
|
||||
**原因**: SSH 进程没有「完全磁盘访问」(Full Disk Access, FDA) 权限,无法修改 `/Applications` 下的 App bundle 文件。
|
||||
|
||||
### 给 SSH 授予完全磁盘访问
|
||||
|
||||
在目标机器的 **GUI** 上操作:
|
||||
|
||||
```
|
||||
系统偏好设置 → 安全性与隐私 → 隐私 → 完全磁盘访问
|
||||
点击 🔒 解锁 → 点 + 号 → Cmd+Shift+G 输入路径
|
||||
```
|
||||
|
||||
**必须添加这两个**(缺一不可):
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `/usr/sbin/sshd` | SSH 守护进程 |
|
||||
| `/usr/libexec/sshd-keygen-wrapper` | SSH 的实际执行进程(负责应用) |
|
||||
|
||||
> ⚠️ 添加后必须**断开 SSH 重新连接**!TCC 权限在进程启动时检查,不会热更新。
|
||||
|
||||
### 验证 FDA 是否生效
|
||||
|
||||
```bash
|
||||
# 重连 SSH 后执行
|
||||
cat ~/Library/Application\ Support/com.apple.TCC/TCC.db > /dev/null 2>&1 && echo "FDA: YES" || echo "FDA: NO"
|
||||
```
|
||||
|
||||
TCC.db 是受保护文件,只有 FDA 进程能读取。
|
||||
|
||||
### 完整流程:SSH 远程重签名微信
|
||||
|
||||
```bash
|
||||
# 0. 前提:SSH 已有 FDA(上面的步骤)
|
||||
|
||||
# 1. 确认微信已退出
|
||||
kill $(pgrep -x WeChat) 2>/dev/null
|
||||
sleep 2
|
||||
pgrep -x WeChat && echo "还在运行!" || echo "已退出"
|
||||
|
||||
# 2. 清除扩展属性(可选,防止干扰)
|
||||
sudo xattr -cr /Applications/WeChat.app
|
||||
|
||||
# 3. Ad-hoc 重签名
|
||||
sudo codesign --force --deep --sign - /Applications/WeChat.app
|
||||
|
||||
# 4. 验证签名
|
||||
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
|
||||
# 期望: flags=0x2(adhoc), Signature=adhoc
|
||||
|
||||
# 5. 用户需在 GUI 上重新打开微信并登录
|
||||
# (或者 SSH 执行 open,但用户仍需在 GUI 上完成登录)
|
||||
open /Applications/WeChat.app
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
| 事项 | 说明 |
|
||||
|------|------|
|
||||
| 微信必须先退出 | 运行中的 App,其 dylib/binary 被占用,codesign 会报 `internal error` |
|
||||
| **重签名后必须重启微信** | 已运行的进程仍使用旧签名的内存映像,task_for_pid 仍会失败。必须 kill 后重新启动 |
|
||||
| 重签名后需重新登录微信 | 签名变更会使登录态失效 |
|
||||
| 自动更新可能覆盖签名 | 微信更新后变回 Apple 签名,需要再次重签 |
|
||||
| 小程序可能受影响 | 部分小程序校验签名,ad-hoc 可能报安全错误 |
|
||||
|
||||
---
|
||||
|
||||
## 三、权限矩阵总结
|
||||
|
||||
| 操作 | 需要的权限 | SSH 需要额外配置 |
|
||||
|------|-----------|-----------------|
|
||||
| 读取微信数据库文件 | 文件系统权限(通常有) | 无 |
|
||||
| `task_for_pid` (ad-hoc App) | sudo | 无 |
|
||||
| `task_for_pid` (Apple 签名 App) | sudo + TCC DeveloperTool | **不可行**,必须本机 Terminal |
|
||||
| `codesign` 重签名 App | sudo + FDA | SSH 需添加 sshd + sshd-keygen-wrapper 到 FDA |
|
||||
| 修改 TCC.db | sudo + 关闭 SIP | **不推荐** |
|
||||
|
||||
### 完全远程操作清单(一次性 GUI 配置)
|
||||
|
||||
只需在目标机器 GUI 上做一次,之后 SSH 永久可用:
|
||||
|
||||
1. **完全磁盘访问** → 添加 `/usr/sbin/sshd` 和 `/usr/libexec/sshd-keygen-wrapper`
|
||||
2. SSH 连入 → `sudo codesign --force --deep --sign - /Applications/WeChat.app`
|
||||
3. 用户在 GUI 重开微信并登录
|
||||
4. 之后 SSH 永久可以 `sudo` 提取密钥,微信重启也不影响(除非更新覆盖签名)
|
||||
|
||||
---
|
||||
|
||||
## 四、常见误区
|
||||
|
||||
| 误区 | 真相 |
|
||||
|------|------|
|
||||
| "需要给终端完全磁盘访问才能调试" | ❌ FDA 控制文件访问,不控制进程调试 |
|
||||
| "需要给终端开发者工具权限" | ⚠️ 仅当目标 App 有 hardened runtime 时才需要 |
|
||||
| "SSH 下永远无法提取密钥" | ❌ 目标 App 是 ad-hoc 签名时,SSH sudo 可以 |
|
||||
| "macOS 版本决定了能否 SSH 调试" | ❌ 主要取决于目标 App 的签名状态 |
|
||||
| "SIP 阻止了调试微信" | ❌ SIP 只保护系统进程,微信不受 SIP 保护 |
|
||||
| "加了 sshd 到 FDA 就行" | ❌ 还需要加 `sshd-keygen-wrapper`,且要重连 SSH |
|
||||
| "微信开着也能重签名" | ❌ 运行中的 binary/dylib 被占用,codesign 会失败 |
|
||||
Loading…
Reference in New Issue