Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ The optional local voice transcription feature uses the following Python package
| `Hugging Face tokenizers` | https://github.com/huggingface/tokenizers | Apache-2.0 |

Whisper model weights are not included in this repository or its pull request. Users provide or download model weights separately and must follow the selected model's license and usage terms.

## Optional macOS WCDB passphrase capture

The optional Apple Silicon LLDB capture workflow adapts the breakpoint and
register-inspection approach from
[`TANGandXUE/wcdb-key-tool`](https://github.com/TANGandXUE/wcdb-key-tool),
which is distributed under the MIT License. The integration adds target
database validation, transaction recovery, signature verification, and
privacy-preserving diagnostics; it does not bundle user databases or keys.
The complete upstream license text is included at
`docs/licenses/wcdb-key-tool-MIT.txt`.
21 changes: 21 additions & 0 deletions docs/licenses/wcdb-key-tool-MIT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 CloudDreamAI / TANGandXUE

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
70 changes: 70 additions & 0 deletions docs/macos-wcdb-key-capture-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# macOS WCDB 密钥捕获验证记录(脱敏)

记录日期:2026-08-16。本文不包含微信账号、昵称、数据库路径、数据库首页、候选密钥、日志原文、个人主目录或证书材料。

## Apple Silicon 真实端到端

| 项目 | 结果 |
| --- | --- |
| 主机 | Apple Silicon (`arm64`) |
| macOS | 27.0 (build 26A5406e) |
| 微信 | 4.1.12 (build 269341) |
| 安装身份 | 腾讯 Developer ID,Team ID `5A4RE8SF68` |
| 数据源 | 默认沙盒内活动 `app_data/xwechat_files` 数据库 |
| 旧密钥缓存 | 不作为本次捕获成功条件 |
| 捕获 | 重新登录时命中已登记的 arm64 内部返回点;候选长度 32 字节 |
| 验真 | 候选通过所选活动数据库首页 HMAC 校验后才保存 |
| 权限 | 缓存目录 `0700`,密钥文件 `0600` |
| 恢复 | `codesign --verify --deep --strict` 通过;Identifier 与 Tencent Team ID 均匹配 |
| 残留状态 | 成功完成后无 `prepared-in-place-capture.json` |

真实流程按“准备临时微信 → 用户登录并进入聊天页 → 断点预检并分离 → 用户退出账号 → 启动正式监测 → 用户重新登录同一账号 → 数据库验真 → 恢复官方微信”执行。公开记录只保留布尔结果和版本,不保留密钥或可关联账号的标识。

## 异常恢复验证

| 场景 | 验证方式 | 期望/结果 |
| --- | --- | --- |
| 用户取消 | 准备完成后调用幂等 cleanup | 临时进程终止,官方签名恢复,状态文件删除 |
| 临时签名失败 | 对签名命令注入异常 | 未完成安全换位时不改动官方微信;已换位时进入统一恢复 |
| 断点预检失败 | 模拟无 PBKDF2/内部返回断点 | 立即停止,不要求用户退出账号,cleanup 恢复 |
| 捕获超时 | 模拟 LLDB 超时且无合格候选 | 不保存候选,`finally` 恢复官方微信 |
| 隔离副本提前退出 | 模拟 LLDB 报告进程已退出及退出码 | 立即返回 `debug_wechat_exited_during_capture` 和非敏感断点计数,不继续等待普通超时 |
| WCDB `rounds=2` | 模拟目标库 HMAC salt 与 raw encryption key | 只接受精确 HMAC salt 映射且通过目标页 HMAC 的候选;前端 API 还要求通过 message + session 双角色校验后才缓存 |
| 数据库不匹配 | 使用无法通过目标首页 HMAC 的 32 字节候选 | 返回 `passphrase_database_mismatch`,不写缓存,恢复官方微信 |
| 本机密钥展示 | 捕获成功并进入数据库解密 | 仅回环接口在完整数据库校验和安全保存后返回 `db_key`,前端验证 64 位十六进制格式后显示;远端请求被拒绝 |
| 监测启动时序 | 管理员授权期间轮询捕获状态 | 仅在 `monitor_ready=true` 后提示用户登录,避免授权前登录导致漏抓 |
| 进程/应用异常退出 | 保留已 fsync 的事务状态后重新进入 | 下次启动优先调用 stale recovery,状态目标和备份路径校验后恢复 |
| 备份路径篡改 | 将状态中的备份改到所选目录之外 | 拒绝覆盖微信并保留可诊断状态 |

上述故障注入由定向测试覆盖;真实端到端记录覆盖成功路径及最终官方签名恢复。任何自动恢复在无法证明目标、归档或签名身份时都失败关闭,不猜测路径、不覆盖其他应用。

## 可复现测试结果

在官方 `main` 基线的隔离工作树中运行:

```text
50 passed in 1.21s
```

覆盖范围包括官方备份/版本校验、原子换位、调试 entitlement、LLDB 输出解析、`rounds=256000` 数据库 salt 与 `rounds=2` HMAC salt 精确过滤、内部返回点预检、候选数据库验真、隔离副本提前退出诊断、状态文件权限、取消/超时/异常恢复、活动库优先和发布审计。

原始 Draft 曾在同一 Python 环境中对官方基线和 Draft 分别执行完整测试:官方 `f6583c5` 基线为 `828 passed, 110 failed, 9 errors, 2 skipped`,原始 Draft 为 `876 passed, 110 failed, 9 errors, 2 skipped`,其新增 48 项全部通过且原有失败/错误数量不变。本次根据微信 4.1.13 社区反馈新增 2 项回归后,已重新执行上述 5 个定向模块并得到 50 项通过;完整仓库测试尚未重跑,不用历史结果冒充本次结果。历史失败集中于本机未提供仓库受限 native core、导出完整性运行时和语音模型环境。

## 发布包审计结果

审计对象为本地 Apple Silicon 独立验证包(仅用于验证,不随 PR 提交二进制):

```text
WeDataKeyExtractor-1.1.8-mac-arm64.zip
SHA-256: 5d6f4e1a4dffac147fe2dff717674fa5061c7e0179e30c083621cb831d3873cb
发布审计通过:未发现个人路径、数据库、日志或密钥缓存文件。
```

同一审计同时扫描了解压目录和 ZIP 内每个条目的内容。未发现数据库、日志、偏好、密钥缓存、个人绝对路径或当前构建用户名;源码提交中也不包含本地签名证书或私钥。PR 本身不上传该验证包,避免把未经上游签名、公证的二进制作为正式产物传播。

## 限制和后续评审点

- 当前真实记录不能推断 Intel Mac 或未来微信版本已兼容;微信 4.1.13 build 269578 的 `rounds=2` 行为来自社区实测,当前仅完成可复现逻辑回归,尚未完成该 build 的 clone 成功路径复测;
- 当前 Draft 故意不替换现有 helper,也不改变默认 UI/API;
- 维护者确认状态机后,应在后续小 PR 中决定调用模式、前端确认步骤和正式 Release 的 Developer ID/公证流程;
- 发布前必须在上游正式构建产物上再次执行同一审计,不能用本地验证包结果替代正式 Release 审计。
111 changes: 111 additions & 0 deletions docs/macos-wcdb-key-capture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# macOS Apple Silicon WCDB 密钥捕获(Draft)

> 仅用于处理当前用户本人拥有或已获明确授权的本机微信数据。本流程需要用户亲自在手机端确认登录;不会绕过登录、联网传输密钥或长期注入微信进程。

## 本 Draft 的边界

现有 macOS 受限原生 helper 仍是默认且优先的获取方式。只有 helper 返回经过允许的运行时、版本或进程附加失败,并且用户在前端再次阅读风险说明后,才会展示实验性本机调试兜底;组件完整性、签名或信任校验失败不会触发兜底。该流程不会静默运行,也不会在结束后保留调试签名。

核心文件:

- `macos_db_key_capture.py`:腾讯签名识别、备份、临时签名、LLDB 调用、密钥格式及数据库验真;
- `macos_clone_capture.py`:APFS 写时复制隔离方案、断点预检、salt 匹配和兼容 UUID 表;
- `macos_inplace_capture.py`:可恢复的原路径临时重签事务和重启恢复;
- `macos_db_key_discovery.py`:只接受能通过所选数据库首页 HMAC 校验的本地候选。
- `routers/keys.py`:提供 status、prepare、preflight、capture、cancel 五个分阶段 API,恢复目录由后端固定,前端不能传入任意恢复路径;
- `frontend/pages/decrypt.vue`:helper 失败后的显式风险确认、分阶段操作提示、停止/切页恢复和遗留状态恢复入口。

上述捕获 API 强制只接受运行 WCDA 的 Mac 本机回环连接;即使聊天查看或总结开启了局域网/Tailscale 访问,远端设备也不能触发临时重签、调试或恢复操作。捕获成功后,数据库密钥会写入权限受限的本机缓存,并返回给本机前端显示,方便用户复制和核对;密钥不会写入日志、诊断状态或版本库。

## 安全状态机

1. 规范化并锁定明确的 `/Applications/WeChat.app`,验证 bundle identifier、腾讯 Team ID、深度签名和版本。
2. 读取所选账号的加密数据库首页;拒绝明文 SQLite、短文件和不可读文件。
3. 在任何改动前创建并验证官方微信恢复归档,再将本机恢复状态以 `0600` 原子写入磁盘。
4. 只对一次捕获所需的临时实例进行调试签名。先登录进入聊天页,再短暂附加做断点预检并立即分离。
5. 用户在未监测状态退出账号;正式监测开始后重新登录同一账号。
6. 通用路径只接受两种经过验证的 WCDB 参数形状:`rounds=256000` 时 salt 必须等于目标数据库 salt;`rounds=2` 时 salt 必须等于目标数据库 salt 逐字节异或 `0x3A` 后的 HMAC salt。其他轮数一律拒绝。两类候选都先通过目标页 HMAC;前端 API 还要求同一候选通过 message + session 双角色校验后才缓存。已知版本还可使用按模块 UUID 明确登记的内部返回点,未知 UUID 不猜偏移。
7. 32 字节候选先通过目标数据库首页 HMAC 校验;API 还会校验同一账号的消息库与会话库,全部通过后才写入缓存。缓存目录和文件分别使用 `0700`、`0600`。
8. 原生监测器设置捕获点后先写入只含状态和 PID 的就绪文件;前端确认该状态后才提示用户重新登录。成功候选通过页面 HMAC 后,临时调试进程在受控异常状态中终止,避免残留 SIGTRAP 被重新投递成微信崩溃。
9. 成功、取消、超时或异常都进入同一恢复路径;恢复后再次执行腾讯签名和版本校验,最后才删除恢复状态。若桌面应用被强制结束,重新进入解密页时会先检测遗留状态,并由用户明确确认恢复。

## 源码复现

前提:Apple Silicon Mac、Xcode Command Line Tools、腾讯官方签名微信,以及运行 Python/桌面应用的完全磁盘访问权限。下面的路径均为示例,不能直接复制真实密钥、账号名或聊天数据库到 Issue/PR。

先运行不接触真实微信的回归测试:

```bash
python -m pytest -q \
tests/test_macos_db_key_capture.py \
tests/test_macos_clone_capture.py \
tests/test_macos_inplace_capture.py \
tests/test_macos_db_key_discovery.py \
tests/test_macos_key_capture_release_audit.py \
tests/test_macos_platform_support.py \
tests/test_decrypt_image_keys_frontend.py
```

真实端到端验证可采用下列显式三阶段调用;桌面前端使用等价的五个分阶段 API,并在每一步向用户确认:

```python
from pathlib import Path

from wechat_decrypt_tool.macos_db_key_capture import (
capture_prepared_macos_passphrase,
cleanup_macos_passphrase_capture,
prepare_macos_passphrase_capture,
preflight_prepared_macos_passphrase,
)

wechat = Path("/Applications/WeChat.app")
backup_root = Path("/path/to/private/recovery-directory")
probe_db = Path("/path/to/active/app_data/xwechat_files/account/db_storage/message/message_0.db")

try:
prepare_macos_passphrase_capture(wechat, backup_root=backup_root)
# 用户登录临时微信并进入聊天页后:
preflight_prepared_macos_passphrase(wechat, backup_root=backup_root)
# 用户先退出账号;监测开始后再重新登录同一账号:
result = capture_prepared_macos_passphrase(
wechat,
backup_root=backup_root,
probe_db_path=probe_db,
)
assert result["official_wechat_verified"] is True
finally:
# 可以重复调用;显式取消和异常退出也使用此恢复入口。
cleanup_macos_passphrase_capture(wechat, backup_root=backup_root)
```

结束后必须验证官方微信:

```bash
codesign --verify --deep --strict --verbose=2 /Applications/WeChat.app
codesign -dv --verbose=4 /Applications/WeChat.app 2>&1 \
| grep -E '^(Identifier|TeamIdentifier|Authority)='
```

期望结果包含 `Identifier=com.tencent.xinWeChat`、`TeamIdentifier=5A4RE8SF68`,且深度签名验证成功。

## 发布审计

对解压后的 `.app` 和最终 `.zip` 都运行:

```bash
python tools/audit_macos_key_capture_release.py \
/path/to/WeChatDataAnalysis.app \
/path/to/WeChatDataAnalysis-mac-arm64.zip
```

审计会拒绝数据库、日志、偏好、密钥缓存、`.env`、构建者主目录绝对路径及当前构建用户名。证书与私钥不属于源码或运行时资源,也不得加入安装包。

## 当前兼容性

- 已验证:Apple Silicon、微信 4.1.12(build 269341)、活动 `Documents/app_data/xwechat_files` 数据库;
- 社区实测反馈:微信 4.1.11(build 269136,MAS)使用无受限 entitlement 的 APFS 调试副本、`CCKeyDerivationPBKDF` 和 `rounds=256000` 候选可完成 25/25 数据库解密;该记录为社区手工复现,不等同于本 PR 自动流程已完成同环境验收;
- 4.1.13 build 269578 的社区实测显示 `rounds=2` password 可作为 raw encryption key;本实现只在 HMAC salt、目标页 HMAC 和完整账号双角色校验全部通过时接受;Apple Silicon、macOS 27.0 的本机调试路径已完成一次真实登录捕获、数据库校验和官方签名恢复验证;
- 同一社区环境报告隔离副本在手机确认登录后静默退出。当前修复增加退出状态诊断,但尚未在该机器上证明副本登录问题已经消失;
- 未验证:Intel Mac、未来微信版本、多套并行安装;真实通过记录不能推导为百分之百成功保证;
- 已知内部返回点仅按 Mach-O UUID 精确启用;版本未知时只保留系统 PBKDF2 路径并返回明确诊断;
- 断点预检不通过时流程必须停止,不能要求用户继续退出账号。
38 changes: 38 additions & 0 deletions frontend/composables/useApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,39 @@ export const useApi = () => {
return await request(url, params?.signal ? { signal: params.signal } : {})
}

const getMacosKeyCaptureStatus = async (params = {}) => {
return await request('/macos-key-capture/status', params?.signal ? { signal: params.signal } : {})
}

const macosKeyCaptureRequest = async (action, params = {}) => {
const options = {
method: 'POST',
body: {
wechat_install_path: params.wechat_install_path || null,
db_storage_path: params.db_storage_path || null,
timeout: params.timeout || 240
}
}
if (params.signal) options.signal = params.signal
return await request(`/macos-key-capture/${action}`, options)
}

const prepareMacosKeyCapture = async (params = {}) => {
return await macosKeyCaptureRequest('prepare', params)
}

const preflightMacosKeyCapture = async (params = {}) => {
return await macosKeyCaptureRequest('preflight', params)
}

const captureMacosKey = async (params = {}) => {
return await macosKeyCaptureRequest('capture', params)
}

const cancelMacosKeyCapture = async (params = {}) => {
return await macosKeyCaptureRequest('cancel', params)
}

// 获取图片密钥
const getImageKey = async (params = {}) => {
const query = new URLSearchParams()
Expand Down Expand Up @@ -1118,6 +1151,11 @@ export const useApi = () => {
getWrappedAnnualMeta,
getWrappedAnnualCard,
getKeys,
getMacosKeyCaptureStatus,
prepareMacosKeyCapture,
preflightMacosKeyCapture,
captureMacosKey,
cancelMacosKeyCapture,
getImageKey,
getImageKeyMemory,
getWxStatus,
Expand Down
Loading