Skip to content

Instantly share code, notes, and snippets.

@bczhc
Last active December 15, 2024 14:40
Show Gist options
  • Save bczhc/c0f29920d4e9d0cc6d2c49f7f2fb3a78 to your computer and use it in GitHub Desktop.
Save bczhc/c0f29920d4e9d0cc6d2c49f7f2fb3a78 to your computer and use it in GitHub Desktop.
找到了Linux QQ NT聊天记录数据库密钥

自QQ用了新内核“NT”架构,原有的数据库解密方法也用不了了。查看新版QQ的文件结构可以发现它的新数据库文件:$HOME/.config/QQ/nt_qq_<ID>/nt_db/nt_msg.db

我尝试在GitHub上全网搜索“nt_msg.db”,发现仅有两个相关的页面:

逆向不会搞。但得到了两个很有用的信息:key的长度为16,和密钥派生函数迭代次数为4000。并且看那上面有个打开SQLCipher的截图,密钥是直接输进去的,那就应都是可打印字符。我使用内存转储,把转储文件通过strings命令过滤出字符串,然后再筛选出16长度的行,这样就可以一个一个去试了。

这是试密码的程序:

#!/bin/env rust-script
//! ```cargo
//! [dependencies]
//! rusqlite = { version = "0.29.0", features = ["bundled-sqlcipher"] }
//! ```
#![feature(try_blocks)]

use rusqlite::{params, Connection};
use std::env::args;
use std::io::{stdin, BufRead, BufReader};
use std::process::exit;

fn main() {
    let args = args().skip(1).collect::<Vec<_>>();
    if args.is_empty() {
        println!("Usage: cmd <db> <kdf_iter>");
        println!("Stdin: keys");
        return;
    }
    let db_path = &args[0];
    let kdf_iter = &args[1];

    let stdin = stdin().lock();
    let reader = BufReader::new(stdin);
    for (i, line) in reader.lines().enumerate() {
        let key = line.expect("Line read error");
        println!("Trying: {} {}", i + 1, key);
        let result: rusqlite::Result<String> = try {
            let db = Connection::open(db_path)?;
            db.pragma(None, "key", &key, |_| Ok(()))?;
            db.pragma(None, "kdf_iter", kdf_iter, |_| Ok(()))?;
            let row = db.query_row("select * from sqlite_master", params![], |r| {
                r.get::<_, String>(0)
            })?;
            println!("{}", row);
            key
        };
        match result {
            Ok(k) => {
                println!("Found it!! {}", k);
                exit(0);
            }
            Err(e) => {
                if e.to_string() != "file is not a database" {
                    println!("{}", e);
                }
            }
        }
    }
}

获取QQ PID的操作:

# https://superuser.com/a/822450
~ ❯ ps --forest -o pid=,tty=,stat=,time=,cmd= -g $(ps -o sid= -p $(pgrep linuxqq))                                           22:53:27
 732596 ?        S    00:00:00 /bin/bash /home/bczhc/bin/scripts/non-proxy linuxqq
 732597 ?        S    00:00:00  \_ /bin/bash /home/bczhc/bin/scripts/exec-override/linuxqq
 732598 ?        S    00:00:00      \_ /bin/bash /home/bczhc/bin/scripts/non-proxy /bin/linuxqq
 732599 ?        Sl   00:03:05          \_ /opt/QQ/qq
 732604 ?        S    00:00:00              \_ /opt/QQ/qq --type=zygote --no-zygote-sandbox
 732650 ?        Sl   00:02:00              |   \_ /opt/QQ/qq --type=gpu-process --crashpad-handler-pid=732633 --enable-crash-reporter
 732605 ?        S    00:00:00              \_ /opt/QQ/qq --type=zygote
 732607 ?        S    00:00:00              |   \_ /opt/QQ/qq --type=zygote
 732771 ?        Sl   00:05:33              |       \_ /opt/QQ/qq --type=renderer --crashpad-handler-pid=732633 --enable-crash-reporte
 829844 ?        Sl   00:00:02              |       \_ /opt/QQ/qq --type=renderer --crashpad-handler-pid=732633 --enable-crash-reporte
 732652 ?        Sl   00:00:02              \_ /opt/QQ/qq --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US
 813475 ?        Sl   00:00:00              \_ /opt/QQ/qq --type=utility --utility-sub-type=audio.mojom.AudioService --lang=en-US --se

