Initial commit: wx-cli again

Fresh start from botiverse/wx-cli (working tree at main, no prior history).
This commit is contained in:
jackwener
2026-09-14 15:43:35 +08:00
commit 077a54cbfe
76 changed files with 20860 additions and 0 deletions
+388
View File
@@ -0,0 +1,388 @@
# 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 权限要求
密钥提取需要调用 `task_for_pid()`,能否成功取决于**微信 App 的代码签名状态**:
- **Ad-hoc 签名**(如安装了防撤回补丁):`sudo` 即可,SSH 也行
- **Apple 官方签名**(有 Hardened Runtime):需要本机 Terminal + sudoSSH 不可行
```bash
# 检查微信签名状态
codesign -dv /Applications/WeChat.app 2>&1 | grep -E "Signature|flags"
# Ad-hoc: flags=0x2(adhoc) → sudo 直接可用
# Apple: flags=0x10000(runtime) → 需本机 Terminal 或先重签名
```
如果需要 SSH 远程操作,可以重签名微信去掉 Hardened Runtime
```bash
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 重启微信后 SSH sudo 即可提取密钥
```
> 📖 完整的权限模型、SSH 配置、常见误区详见 [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` 标志,只读
### 实际操作步骤
#### 方法 A: 使用 C 版扫描器(推荐,4.x)
```bash
# 编译
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
# 运行(自动查找微信进程、扫描内存、匹配 DB salt)
sudo ./find_all_keys_macos
```
扫描器会在内存中搜索 `x'<64hex_key><32hex_salt>'` 格式的密钥,自动匹配 DB 文件的 salt,输出 `all_keys.json`
#### 方法 B: 使用 Frida3.x / 通用)
```bash
# 附加到微信进程,手动 dump 内存搜索 32 字节密钥
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, 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填零
page = bytearray(b'SQLite format 3\x00' + decrypted + b'\x00' * reserve)
# 清除 header offset 20 的 reserved-space 字段
# 加密时该字段 = reserve size,解密后需要归零,否则 SQLite 误判 usable page size
page[20] = 0
return bytes(page)
else:
# Reserve 区填零(SQLite 不读取该区域,清零保持输出干净)
return decrypted + b'\x00' * 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, reserve)
# SQLCipher 3 reserve = 48: IV(16) + HMAC-SHA1(20) + padding(12)
configs = [
(1024, False, 48), # 大部分 DB
(4096, False, 48), # WebTemplate
(1024, True, 48), # FTS 索引
(4096, True, 48), # mediaData
]
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
if verify_hmac_page1(data, enc_key, page_size, reserve):
# HMAC 验证通过,开始解密
# 注意: 生产代码应对每一页都验证 HMAC,防止单页损坏/篡改
# 后续页的 HMAC 计算方式相同,只是 content 从 offset 0 开始(无 salt),
# 且 page_no 使用对应的页码(从 1 开始)
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!)
# 方法 A: 使用 C 工具(推荐,见本 repo 的 find_all_keys_macos.c
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
sudo ./find_all_keys_macos
# 输出 all_keys.json,可直接用于解密
# 方法 B: 使用 Frida(需自行编写扫描脚本)
# sudo frida -p $(pgrep -x WeChat) -l your_scan_script.js
# 5. 运行解密(需配置 config.json 指向 db_storage 目录)
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 更复杂,因为同一账号内的数据库使用了不同的加密参数组合。
+321
View File
@@ -0,0 +1,321 @@
# 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"
```
#### 情况 AAd-hoc 签名(无 Hardened Runtime
```
flags=0x2(adhoc)
Signature=adhoc
TeamIdentifier=not set
```
**原因**: 安装过防撤回补丁等第三方修改工具,App 被重新签名。
**权限要求**: 只需 `sudo`,任何上下文(Terminal、SSH、cron)都能成功。
```bash
# SSH 远程直接可用
sudo ./find_all_keys_macos
```
#### 情况 BApple 官方签名(有 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 会失败 |
---
## 五、重签名后微信权限 silent 失效
### 现象
完成 ad-hoc 重签名后,微信任意以下功能都可能"看起来已授权但实际被拒绝":
- 截图 / 屏幕共享(`ScreenCapture`
- 视频通话 / 扫码(`Camera`
- 语音消息 / 通话(`Microphone`
- 自动化、第三方输入法(`AppleEvents`
- 同步通讯录(`AddressBook`
- 文件发送 / 接收(`SystemPolicyDocumentsFolder` / `Downloads` / `Desktop`
System Settings 里通常仍看到"微信.app"开关是 ON,但运行时权限校验失败。微信会反复弹"需要开启 X 权限"。
### 根因(第一性原理)
macOS TCCTransparency, Consent, and Control)按 **bundle id + csreq** 联合校验权限。`csreq`code requirement)是从 app 的 code signature 推导出的二进制 blob,存在 `/Library/Application Support/com.apple.TCC/TCC.db``access` 表里,每条 ~160 字节。
`codesign --force --deep --sign -` 把 WeChat 从官方签名换成 ad-hoc 签名(甚至 ad-hoc → ad-hoc 重签也会变),新进程的 csreq 跟旧记录里那条对不上 —— tccd 拒绝。
System Settings UI 只按 client 显示开关、不重算 csreq,所以视觉上是"已授权",运行时实际拒绝。这是 silent drift。
### 修复步骤
把 WeChat 在 TCC 里的旧记录全部抹掉,让 macOS 在下次微信请求权限时按新签名重新生成 csreq:
```bash
for s in ScreenCapture Camera Microphone AppleEvents AddressBook \
SystemPolicyDocumentsFolder SystemPolicyDownloadsFolder SystemPolicyDesktopFolder; do
tccutil reset "$s" com.tencent.xinWeChat
done
```
`tccutil` 对没有授权过的 service 会报 "No such bundle identifier",这是 no-op,不影响其他 service 的 reset。
之后退出并重新打开微信,按 GUI 提示重新允许:
```bash
killall WeChat
open /Applications/WeChat.app
```
> 这一步**应当由用户/agent 手动执行**,不在 `wx init` 里自动跑——TCC 重置会让用户的现有授权失效,需要由人决定时机。
#### macOS 26 的 UI 拆分
在 macOS 26 上,**隐私与安全 → 录屏与系统录音** 显示为两块,容易踩坑:
| 区域 | 作用 |
|------|------|
| **录屏与系统录音**(上半区) | 录制屏幕内容 + 系统音频;微信截图、屏幕共享需要这一项 |
| **仅系统录音**(下半区) | 只录系统音频;只打开这一项**不能**修复微信截图 |
把 WeChat 加进上半区;只勾下半区的"仅系统录音"无效。
### 验证
确认 WeChat 当前是 ad-hoc 签名(这是修复前提):
```bash
codesign -dv --verbose=4 /Applications/WeChat.app 2>&1 | grep -E "Signature|flags|TeamIdentifier"
```
期望看到:
```text
flags=0x2(adhoc)
Signature=adhoc
TeamIdentifier=not set
```
最直接的功能验证:在微信里使用截图、视频通话、麦克风等功能,按 GUI 弹窗的"允许"重新授权一次,之后正常工作。
---
## 六、`"微信" 想访问其他 App 的数据` 弹窗
### 现象
执行过 `wx init`、对 `/Applications/WeChat.app` 做过 ad-hoc 重签名之后,再使用微信时会比较频繁地看到 macOS 弹出:
```
"微信" 想访问其他 App 的数据。
单独存放 App 数据可让你更容易管理隐私和安全。
[ 不允许 ] [ 允许 ]
```
最常见的触发面是**在微信里打开公众号文章**,但这只是高频触发面,不是根因。
### 根因(第一性原理)
这弹窗是 macOS Ventura+ / 14 / 15 对 **app data container 跨身份访问** 的保护:当前进程("微信")正在读取另一个 code identity 的 app 留下的数据。
我们当前 macOS 方案为了让 `task_for_pid` 能拿到 WeChat 的 task port、读取进程内存里的 raw key,要求用户执行:
```bash
codesign --force --deep --sign - /Applications/WeChat.app
```
这一步把 WeChat 从 Apple 官方签名换成 ad-hoc 身份。对用户来说它仍然是"微信";对 macOS 安全模型来说,**重签前的 WeChat** 和 **重签后的 WeChat** 已经不是同一个 app identity。
之后当(重签后的)微信访问它原本的 `~/Library/Containers/com.tencent.xinWeChat/...`、缓存、app group 等数据时,系统看到的是"一个新身份在读旧身份留下的 container 数据",于是按隐私保护策略弹这个对话框。公众号文章里的 webview / cookie / 缓存路径刚好踩到了这条访问路径,所以"打开公众号就弹"会非常容易复现,但**本质不是公众号页面的问题**,而是 code identity + container access。
> 注意:这**不是** "wx-cli 在偷偷读别的 App 的数据"wx-cli 进程本身对 WeChat container 是只读访问;但**要求用户重签 WeChat** 这一步本身就是这类弹窗的直接诱因。所以这是当前 macOS invasive init 路径的已知副作用,不是与 wx-cli 无关的系统行为。
### 应对
短期缓解:
- 点"允许"通常只是放行**当前这次** WeChat 进程;下一次 WeChat 启动权限会 reset,可能还会再弹
- 该授权一般不会在 System Settings 里留下显式开关,因为它绑定的是动态的 code identity
彻底不弹:
-`/Applications/WeChat.app` 恢复成官方签名(重装官方 WeChat 包),不再执行 `codesign --force --deep --sign -`
- 这一步只是放弃**当前依赖 ad-hoc 重签的默认路径**,并不等于放弃 macOS memory-scan:在本机 GUI Terminal 下、对 Terminal.app 授予「开发者工具」TCC 权限后,`task_for_pid` 对 Apple 官方签名(hardened runtime)的 WeChat 应当仍能走通——参考 §一 实测表里的"Apple 签名 + 本机 Terminal sudo = ✅"
- ⚠️ 实测覆盖范围说明:§一 实测表里 "Apple 签名 + 本机 Terminal sudo ✅" 的两条实证只覆盖 macOS 10.15 (Catalina) 与 11.1 (Big Sur)macOS 14 (Sonoma) / 15 (Sequoia) 上是否仍走通**未在本项目内实测**。如果你按这条路恢复官方签名后发现 init 走不通,请回到重签路径并接受本节描述的弹窗副作用
- 真正受限的场景是 SSH 远程 + Apple 签名 WeChat`sshd` 拿不到 TCC 开发者工具授权,这时才必须走重签路径
长期方向:
- 这条副作用的真正修复是把 `wx init` 重新设计成 `safe → assisted → invasive fallback` 三层:默认不动 WeChat,只有在前两条都不可行时才走 ad-hoc 重签,并先打出完整副作用清单让用户显式确认。在那之前,这是已知 trade-off。
+41
View File
@@ -0,0 +1,41 @@
# old-main → new main 对比与捞回审计
基准:
- **新 main 源**`feat/appmsg-url`0.5.0 线)
- **old-main**:原 `botiverse/main` tip `6424a21`0.1.11 线)
- 分叉点:约 `f0dcd4e`
## 结论摘要
| 类别 | 判定 |
|------|------|
| 产品功能(attachment/SNS/biz/appmsg/favorites url | **两边都有**feat 侧经 `d5492ea` 恢复/重做,无缺文件 |
| 0.5 独有(doctor/key/timeline/watch/media/online SQLCipher/shard-meta/FTS | **仅新 main**,不必从 old-main 回捞 |
| 工程硬化(SUDO_USER home、PidFile JSON、stop_daemon、short-page read_exact、new_messages state | **新 main 已具备** |
| Windows 页保护扫描加宽(#54 | **old-main 更强****已捞回** |
## old-main 独有 commit 逐条
| Commit | 主题 | 是否捞回 |
|--------|------|----------|
| e8939f3 / 2b5d872 / c7e2775 | SNS 命令与 media/DOM | 已在新 main,跳过 |
| d750ef6 | sudo home + stop_daemon + ReloadConfig 预留 | 新 main 已有完整实现(含真实 ReloadConfig),跳过 |
| 35a8f0e / b043135 / 1b00d04 / c284b4a | 群昵称 / 引用 / appmsg url / type49 | 已在新 main,跳过 |
| 9d5a78a | macOS TCC 文档 | 文档向;新 README 已改默认「不重签」,跳过 |
| dab3217 / f0f3d3c | biz-articles / favorites url | 已在新 main,跳过 |
| d4587b1 | contacts private 过滤 / search JoinSet / new_messages state | 新 main query 已含等价逻辑,跳过 |
| 70aa3a4 | daemon lifecycle / PidFile / short read / **Windows page protect** | 前三项已有;**page protect 已捞回** |
| 5c001b1 | 0.1.11 bump | 版本已是 0.5.0,跳过 |
| 14fdfde…ff96f95 / 7feacc6 | attachment 全套 + extract 去掉 payload `ok` | 模块在新 mainextract 无重复 `ok`,跳过 |
| b032b8b / e9f65ba / 6424a21 | WAL 增量 cache | 新 main 有 WAL + online + per-key 锁,跳过 |
## 已实现捞回
1. `scanner::is_writable_readable_page`WinNT 常量 + 单测)
2. `scanner/windows.rs` 扫描条件改回宽匹配(WRITECOPY / EXECUTE_*WRITE* + modifier strip
## 明确不捞
- 默认 ad-hoc 重签 / 依赖关 SIP 的文档路径(与 0.5 产品方向冲突)
- 把 0.1.11 版本号或 npm 包元数据倒退
- 机械 cherry-pick 整段 query/cache(会覆盖 online-open 重构)