GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-11-weixin-4x-db-decrypt-progress.md
2026-05-20 21:39:12 +08:00

29 KiB
Raw Permalink Blame History

title created status author related environment
Weixin 4.x SQLCipher 数据库破解进度日志 2026-05-11 SOLVED Claude Code
docs/superpowers/specs/2026-05-11-weixin-4x-automation-survey.md
docs/superpowers/specs/2026-05-09-wechat-uia-channel-design.md
Weixin 4.1.8.107
Windows 11 26200
Node v24.11.1
PowerShell 5.1

Weixin 4.x SQLCipher 数据库破解进度日志

2026-05-12 突破:破解成功!message_0.db 完整解密,可读真实群消息内容(含"123 测试")

🎯 最终公式(直接复用)

Weixin 4.x 用 WCDB + 标准 SQLCipher 4,raw key 模式,reserve_sz = 80(关键!不是 SQLCipher 3 的 48):

encKey = 32 字节 raw key (从 Weixin 进程内存提取,见 §S9)
salt = DB page 1 前 16 字节
hmacKey = PBKDF2-HMAC-SHA512(encKey, salt XOR 0x3a, 2 rounds, 32 字节输出)

每 page (4096 字节):
  encStart = 16 if page == 1 else 0
  ciphertext = page[encStart : 4096 - 80] = 4000B (page 1) or 4016B (others)
  iv = page[4096 - 80 : 4096 - 64] = 16B
  storedHmac = page[4096 - 64 : 4096] = 64B HMAC-SHA512

HMAC 验证:
  HMAC-SHA512(hmacKey, ciphertext || iv || pageNumber_LE_u32) == storedHmac

解密:
  AES-256-CBC(encKey, iv).decrypt(ciphertext) → plaintext

输出明文 DB 时 page 1 前 16 字节替换为 "SQLite format 3\0"

验证: poc-7f-reserve80.mjs page 1 HMAC + AES 一次通过;poc-10-decrypt-and-read.mjs 解 10644 个 page 全部成功 (0 HMAC fail) 用时 0.2s

TL;DR · 当前进度

阶段 状态 产出
S1 实测 Weixin 4.x 环境 完成 确认 UIA 路径不可行;定位到 ~\Documents\xwechat_files\<seed>\db_storage\
S2 DB 文件监控 完成 message_0.db-wal 实时被 Weixin 写入(用户发消息后 mtime 立刻变)
S3 定位 key 存储 完成 all_users\login\shin_seed\key_info.db 明文 SQLite,86 条 180B 记录
S4 分析 key_info 结构 🟡 部分 protobuf 封装,固定头 17B + 变化 payload;加密算法未破解
S5 内存 dump key 候选 完成 500K 候选,去重后 47K;PoC 2 不需要管理员权限
S6 暴力 SQLCipher 解密 3 phase 全未命中 Phase 1/2/3 跑完(raw / PBKDF2 4000 / 64000),说明 Weixin 4.x 非标准 SQLCipher
S7 静态分析 Weixin.dll 完成 确认用 WCDB(Tencent fork of SQLCipher),compatibility=4page_size=4096
S8 内存全量 dump + 字符串扫描 完成 在 memdump 找到 PRAGMA cipher_salt = ...17 条 96-char hex literals,每条对应一个 DB(末尾 16B = DB salt 完全匹配)
S9 用 hex literal 当 raw key 解密 失败 17 条 hex × salt 一一对应非常强证据,但作为 raw key 时:AES 输出非 SQLite 格式 + HMAC 396 种派生组合全部不通过
S10 看 WCDB 源码 找到真正问题 reserve_sz 不是 48 而是 80(HMAC-SHA512 全长 64B + IV 16B);之前误用 SQLCipher 3 的 48B 布局
S11 用正确公式解密 成功 poc-7f reserve=80 → HMAC + AES 同时通过;poc-10 全 DB 10644 pages 全部解密
S12 读群消息 完成 用 node:sqlite 打开明文 DB,读到 Msg_<sha256(room_id)> 表,找到 "123 测试" 那条,真实 wxid/时间/内容全可见

当前阻塞: 已解决!key + 公式都对了,reserve_sz = 80 是关键。

下一步:

  1. 已可全量解密 + 读取
  2. 把 PoC 整理成 production 代码:
    • 内存抽 key + 自动找各 DB 的 raw key
    • WAL 增量解密(实时监听新消息)
    • 配合 Win32 SendInput 实现回复
  3. 集成进 Neta.WeChatBridge 替换 UIA 实现

S1 · 环境实测与事实清单

S1.1 进程与窗口

