--- name: wechat-db-decrypt description: 端上解密打开微信 EnMicroMsg.db(SQLCipher),拿聊天/联系人/朋友圈原始数据。纯本地 su,不经 Mac/Frida。含 key 推导 + 多账号选库 + 已验证密钥缓存。 version: 1.0 category: communication --- # WeChat DB Decrypt(微信数据库端上解密) 微信的 `EnMicroMsg.db` 是 **SQLCipher 加密**的。这个 skill 让你在设备内直接算出密钥、拷库、解密只读打开, 拿到 `rcontact`(联系人)、`message`(聊天)、`SnsInfo`(朋友圈)等原始表。**纯 su + sqlcipher,不经 Mac、不用 Frida。** ## When to Use - 需要读微信联系人 / 聊天记录 / 朋友圈的**结构化原始数据**(不是截图 OCR) - 给 lifelog / CRM 灌微信数据、做人物档案、群消息摘要 - 任何"要查微信数据库"的任务 ## 密钥推导公式(真机验证) ``` key = MD5( IMEI + uin ) 取 hex 前 7 位 IMEI = "1234567890ABCDEF" # 微信默认值,本机适用 uin = 从 shared_prefs 读 last_login_uin(当前登录账号) SQLCipher 参数: cipher_compatibility = 1, cipher_page_size = 1024 ``` ## ⭐ 已验证密钥缓存(2026-07-08,先查这里,命中就别重算) | uin | MicroMsg 目录 | key | 库大小 | 备注 | |---|---|---|---|---| | 257966415 | `ec4f423b316d41e9af5e8398155cf105` | `1986554` | ~90MB | **当前主账号**,已实测 720 表 / rcontact 42271 条 | > 只要 `last_login_uin` 还是 257966415,直接用 key `1986554`,跳过推导。 > 换号了才需要走下面的"重新推导"。 ## 选对账号目录(多账号必看) 设备上 `MicroMsg/` 下可能有**多个账号目录**,目录名 = `MD5("mm" + uin)`。 **不要用 `ls | head -1`**——那会按字典序选错账号。正确定位: ```bash BASE=/data/user/0/com.tencent.mm UIN=$(su --mount-master -c "grep -hoE 'last_login_uin\">[0-9-]+' $BASE/shared_prefs/*.xml" | grep -oE '[0-9-]+' | head -1) DIR=$(printf '%s' "mm${UIN}" | md5sum | cut -c1-32) # = 当前登录账号的目录名 echo "uin=$UIN dir=$DIR" ``` ## 完整流程(重新推导 + 打开) ```bash BASE=/data/user/0/com.tencent.mm export PATH=/system/bin:/system/xbin:$PATH # 1. 读当前账号 uin,算目录、算 key UIN=$(su --mount-master -c "grep -hoE 'last_login_uin\">[0-9-]+' $BASE/shared_prefs/*.xml" | grep -oE '[0-9-]+' | head -1) DIR=$(printf '%s' "mm${UIN}" | md5sum | cut -c1-32) KEY=$(printf '%s' "1234567890ABCDEF${UIN}" | md5sum | cut -c1-7) echo "uin=$UIN key=$KEY dir=$DIR" # 2. su 拷出该账号的库到可读位置(含 wal/shm,保证最新数据) su --mount-master -c "D=$BASE/MicroMsg/$DIR; cp \$D/EnMicroMsg.db /sdcard/en.db; \ [ -f \$D/EnMicroMsg.db-wal ] && cp \$D/EnMicroMsg.db-wal /sdcard/en.db-wal; \ [ -f \$D/EnMicroMsg.db-shm ] && cp \$D/EnMicroMsg.db-shm /sdcard/en.db-shm; \ chmod 666 /sdcard/en.db*" # 3. sqlcipher 打开验证 + 查数据 sqlcipher /sdcard/en.db </avatar/` 及独立库 --- ## 读会话上下文(自动回复·环节2:给回复生成备料) 拿到一条触发事件 `{talker, isGroup, sender(wxid), kind}`(来自 watcher 的 `incoming.jsonl`), **一次拷库**即可查齐下面三块,拼成喂给 LLM 的上下文。别分三次拷库。 ### 1. 发送者身份 ```sql SELECT nickname, conRemark, alias FROM rcontact WHERE username=''; ``` - 私聊:sender = talker;群聊:sender = 群消息 `wxid:` 前缀那个 - **显示名优先级**:conRemark(备注) > nickname(昵称) > alias > wxid - 群名:`SELECT nickname FROM rcontact WHERE username='';`(群的 nickname 就是群名) ### 2. 最近 N 条会话历史 ```sql SELECT isSend, type, createTime, content FROM message WHERE talker='' ORDER BY msgId DESC LIMIT 20; -- 再按时间正序排回来 ``` 解析规则: - `isSend=1` → 我(小火子)发的;`isSend=0` → 对方发的 - 群消息 content 形如 `wxid_xxx:\n正文`,剥前缀取「发送者 wxid + 正文」,每个 wxid 再走身份查询解析显示名 - `type`:`1`=纯文本;`3`=图片;`34`=语音;`43`=视频;`47`=表情;`49`=appmsg(链接/文件/引用/聊天记录);大 type + 内容含「撤回了一条消息 / 拍了拍」= 系统消息(忽略) - 只把**文本**喂给 LLM;非文本给占位符 `[图片]`/`[语音]`/`[链接:标题]`,别把整坨 XML 塞进去 ### 3. 引用原文(kind=group_quote_me,或消息含 ``) type=49 且 appmsg `57`,`` 块内: - `` / `` = 被引消息作者 wxid - `` = 作者显示名 - `` = 被引原文(群里被引消息也可能带 `wxid:` 前缀,同样剥) 把 `` 当作「对方在引用这句话回复你」。(真样例少,遇到先 dump 一条核对字段。) ### 组装(喂环节3 的上下文骨架) ``` [群] {群名} ← 群聊才有 对方:{显示名}({备注或wxid}) [引用] {被引作者}: {被引原文} ← 有引用才有 最近对话: {时间} {对方显示名}: {文本} {时间} 我: {文本} ... 本条:{对方显示名}: {正文} ``` ### 注意 - **官方号**(username 以 `gh_` 开头)= 公众号推送,不是真人,通常不回(策略层过滤) - 群成员的「群昵称」(per-group) 在 chatroom 表的 displayname,比 rcontact.nickname 更贴切但要多查一次;MVP 先用 rcontact 的名字够用 ## 说明 - key 是**确定性**的:uin 不变 → key 不变。没有"独立 key 文件"存在别处,缓存表就是唯一的省算路径。 - app 侧同逻辑源码在仓库 `lifelog-recorder/.../vision/WeChatDb.kt`(供交叉参考)。