QQ NT 数据库解密方法(技术方法摘编)
本篇只保留逆向取 key 与 SQLCipher 参数探索的技术方法,不含任何明文 key、数据库头部 hex、salt、本地路径或聊天/联系人信息。 目标场景:解密 macOS 版 QQ NT 的本地
nt_msg.db,本地自用。 状态记录:取 key 一关(最难的逆向部分)已完全攻克并可复现;解密卡在这一版客户端(6.9.80)用了非标准 SQLCipher 配置,标准参数与穷举都失败——留作后续攻关方向。
#一、取 key:在 nt_sqlite3_key_v2 断点抓
- QQ NT 用 WCDB(SQLCipher 变体)。开库时会调用一个入口函数
nt_sqlite3_key_v2(db, "main", key, keylen),签名与标准sqlite3_key_v2一致。 - 在该函数入口下 lldb 断点,抓第 3 个参数(key 指针),
keylen=16。连续 40+ 次调用抓到的值完全一致,x1恒为"main",说明 key 原样透传给底层 SQLCipher、没做任何变换,所有库(含nt_msg.db)共用同一把 key。 - 触发时机:登录/打开主界面不一定触发开库;点开一个具体聊天会话、滚动历史消息能稳定触发
nt_sqlite3_key_v2。
#二、正确定位函数入口 VA(关键坑)
上游工具 qq-win-db-key 自带的 find_key_func.py 在这一版上定位错误,返回的是假地址,据此下的断点永远打不中。两个 bug:
- 只匹配字符串地址的低 12 位 → 在上百 MB 的二进制里撞上大量假的
ADD指令; - fat binary(通用二进制)里 x86_64 切片在前,
data.find会先命中 x86 那份字符串,导致 VA 算成负数。
修正做法(自写的 find_key_func2.py):
- 完整解析
adrp + add指令配对,精确还原字符串的完整虚拟地址(VA); - 用
LC_FUNCTION_STARTS(Mach-O 的函数起始表)反查字符串引用点所属的函数入口; - 通过两条诊断字符串(形如
nt_sqlite3_key_v2: db=和no key)交叉确认目标函数。
定位到真入口 VA 后,写进 lldb 自动脚本,断点会自动吸附到函数入口,点开会话即可打印 key。
#三、解密卡点与后续攻关方向
拿着确认无误的 key,用所有已知 SQLCipher 参数都解不开这一版的 nt_msg.db。已排除/尝试过:
- 页结构已确认:文件剥掉前 1024 字节头后,剩余长度正好是
页数 × 4096,所以「头长 1024、page_size=4096」是对的。头部有明文标识SQLite header 3+QQ_NT DB+ 版本号 +HMAC_SHA1(字样。 - sqlcipher CLI 全组合穷举失败:
kdf_iter ∈ {1,2,4000,4096,64000,256000,…},hmac ∈ {SHA1,SHA256,SHA512},kdf ∈ {PBKDF2_HMAC_SHA1/256/512},page ∈ {4096,1024},cipher_compatibility 1~4,cipher_use_hmac=OFF,cipher_plaintext_header_size=1024(不剥头)——全部报file is not a database。 - 纯 Python AES 逐页解穷举失败:绕过 HMAC 直接逐页 AES 解、看明文特征;穷举 iter(1 到 256000 多档)、raw key 直接当 AES-128/256、以及 md5/sha1/sha256(key) 派生——结果零字节数全在随机基线附近,没有任何组合解出结构化明文。
- 反汇编确认
nt_sqlite3_key_v2只是把 key 原样透传给更深一层的 wrapper,wrapper 里无变换。
最靠谱的后续破法(别再盲猜 KDF 参数):
- 在更深处断点抓「最终派生出的 32 字节 AES key + SQLCipher 实际用的 salt」:跟进
nt_sqlite3_key_v2→ 底层 wrapper → SQLCipher 的sqlcipher_codec_key_derive/sqlite3Codec附近,断点直接读派生后的 raw key 和 salt,拿去 AES-CBC 解,跳过一切 KDF 猜测。 - 或对 wrapper 里
PBKDF2/EVP_*/sqlcipher相关字符串做 xref(复用上面adrp+add的定位法),定位 codec 实现。 - 从 QQ 内存直接捞明文页:运行时 SQLite page cache 里有已解密的页,dump 出来能直接读(但只有近期访问过的)。
- 兜底:等上游
qq-win-db-key/QQBackup更新,或实测其全自动流程(可能也栽在同样的 codec 差异上)。
#四、解密成功后的正文表结构(字段号 schema)
消息表:c2c_msg_table(私聊)/ group_msg_table(群)。字段号是纯数字:
| 字段号 | 含义 |
|---|---|
40001 |
msgID |
40020 |
发送者 UID |
40030 |
对方号或群号 |
40033 |
发送者号 |
40050 |
时间(Unix 秒) |
40090 |
群名片 |
40013 |
方向(0 收 / 1,2 发) |
40800 |
正文(protobuf,文本在 tag 45101) |
判断发送方:40033 == 自己的号。
#五、合规红线
key、解密库、导出记录全部只留本地,绝不上传/部署;机器上的真实聊天数据不外发。仅限用户导出自己的记录、本地自用。
来源:沉淀/04-逆向与数据导出/QQ解密-交接文档.md(整理于 2026-08-18)