Get-Process Weixin | ? MainWindowHandle -ne 0
# => PID 10888, Path: C:\Program Files\Tencent\Weixin\Weixin.exe, Version 4.1.8.107

S1.2 UIA 树(证明原 spec 路径失效)

Add-Type -AssemblyName UIAutomationClient
$root = [System.Windows.Automation.AutomationElement]::FromHandle($hwnd)
# 完整深度遍历结果:
# [Window] "微信" cls=Qt51514QWindowIcon
#   [Pane] "Weixin" cls=Qt51514QWindowIcon
#   [Pane] "MMUIRenderSubWindowHW" cls=MMUIRenderSubWindowHW
# 总共仅 3 节点 — 无列表、无消息气泡、无输入框

结论: Weixin 4.x 使用 Qt 5.15.14 自绘 UI,默认不发 UIAutomation 事件,UIA 树只有 3 个空壳。不是改注册表能解决 —— 注册表(EnableUIADesktopToggle 等)只控制跨权限访问,不能让 Qt 自绘内容变成可读 UIA 元素。

S1.3 监听端口

Weixin 主进程(PID 10888)监听: 127.0.0.1:14013, 14016, 14019, 14022, 14023

curl 返回 HTTP 000 —— 是私有 IPC 而非 HTTP/CDP。CDP 注入路径不成立

S1.4 文件存储布局

~\Documents\xwechat_files\
├── all_users\
│   └── login\shin_seed\
│       └── key_info.db           ⭐ 明文 SQLite!
├── Backup\
└── <wxid_seed>_<suffix>\         (本机是 shin_seed_7861)
    └── db_storage\
        ├── message\
        │   ├── message_0.db       ⭐ SQLCipher 加密,是本 PoC 目标
        │   ├── message_0.db-wal   (SQLite WAL journal)
        │   ├── message_0.db-shm
        │   ├── message_0.db-*.material  (Tencent 自定义增量索引)
        │   ├── media_0.db
        │   └── biz_message_0.db
        ├── contact\contact.db     (加密)
        ├── session\session.db     (加密)
        └── ...

老 spec 假设的 Tencent\WeChat\WeChat Files\ 在 4.x 不存在


S2 · DB 文件实时性验证

脚本: packages/backend/poc/weixin-4x/poc-1b-poll-all-db.mjs

验证方法: 对 db_storage/ 下所有 .db/-wal/-shm/.material 文件,每 500ms fs.stat 一次, 对比 size/mtime。

实测结果: 用户在"小云雀用户交流群90"发"123 测试"后,观测到:

  • message_0.db-wal 的 mtime 实时变化(但 size +0)
  • message_fts.db-wal 同步变化
  • head_image.db+4096 bytes 的真实增长

关键发现 — SQLite WAL 模式是预分配 + 就地覆盖,写入不改变文件 size:

prev.size === curr.size, but mtime 变

结论: DB 被实时写入,信号可捕捉。正确做法:监听 mtime 或 -wal 文件内容变化(hash 前几 KB),不要依赖 size delta。


S3 · 定位 key_info.db(明文!)

意外发现

xxd key_info.db | head -1
# 00000000: 5351 4c69 7465 2066 6f72 6d61 7420 3300  SQLite format 3.

Weixin 4.x 把某种 key 信息存在明文 SQLite 里,不是 DPAPI 加密文件。

Schema (单表 LoginKeyInfoTable)

CREATE TABLE LoginKeyInfoTable (
  user_name_md5  TEXT,   -- 登录 wxid 的 md5,本机所有记录一致
  key_md5        TEXT,   -- 全为空字符串("")
  key_info_md5   TEXT,   -- 每条一个 md5 指纹(版本号?)
  key_info_data  BLOB    -- 180 字节 payload,见下
);

key_info_data 字节结构分析

86 条记录,每条 180 字节。逐字节对比同/异:

offset  0-16:   0a a8 01 00 0b b5 17 39 05 00 00 01 00 00 00 00 00    ← 所有记录相同 (17 字节固定头)
offset 17-30:   XXXXXXXXXXXXXX                                         ← 每条不同 (14 字节)
offset    31:   20                                                     ← 固定
offset 32-34:   00 00 00                                               ← 固定
offset 35-66:   6e b0 01 aa 1e 57 b6 93 70 d0 71 8d 0f 53 e0 fd        ← 所有记录相同!(32 字节共享 blob)
                67 21 8d f5 ca 93 32 e8 64 59 66 5d ac f4 4f e9
offset 67-154:  XXXXXXXXXXXX...                                        ← 每条不同 (88 字节变化区)
offset 155-158: . X . .                                                ← 部分变化
offset 159+:    混合 .X.X                                              ← 混合

Protobuf 解析(部分)

固定头首字节 0a a8 01 是标准 protobuf:

  • 0a = field 1, wire type 2 (length-delimited bytes)
  • a8 01 = varint length = 168 (0xa8 = 168)

所以 field 1 是 168 字节的 bytes(offset 3 到 170)。 后面 180 - 3 - 168 = 9 字节是其他 field(可能是时间戳、版本号)。

field 1 内部结构(168 字节):

offset 0-13 (of field):   前 14 字节每条不同 → 可能是 IV 或 nonce
offset 14-16:             20 00 00 00 固定
offset 17-48:             共享 32 字节 blob ⭐
offset 49+:               88 字节变化区 ⭐

假设(未验证)

假设 A: 共享 32 字节 blob 是 wrap key(KEK),每条记录的"加密 key"用这个 KEK 解开。 但 KEK 本身存在明文文件里太危险,Tencent 应该不会这么做。

假设 B: 共享 32 字节是 协议版本/算法标识符,不是 key。每条的 88 字节变化区才是加密后的 AES key。

假设 C: key_info.db 只存"加密后的 key",真正的 wrap key 在 Weixin 二进制里硬编码,或通过某种 KDF 从系统信息(MachineGuid、用户 SID 等)派生。Tencent 常见做法。

下次可以做的实验

  • 实验 E1: 把 user_name_md5 反推 — 对 "shin_seed" "shin_seed_7861" 各种组合做 md5,看是否匹配 3d25d0b45f678b1c29b99e44280e5aea。如果匹配,说明 user_name_md5 是 wxid 本身的 md5,说明这个 文件是按 wxid 索引的(多账号场景下每个 wxid 一条或多条记录)
  • 实验 E2: 用 Weixin.exe 在 ProcMon 下跑,看它读 key_info.db 后紧接着读/写了哪些其他文件。 通常 KEK 派生会读 SYSTEM\CurrentControlSet\Control\... 之类注册表
  • 实验 E3: 用 API Monitor 看 Weixin 调 CryptUnprotectData (DPAPI) / BCryptDecrypt / EVP_Decrypt 等 API 时的参数。如果用 DPAPI,CryptUnprotectData 的 input 会泄露加密 blob,output 就是明文 key

S4 · 内存 dump key 候选

脚本: poc-2b-dump-candidates.ps1

原理: 用 Win32 OpenProcess(VMRead|QueryInfo) + VirtualQueryEx + ReadProcessMemory 枚举 Weixin 进程的所有私有可读写已提交内存区域,每 4 字节步长扫描 32 字节窗口,用启发式 过滤器(排除全零/全 FF/全 ASCII/低熵)。

重要发现 🎯

OpenProcess 不需要管理员权限 — 只要是同用户运行的脚本即可。 这极大降低了未来产品化的用户门槛(不用 UAC 弹窗)。

执行结果

[+] Target PID: 10888
[+] 内存区域 635 个, 合计 321.95 MB
[+] 扫描耗时: 30.8s
[+] 候选数 (上限): 500000 (16 MB bin 文件)

去重后分析

[+] 唯一候选: 47743
[+] 频度分布: >=100次:26 个, 10-99:97 个, 2-9:2016 个, 1次:45604 个

Top 10 高频候选都是噪声:

#1: freq=396020 aaaa...aaaa  (Windows debug heap marker)
#2: freq=22983  x64 pointer pattern (e05b5f67f97f0000 重复)
#3: freq=22842  pointer tail (f97f0000 偏移的变形)
#4-10: 小整数 / 低熵

结论: 高频候选几乎全是噪声;真 key 应该在 2-9 频次区间(被 SQLite 连接池/每 DB 一份 引用,但不过多)。

坑与教训

  1. PowerShell FileStream 不 Flush 导致空文件: [System.IO.File]::Create().Close() 在某些 PS 版本不可靠,必须 Dispose()。修正版见 poc-2b-dump-candidates.ps1
  2. Measure-Object 对 hashtable 的自定义属性不识别: 改用手动 foreach + +=
  3. 启发式过滤不够严: 前 3 个候选看是堆指针,说明 "前 4 字节全零/全 ASCII" 过滤不足。 下次可加 "所有 32 字节的低字节熵 > 4.0" 条件

S6 · SQLCipher 暴力解密尝试

message_0.db 文件头

91 f3 c3 14 7a 62 cd 0f 9d 1b 96 e8 f5 00 04 f1
85 e5 81 fd 32 76 f2 d0 f1 08 20 0f 4c 35 55 8a

标准 SQLite 应是 53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00(ASCII "SQLite format 3\0")。 前 16 字节按 SQLCipher 规范应是 salt(PBKDF2 用)。

脚本: poc-3d-crack-key.mjs(松验证)和 poc-3e-crack-strict.mjs(严格验证)

测试候选来源:

  1. key_info.db 的 86 条记录 × 每条 180B 滑窗 = 约 12K 候选
  2. 内存 dump 去重后 Top 5000 高频候选
  3. 合并去重: ~17K 候选

SQLCipher 参数矩阵:

  • reserved size: 48 (v4 默认,16 IV+32 HMAC-SHA512) / 36 (v3, 16 IV+20 HMAC-SHA1) / 16 (仅 IV)
  • PBKDF2 iterations: 0 (raw key) / 1 / 4000 (SQLCipher 老默认) / 64000 (SQLCipher 3) / 256000 (v4 默认)

验证逻辑(严格版):

解密后的 plaintext 对应 DB 从 offset 16 开始,必须匹配 SQLite 文件头规范:

  • pt[0..1] = page size,必须 ∈ {512, 1024, 2048, 4096, 8192, 16384, 32768}
  • pt[2] (write version) ∈ {1, 2}
  • pt[3] (read version) ∈ {1, 2}
  • pt[5..7] = 0x40 0x20 0x20(SQLite 固定值,对应 DB offset 21-23)

试过的组合与结果

Phase 参数 候选数 耗时 结果
松验证暴力(PoC 3d) reserved=48 iter=1 17K × 3 × 5 296s 假阳性: pageSize=1 (65536) 不合理
严格验证 Phase 1 reserved=[48/36/16] iter=0 (raw) 22289 × 3 = 66867 0.5s 未命中
严格验证 Phase 2 reserved=[48/36/16] iter=4000 66867 51.3s 未命中
严格验证 Phase 3 reserved=[48/36/16] iter=64000 66867 808.8s (13.5min) 未命中
严格验证 Phase 4 iter=256000 66867 ~4h 预计 未跑(3 个 phase 都不中,256000 继续也不会中,说明 SQLCipher 参数不标准或 key 不在池里)

最终 verdict: 标准 SQLCipher 所有常见 PBKDF2 参数 + 3 种 reserved 布局 × 22K 候选全部未命中 → Weixin 4.x 加密方案非标准 SQLCipher,或 key 不是裸 32 字节 raw bytes。


S7 · 静态分析 Weixin.dll(2026-05-12 新进展)

确认加密库

grep -ao "sqlcipher\|wcdb\|WCDB" "C:/Program Files/Tencent/Weixin/4.1.8.107/Weixin.dll"
# 命中: sqlcipher, wcdb, WCDB

Weixin 4.x 使用 WCDB —— Tencent 自己开源的 SQLCipher 衍生加密 SQLite 库。

字符串证据

com.Tencent.WCDB.Config.Cipher
sqlcipher_export
HMAC_SHA1 / HMAC_SHA256 / HMAC_SHA512
PBKDF2_HMAC_SHA1 / PBKDF2_HMAC_SHA256 / PBKDF2_HMAC_SHA512

PRAGMA cipher_compatibility = %d;
PRAGMA cipher_default_kdf_algorithm = %s;
PRAGMA cipher_default_kdf_iter = %d;
PRAGMA cipher_default_page_size = %d;
PRAGMA cipher_default_plaintext_header_size = %d;
PRAGMA cipher_default_hmac_algorithm = %s;
PRAGMA cipher_hmac_algorithm = %s;
PRAGMA cipher_kdf_algorithm = %s;
PRAGMA cipher_page_size = %d;
PRAGMA cipher_use_hmac = %d;
PRAGMA kdf_iter = %d;

WCDB 完整暴露了标准 SQLCipher 4 的 PRAGMA 接口。


S8 · 内存全量 dump + PRAGMA 字符串扫描

思路

PowerShell 正则在 322MB 内存上太慢。改两步:

  1. PowerShell 只做 OpenProcess + VirtualQueryEx + ReadProcessMemory 把所有 private RW 区域整块写到磁盘
  2. Node Buffer.toString('latin1') + 高效正则扫描

结果

  • 总 dump 大小: 239.6 MB(612 个内存区域)
  • 64-char hex 字符串(可能是 raw key 候选): 10068 个唯一值
  • x'<96-char hex>' 形式(SQLCipher raw key 字面量): 20 个唯一值
  • PRAGMA cipher_salt = ... 出现 3 次

决定性发现

在 memdump 中搜索 PRAGMA cipher_compatibility:

compatibility = 4
page_size = 4096

完全是 SQLCipher v4 默认参数:

  • KDF: PBKDF2-HMAC-SHA512, 256000 iterations
  • HMAC: HMAC-SHA512
  • reserved per page: 48 字节(16 IV + 32 HMAC truncated)
  • page size: 4096

S9 · 找到候选 raw keys 一一对应(关键证据)

PoC 8 思路

db_storage/ 下所有 17 个 .db 文件,提取前 16 字节作为 salt。 在 memdump 的 20 个 x'<96-char>' 字面量里,找末尾 16 字节恰好等于某 DB salt 的。

结果(完美对应,17:17)

DB 文件 salt 候选 raw key(32B)
message/message_0.db 91f3c3147a62cd0f9d1b96e8f50004f1 374c4e1a2da5a7bee6e0de020a1ad24e9234c9db709e93e4c01dd1eda9e40b50
message/biz_message_0.db 30e6a2caee77a38578684980b8e7e1b9 5b1d42eefa14e7f050e41af8836a5fb9bb1f3698a67f10e958f450853066c5a3
message/media_0.db 07788b05e312eee3ba7133af2e19af1c a194a326c5305bbe0808a47892f27b283079848748fe63a69f9d3f098575f231
message/message_fts.db 03b5195c66cf90ad5eb90abb27422a1c f8f9b52cd299a4f723408f28849db8c113aacd12686c44afcf7898c0ed3a21e4
message/message_resource.db 5bd8155588a884b7e0153b244c744194 c0b004db73e9262fd407a428671d8bf48a7a7386b59e41516f3ca29ef392db70
contact/contact.db 6b9563aa1b2d5853a57c906040b3736c bd93ce66be76d2efc88ec5347b954f1b9bc14a148eb5eae9667c19866ca51338
contact/contact_fts.db 57c24e569cba0fe37c81c8ffc6c0bc13 7da06127d8a0a560c8ae7d1bb479fdbd944ca3720cb651b87fd8c209fec2edde
session/session.db 60dcef0524c20d75d1a54b425ec0cf1a e43bd85c5b509122d431c1512183f9e2da75fb8997677271bc1a6862c99dfbb3
general/general.db 690a391e6da19442d6feef3d3f381807 bfb2547c735713c88421537985b15e6867e2d87d9fb881e0beb3d145d014c3fc
favorite/favorite.db 4b1699953959192abaa6f8a4aa92b87a 2dcff10b76f756b248d064b35cf26d67d43f2b93f3da5f138dde1ee68049108d
favorite/favorite_fts.db 07a68944fba45ed5e3ff536d3619392e d78287bb99b9404f53866bcc2b1344d8ff8edd520ecb6ffc36cb933dfcdce1c0
emoticon/emoticon.db ca01076c55626bc4ebac1543e8f44d2a 85aa5fc40b53f8c2c42fd11818120f9c02645ac2d0cd2c56d9aafae4e869a459
head_image/head_image.db 76446b412632e8cf571b612450b98373 a5e1343be87076eb60e4780f77cf64d3af9131ac65ab95b4744d0854771224bd
hardlink/hardlink.db b7a6afb6d478a483da8e77b1c76c66da 0b74bffe72d8e6fa646b9cbd7bcce4ad4731a6c928ebf637eefc7844e990948d
bizchat/bizchat.db 6662742c773b9316d9b27e1388265cac 4a6b1dcc417bc6ba4d56c7ffe9bd5998975836b13b7d1a5bf7f260e4b5dd6908
(sns, solitaire — 类似,记录省略)

17 DB × 17 候选,每个 DB 恰好 1 个候选,salt 完全匹配。这绝对不是巧合, 这就是 SQLCipher PRAGMA key = "x'<32B raw key><16B salt>'" 的 raw key 模式字面量, Weixin 4.x 在运行时构造此字符串调用 sqlite3_key_v2()。

关键备注:这个证据是当前会话最大突破

下次接手时直接信这些 key 就是真 key,不要再回去暴力搜了——focus 在派生公式上。

memdump 文件保留

  • weixin-memdump.bin (240 MB) — 整个 Weixin 进程内存
  • weixin-memdump.idx — 每个区域的 base address + offset

这俩可以离线反复扫描,无需重启微信。但注意 Weixin 重启后 PID 变 / 内存布局变,这个 dump 仅对本次登录有效


S10 · 用候选 raw key 解密失败(派生公式未破)

实验:把 374c4e1a... 当作 message_0.db 的 PRAGMA key raw 字节