使用732599

最终的操作:

# https://serverfault.com/a/408929
sudo ../dump-memory <QQ-pid>
cat *.dump > d
cat d | strings | rg '^.{16}$' | sort | uniq > len16

cp ~/.config/QQ/nt_qq_6bb87db59dd2e7d303966b6fc81dc8dd/nt_db/nt_msg.db .
# 哦对,还有要提到的是这个数据库需要跳过1024字节才是真正的SQLite数据库
cat nt_msg.db | tail -c +1025 | sponge db

# find-key为上面的Rust程序
cat len16 | find-key db 4000

运行,很快就找到了key。

image image image

转为不加密的SQLite:

sqlcipher db "pragma key = 'Q}MD}n\$3P]9uP)Xm'; pragma kdf_iter = 4000" .d | tail +2 | sqlite3 decrypted-db

其他的数据库应该也用的是同一个key,比如group_msg_fts.db,这个应该是存群聊天记录。

我的本地快速解密脚本

key='Q}MD}n$3P]9uP)Xm'
rm /tmp/nt_msg.db /tmp/group_msg_fts.db
cd ~/.config/QQ/nt_qq_6bb87db59dd2e7d303966b6fc81dc8dd/nt_db
pv nt_msg.db | tail -c +1025 > /tmp/db
sqlcipher /tmp/db "pragma key = '$key'; pragma kdf_iter = 4000" .d | tail +2 | sed 's/ROLLBACK; -- due to errors/COMMIT;/' | sqlite3 /tmp/nt_msg.db
pv group_msg_fts.db | tail -c +1025 > /tmp/db
sqlcipher /tmp/db "pragma key = '$key'; pragma kdf_iter = 4000" .d | tail +2 | sed 's/ROLLBACK; -- due to errors/COMMIT;/' | sqlite3 /tmp/group_msg_fts.db
@Young-Lord
Copy link

好强!请问您愿意把这个 gist 的内容或链接加入 qq-win-db-key 里吗?我来做或者您发 pr 都行。

@bczhc
Copy link
Author

bczhc commented Apr 13, 2024

好强!请问您愿意把这个 gist 的内容或链接加入 qq-win-db-key 里吗?我来做或者您发 pr 都行。

啊,只是我这种做法太暴力毫无技术可言,这个只是最开始我想尝试解数据库,不管用什么方法。因为逆向这种东西我没怎么研究过。你来看着做吧,如果这个gist有价值的话。内容也是愿意共享的。

@Young-Lord
Copy link

好,那我去贴个链接。毕竟逆向还是需要经常微调各种参数的,这样搞可能还简便一些。

@HHaoWang
Copy link

请问这种暴力搜索的方法在windows上也可以实现吗?

@bczhc
Copy link
Author

bczhc commented May 13, 2024 via email

@HHaoWang
Copy link

理论密码当然出现在程序内存中的,可以暴力搜索,至于怎么做……我不会Windows的😂

---- 回复的原邮件 ---- | 发件人 | @.> | | 日期 | 2024年05月13日 10:36 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: bczhc/a.md | @HHaoWang commented on this gist. 请问这种暴力搜索的方法在windows上也可以实现吗? — Reply to this email directly, view it on GitHub or unsubscribe. You are receiving this email because you authored the thread. Triage notifications on the go with GitHub Mobile for iOS or Android.

我用procdump了,然后又用之前附加调试后找到的密钥使用windbg进行查找结果没找到,不知道是不是我没dump对

@Young-Lord
Copy link

我用procdump了,然后又用之前附加调试后找到的密钥使用windbg进行查找结果没找到,不知道是不是我没dump对

密钥可能每次更新,你这个方法看起来是没问题的

@HHaoWang
Copy link

HHaoWang commented May 13, 2024

我用procdump了,然后又用之前附加调试后找到的密钥使用windbg进行查找结果没找到,不知道是不是我没dump对

密钥可能每次更新,你这个方法看起来是没问题的

唔,我这边试没发现密钥有更新,也就是在密钥没有更新的情况下dump出来找不到,我觉得应该是没dump对?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment