English: A WeChat 4.1.x (Windows) chat database decrypt & export toolkit. It extracts the DB master key from memory via Frida (no QR login needed), decrypts SQLCipher 4 databases page-by-page, and exports each conversation to readable text.
功能 / Features
- 内存取钥:Frida spawn 微信,捕获 codec 主密钥(登录前即命中)并用 HMAC 校验,无需扫码
- 批量解密:SQLCipher4(AES-256-CBC + PBKDF2)递归解密
db_storage全部数据库 - 精确导出:按会话逐个导出 txt——发送者 100% 精确归因(
Name2Id+Msg_<md5>),昵称/备注解析、按日期分栏、文本+类型占位 - 隐私安全:真实密钥与本地数据不入库(
.gitignore+local_config.py),分享代码不泄露个人数据
缘起:社区
wechat-decrypt在微信 4.1.11+ 失效(Issue #2)。 上游方案在 微信 4.1.12.26 (x64) 上验证 25/25 数据库解密成功;本仓库在此基础上适配微信 4.1.13.12 (x64) 并新增导出工具。上游在 微信 4.1.12.26 (x64) 上验证 25/25 数据库解密成功;本仓库在此基础上适配微信 4.1.13.12 (x64) 并新增导出工具。
本仓库在上游 wechat-4.1.12-decrypt 基础上做本机适配,主要变化:
微信 4.1.13.12 的 weixin.dll 中 codec 配置函数入口 RVA = 0x35de480
(定位方法:.pdata 展开 + 引用 MMV1 特征串做交叉引用)。
⚠️ 偏移必须落在函数入口;若落在指令中间(如0x35de489)永远不会触发,抓不到密钥。
运行环境为 frida 17.x,部分旧 API 已移除,脚本相应调整:
- 读内存:
Memory.readByteArray(addr, n)→ 改用NativePointer实例方法addr.readByteArray(n) - 模块加载监听
ModuleMap.on('change', …)已不可用 → 改为纯轮询 hook - HMAC 验证库路径按 4.1.13 子目录布局
db_storage\message\message_0.db自动查找 - 捕获触发密集时,验证循环遍历所有唯一候选而非只看最后一条
4.1.13 把消息收敛为单个 message_0.db,内部是每会话一张的 Msg_<md5> 表:
- 表名 md5 后缀 =
md5(会话对象 user_name)→ 可精确还原每张表对应哪个聊天对象 real_sender_id= 库内Name2Id表的 rowid → 发送者可 100% 精确归因,无需"锚点句"- 文本消息
local_type=1;非文本按中文占位导出(图片/语音/视频/表情/系统等)
新增 export_msgs.py:按会话逐个导出 txt —— 自动解析昵称/备注、按日期分栏、头部元信息、--list / --only 过滤。用法:
python export_msgs.py [decrypted目录] [out目录] --list # 先看会话清单
python export_msgs.py decrypted export --only 某聊天关键字 # 只导出匹配会话
python export_msgs.py decrypted export # 全量导出
python find_key.py # 取密钥: 自动 hook 0x35de480, HMAC 验证后写 key.txt
python decrypt_all.py # 解密 db_storage 全部库到 decrypted/
python export_msgs.py decrypted export # 导出每个会话为 txt
🔒 密钥与数据目录为本机专属:真实路径写在被
.gitignore排除的local_config.py中;key.txt/key_meta.json/decrypted//export//*.log均不入库。分享代码前请确认未附带个人数据。
社区工具 wechat-decrypt 通过扫描进程内存中的 x'<64hex_enc_key><32hex_salt>' 模式提取密钥。
但在微信 4.1.11+ 中此模式不再存在(密钥缓存格式变更),导致工具失效。
- 密钥格式变了:4.1.11+ 不再缓存
x'<hex>'字符串,password(32 字节原始密钥)以二进制形式存在于 codec 配置函数的参数中 - password ≠ enc_key:password 需要经过 KDF 派生才是加密用的 enc_key(之前的工具直接拿缓存值当密钥验证,参数不对必然失败)
- KDF 参数:
PBKDF2-HMAC-SHA512, 256000 iterations, salt = 数据库文件头 16 字节,派生 32 字节 enc_key - 加密参数(SQLCipher 4):
AES-256-CBC + HMAC-SHA512page_size = 4096, reserve = 80(IV 16 字节 @ offset 4016,HMAC 64 字节 @ offset 4032)- 布局:
[0:16]=salt | [16:4016]=加密数据 | [4016:4032]=IV | [4032:4096]=HMAC
- HMAC 验证(SQLCipher 4 标准):
mac_salt = salt XOR 0x3a mac_key = PBKDF2-HMAC-SHA512(enc_key, mac_salt, 2, 32) valid = HMAC-SHA512(mac_key, page1[16:4032] + pack('<I', 1)) == page1[4032:4096]
微信 4.1.12 的 codec 配置函数引用 "MMV1" 魔数字符串,位于 weixin.dll 偏移 0x3486140(4.1.12.26)。
关键点:
- 必须用 Frida spawn(进程启动早期注入),attach 会错过密钥设置
- hook 该函数入口时,rcx 指向的结构前 32 字节就是 password
⚠️ 该路径存在 anti-hook 检测(hook 后微信可能弹错误报告崩溃),但 spawn 早期 hook 可以捕获
# 伪代码
import frida
pid = frida.spawn(r"C:\Program Files\Tencent\Weixin\Weixin.exe")
session = frida.attach(pid)
script = session.create_script("""
var base = Process.getModuleByName('Weixin.dll').base;
Interceptor.attach(base.add(0x3486140), {
onEnter: function(args) {
// args[0] (rcx) 指向的结构前 32 字节 = password
console.log('PASSWORD:', hexdump(args[0], {length: 64}));
}
});
""")
script.load()
frida.resume(pid) # 微信启动 -> 密钥设置 -> 捕获结构说明:rcx 指向的 codec 配置结构 = password[32] + 其他字段,触发时机为数据库打开(进程启动早期)。
import hashlib
from Crypto.Cipher import AES
PASSWORD = bytes.fromhex("<获取到的 64 位 hex>") # 32 字节主密钥
def derive_enc_key(salt):
return hashlib.pbkdf2_hmac("sha512", PASSWORD, salt, 256000, dklen=32)
def decrypt_page(enc_key, page_data, pgno):
iv = page_data[4016:4032]
if pgno == 1:
encrypted = page_data[16:4016]
page = b'SQLite format 3\x00' + AES.new(enc_key, AES.MODE_CBC, iv).decrypt(encrypted) + b'\x00'*80
else:
encrypted = page_data[:4016]
page = AES.new(enc_key, AES.MODE_CBC, iv).decrypt(encrypted) + b'\x00'*80
return page- 每个数据库独立 salt(文件头 16 字节),用同一个 password 派生各自的 enc_key
1. 表名映射:Msg_<MD5(wxid)>,通过 Name2Id 表(user_name ↔ rowid)确认。示例:MD5("wxid_xxx") = 表名 hash。
2.
- message_0(最新库):
real_sender_id=Name2Id表的 rowid(如 102 = 某 wxid,3 = 另一 wxid) - 旧库(message_1/2/3 等):
real_sender_id= 会话内角色(1/2),且不同库的 1/2 含义可能相反! - 必须用"锚点句"逐库验证:找双方都确认过的句子(如某句确定是用户发的),确定该库的 sender 映射后再导出
- 实测:message_1/2 的 sender1=用户;message_3 的 sender1=对方(相反!)
3. 消息类型:local_type 字段
1= 文本(message_content 明文)47= 表情(XML,含 alias)3/34/43= 图片/语音/视频(content 是 protobuf,需额外解析)10000= 系统消息(撤回等)
4. 其他:WAL 文件帧格式 = pgno(4,大端)+unk(4)+salt(16)+page(4096)(解密:用主库 enc_key,IV=页[4016:4032],加密范围[0:4016]——帧 salt 只是密钥轮换检测,实际用主库密钥);手机备份 Backup.db 用手机端独立密钥,PC 端无法解密
| 项目 | 结果 |
|---|---|
| 微信版本 | 4.1.12.26 (x64, Win7) |
| 数据库 | 25/25 全部解密成功 |
| 单库 | message_0~3.db 共数万条会话消息完整导出 |
| 发送者 | 100% 精确(数据库字段,非推测) |
- WAL 文件:SQLCipher 4 的 WAL 帧格式 =
pgno(4, 大端) + unk(4, 大端) + salt(16) + page(4096),解密用主库 enc_key(IV=页[4016:4032],加密范围[0:4016]),帧 salt 仅用于密钥轮换检测 - 手机备份 Backup.db:使用手机端独立密钥,电脑端无法解密
find_key.py— Frida spawn 密钥获取脚本decrypt_all.py— 全库解密脚本export_msgs.py— 消息导出(含逐库 sender 验证示例)
本次破解由 AI 辅助逆向 完成:
- 逆向分析与脚本编写:DeepSeek V4 系列模型(v4-pro-preview → v4-flash-0731)
- 环境操作与验证:Windows 7 + Python 3.8 + Frida 15.2.2 + pycryptodome
一个时代正在到来:AI 与人类协作,可以把"不可能"变成 48 小时内的"已完成"。
MIT