按 SQLCipher 4 标准:

  • encKey = raw key 32B(不派生)
  • hmacKey = PBKDF2-HMAC-SHA512(raw_key, salt XOR 0x3a, 2 rounds, 32B output)
  • HMAC 计算: HMAC-SHA512(hmacKey, ciphertext || iv || pageNumber_LE_u32)[0..32]
  • AES: AES-256-CBC(encKey, iv).decrypt(ciphertext)

测试矩阵(失败汇总)

实验 KDF 算法 HMAC 算法 reserved HMAC 输入 结果
PoC 7 标准 SQLCipher 4 PBKDF2-SHA512 × 2 SHA512 48 content+iv+pgLE HMAC AES
PoC 7b 9 组合 SHA1/256/512 × {std/256k/raw} SHA1/512 36/48 content+iv+pgLE
PoC 7c 7 派生 × 7 输入顺序 × 3 算法 全部 全部 48 全部排列
PoC 7d 不验 HMAC,试 reserved=48/32/24/20/16 直接 AES - - 5 种 - AES 解出非 SQLite 格式
PoC 7e 396 组合(3 kdf × 7 iter × 3 hmac × 6 input) 全部 全部 48 全部

结论(systematic-debugging Phase 4.5 触发)

3+ 次大规模尝试失败 → 不再继续猜测,而是质疑前提:

  • raw key 候选肯定正确(17:17 一一对应是不可能巧合的)
  • HMAC/KDF 派生公式是非标准的——WCDB 改了 SQLCipher 默认行为

可能的非标准:

  1. WCDB 把 cipher_use_hmac = 0 禁用 HMAC,reserved 48B 是别的用途
  2. WCDB 改了 HMAC 输入(可能 include salt 或 page header 等)
  3. raw key 在被用作 encKey 之前还要做一次变换(SHA256?XOR?)
  4. WCDB 自定义的 cipher provider(PRAGMA cipher_provider 暴露但具体值未知)

下次接手必读

下次接手时,不要重新做暴力——key 已经找到。Focus 在以下:

  1. 找现成的 WCDB 4.x decrypt 工具 —— GitHub 搜 wxdump 4 / WeChatMsg 4 / wcdb-decrypt
  2. 直接读 WCDB 源码 —— GitHub Tencent/wcdbsrc/cpp/core/codec/ 目录
  3. API Monitor hook Weixin 调用 sqlite3_key_v2() —— 看它传的真实 password buffer

可能还要做的:

  • 把 17 个 raw key 用 17 个 DB 文件做 Python pysqlcipher3 库直接试解(如果能装上,会自动用 SQLCipher 库内的所有版本兼容模式)
  • PRAGMA cipher_compatibility = 1/2/3/4 各版本(已知 default=4,但可能存在 fallback)

当前所有候选 raw key(可直接复用)

下次接手保留这些 key 即可,不用重做内存 dump。 注意: 这些 key 仅在本机当前已登录用户有效。如果用户重新登录或换号,key 会变。

DB raw key (32 bytes hex)
message_0 374c4e1a2da5a7bee6e0de020a1ad24e9234c9db709e93e4c01dd1eda9e40b50
biz_message_0 5b1d42eefa14e7f050e41af8836a5fb9bb1f3698a67f10e958f450853066c5a3
media_0 a194a326c5305bbe0808a47892f27b283079848748fe63a69f9d3f098575f231
message_fts f8f9b52cd299a4f723408f28849db8c113aacd12686c44afcf7898c0ed3a21e4
message_resource c0b004db73e9262fd407a428671d8bf48a7a7386b59e41516f3ca29ef392db70
contact bd93ce66be76d2efc88ec5347b954f1b9bc14a148eb5eae9667c19866ca51338
contact_fts 7da06127d8a0a560c8ae7d1bb479fdbd944ca3720cb651b87fd8c209fec2edde
session e43bd85c5b509122d431c1512183f9e2da75fb8997677271bc1a6862c99dfbb3
general bfb2547c735713c88421537985b15e6867e2d87d9fb881e0beb3d145d014c3fc
favorite 2dcff10b76f756b248d064b35cf26d67d43f2b93f3da5f138dde1ee68049108d
favorite_fts d78287bb99b9404f53866bcc2b1344d8ff8edd520ecb6ffc36cb933dfcdce1c0
emoticon 85aa5fc40b53f8c2c42fd11818120f9c02645ac2d0cd2c56d9aafae4e869a459
head_image a5e1343be87076eb60e4780f77cf64d3af9131ac65ab95b4744d0854771224bd
hardlink 0b74bffe72d8e6fa646b9cbd7bcce4ad4731a6c928ebf637eefc7844e990948d
bizchat 4a6b1dcc417bc6ba4d56c7ffe9bd5998975836b13b7d1a5bf7f260e4b5dd6908

