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
@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