失败原因候选

  1. Key 根本不在我们的候选池里 — 可能 Weixin 4.x 的 key 不是 32 字节原始随机,而是某种派生结构
  2. SQLCipher 参数不标准 — Tencent 可能自定义了 HMAC 算法(BLAKE2? SHA3?)或者改了 reserved 布局
  3. Page 1 不是标准 SQLCipher 格式 — 可能 Weixin 在 SQLCipher 之上又套了一层加密(双层)

已尝试的坑

  1. PoC 3d 的验证条件太松:只检查 pt[0..1] 是否是有效 page size,但 pageSize=1 虽有效但极少见, 其他字节也需验证。PoC 3e 改为严格验证 6 个字节
  2. PBKDF2 时间预算估算错误: 初版想一次跑所有 iter × 所有候选, 实际 17K × 3 × 5 = 255K 组合,其中 256000 轮占主要时间,导致卡 4+ 小时
  3. Node stdout 在 bash 后台 buffer: 进度输出在 task 结束前不 flush,误以为脚本挂了。 下次用 node --no-stdout-buffering 或显式 process.stderr.write() + flush

S6 · 下次接手的建议路径

推荐优先级 1: ProcMon + API Monitor 黑盒分析

动机: 与其盲猜 Weixin 怎么用 key,不如观察它怎么读

步骤:
1. 关 Weixin,清空 ProcMon
2. 启 ProcMon 过滤: Path contains "xwechat_files" OR "CryptUnprotect"
3. 启 Weixin 并登录
4. 分析 Weixin 启动后读了哪些文件、按什么顺序
5. 用 API Monitor (x64 版本) 在 Weixin.exe 启动前 attach,监控:
   - advapi32!CryptUnprotectData
   - bcrypt!BCryptDecrypt
   - crypt32!CryptDecrypt
   - sqlite3!sqlite3_key_v2 (如果有导出,否则看内存)
6. 检查 CryptUnprotectData 的 in/out 参数:in 应该是 key_info_data,out 应该是明文 key

如果 Weixin 用 DPAPI: 那我们可以用 CryptUnprotectData API 在同用户会话下直接解开 key_info_data, 完全不需要破 SQLCipher!这是最优路径。

推荐优先级 2: 找社区最新 Weixin 4.x 解密项目

已知可能相关的 GitHub 关键词(WebSearch 在当前环境失效,需另外找):

  • Weixin 4.0 sqlcipher decrypt
  • xwechat_files LoginKeyInfoTable
  • pywxdump Weixin 4
  • wechat-dump-rs
  • @0xlane WeChatMsg 4.0

如果有现成项目,他们的 key 派生算法就是答案。

推荐优先级 3: 继续完成 Phase 3/4 暴力

如果没时间做逆向,把 poc-3e-crack-strict.mjs 的 Phase 3 (iter=64000) 和 Phase 4 (iter=256000) 跑完。在 Node worker_threads 并行可以把 4 小时压到 1 小时。

推荐优先级 4: 扩大候选池

  • 把内存 dump 的候选从 5000 扩到 47743(全量 dedup 后)
  • 加入 相邻滑窗互异 (diff(buf[i], buf[i+1]) > threshold) 启发
  • 考虑 key 不一定是 32 字节,也可能是 16 字节(AES-128)或 64 字节(key + hmac_key 合并)

推荐优先级 5: 放弃 SQLCipher 路径,用 DLL 注入

如果以上都不行,回退到调研报告 §4.2 "WeChatFerry 4.x 路线"。注入风险高但确定能做。


文件索引 (packages/backend/poc/weixin-4x/)

文件 作用 状态
poc-1-watch-db.mjs fs.watch 监听 DB 变化 v1,在 Windows 漏事件
poc-1b-poll-all-db.mjs 轮询 stat 监听 确认了实时写入信号
poc-2-dump-candidates.ps1 Win32 内存 dump v1 文件写入 bug
poc-2b-dump-candidates.ps1 Win32 内存 dump 修复版 生成 500K 候选
poc-3a-dedup.mjs 候选去重 + 频度排序 得到 47K 唯一候选
poc-3b-explore-keyinfo.mjs 打开 key_info.db 看 schema 确认 LoginKeyInfoTable
poc-3c-dump-keyinfo-raw.mjs 逐字节对比 86 条记录 识别出固定头/变化区
poc-3d-crack-key.mjs 宽验证暴力(已废) ⚠️ 假阳性
poc-3e-crack-strict.mjs 严格验证暴力 (SHA1) 3 phase 全未命中
poc-3f-wcdb-aware.mjs WCDB-aware (SHA512+256000+HMAC) 仍未命中
poc-4-dpapi.ps1 DPAPI 试解 key_info_data 数据无效,不是 DPAPI
poc-5-scan-hex-strings.ps1 PS 扫内存找 hex 字符串(慢) ⚠️ 被 poc-6 替代
poc-6a-memdump.ps1 整内存 dump 到磁盘(快) 240MB memdump
poc-6b-scan-memdump.mjs Node 高速扫 memdump 找到 10068 hex + 20 xQuote
poc-6c-find-pragma-context.mjs 找候选 key 上下文 找到 sqlcipher_export 调用点
poc-7-decrypt.mjs 用 raw key 解 message_0.db HMAC 失败
poc-7b/7c/7d/7e 各种 HMAC/KDF 派生组合 400+ 组合全败
poc-8-collect-db-keys.mjs 每 DB 一一对应候选 key 17:17 完美对应
key-candidates.bin 500K 候选 (16 MB) 产物,可复用
key-candidates-dedup.bin 47K 去重候选 (1.5 MB) 产物,可复用
key_info_copy.db key_info.db 拷贝 产物,可复用
weixin-memdump.bin 240MB 全内存 dump 关键产物
weixin-memdump.idx 内存区域索引 关键产物

关键事实摘录(下次 5 分钟读懂)

  1. UIA 路径死: Weixin 4.x 窗口类 Qt51514QWindowIcon,UIA 树仅 3 空壳节点
  2. DB 实时写: message_0.db-wal 在用户发消息时 mtime 立刻变(但 size 不变,WAL 预分配)
  3. key_info.db 明文: SQLite 标准文件,LoginKeyInfoTable 86 条 180B 记录,里面存加密的 key
  4. dump 内存不要管理员: 同用户进程用 OpenProcess 就能读 Weixin 内存
  5. Weixin 用 WCDB(Tencent fork of SQLCipher),cipher_compatibility = 4page_size = 4096
  6. 找到 17 个 raw key 候选: 内存里有 17 条 x'<32B key><16B salt>' 字面量,17:17 与 DB salt 完美一一对应
  7. 暴力失败: 17 个 raw key + 400+ HMAC/KDF 派生组合全部不通过 SQLCipher 4 标准验证
  8. 结论: key 100% 正确,WCDB 自定义了 KDF/HMAC 公式,需查源码或 API hook 才知道

风险 / 合规性再次确认

按调研报告 2026-05-11-weixin-4x-automation-survey.md:

  • 读本机 DB 文件 = 读用户自己电脑上的文件 → 零技术风控(Tencent 服务器无信号)
  • 法律上属"处理用户个人数据",产品上线前务必过法务 + 用户明示授权
  • 破解 key 的过程(即使是研究自己账号)严格意义触及 "反绕过 DRM",但因对象是自己数据非商用传播,属低风险
  • 本 PoC 所有产物(候选 bin、key_info 拷贝)不可分发,每台机器的 key 都不同,也无迁移价值

更新日志

  • 2026-05-11 03:xx (Claude): 初稿。完成 S1-S5。
  • 2026-05-11 03:4x (Claude): 补 S6 暴力解密 Phase 1/2/3 结果 — 全部未命中。
  • 2026-05-12 10:xx (Claude): 重大突破 + 新阻塞。S7-S9 完成,找到 17:17 一一对应 raw key 但派生失败。
  • 2026-05-12 11:xx (Claude): 完全攻破
    • 下载 WCDB 源码到 wcdb-master/,阅读 deprecated/android/sqlcipher/sqlite3.c 的 codec 实现
    • 关键发现:reserve_sz = iv_sz + hmac_szhmac_sz 对 HMAC-SHA512 是 64 字节, 所以总 reserve = 80 字节,不是我之前以为的 48 字节(SQLCipher 3 时代的 SHA1 才 20)
    • poc-7f-reserve80.mjs 用 reserve=80 一次性通过 HMAC + AES
    • poc-10-decrypt-and-read.mjs 全量解密 10644 pages 用时 0.2s,0 HMAC failure
    • poc-12-find-123.mjs 在 Msg_0600e242d978ea1f90b85ac3fe2d22ba 表找到 "123 测试" 那条
    • 方案 1 (DB 读 + Win32 SendInput) 完全可行,可以开始 production 集成