QQNT 9.9.26-44343(20260217有效)提取数据库 Key 完整教程(含可运行脚本)

适用场景:Windows 上的 QQNT 9.9.26-44343,需要提取 SQLCipher 数据库 key(用于后续解密 nt_msg.db 等数据库)。

0. 风险说明

  • 有一定账号风控风险,建议先备份聊天记录。
  • 仅用于个人数据导出与学习,请遵守法律法规与平台协议。

1. 环境准备

  • 管理员 PowerShell
  • QQNT 已安装:9.9.26-44343
  • Python 3.10+(实测 3.13 可用)

安装依赖:

py -m pip install frida psutil

2. 保存脚本(版本专用)

新建文件:qqnt_get_key_9926.py,内容如下:

import time
import frida
import psutil

# 仅适配 QQNT 9.9.26-44343(wrapper.node 内偏移)
TARGET_RVA = 0x021A5BF0
QQ_EXE = r"C:\Program Files\Tencent\QQNT\QQ.exe"


def try_attachable_main_pid():
    for p in psutil.process_iter(['pid', 'name', 'cmdline']):
        if (p.info['name'] or '').lower() != 'qq.exe':
            continue
        cmd = p.info['cmdline'] or []
        if len(cmd) != 1:
            continue
        pid = p.info['pid']
        try:
            s = frida.attach(pid)
            s.detach()
            return pid
        except Exception:
            pass
    return None


def build_script():
    return f"""
function bytesToHex(ab) {{
  if (!ab) return '';
  const u8 = new Uint8Array(ab);
  let s = '';
  for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, '0');
  return s;
}}

(function waitAndHook() {{
  const moduleName = 'wrapper.node';
  const targetRva = ptr('0x{TARGET_RVA:x}');
  let hooked = false;

  function doHook() {{
    if (hooked) return;
    let m = null;
    try {{
      m = Process.getModuleByName(moduleName);
    }} catch (e) {{
      return;
    }}
    if (!m) return;

    hooked = true;
    const target = m.base.add(targetRva);
    console.log('[hook] base=' + m.base + ' target=' + target);

    Interceptor.attach(target, {{
      onEnter(args) {{
        try {{
          // x64 Win: rcx, rdx, r8, r9
          const dbNamePtr = args[1];
          const keyPtr = args[2];
          const nKey = args[3].toInt32();
          const dbName = dbNamePtr.isNull() ? '' : (dbNamePtr.readUtf8String() || '');
          const keyHex = (keyPtr.isNull() || nKey <= 0) ? '' : bytesToHex(keyPtr.readByteArray(nKey));

          console.log('[dbName] ' + dbName);
          console.log('[nKey] ' + nKey);
          console.log('[key] ' + keyHex);
        }} catch (e) {{
          console.log('[hook-error] ' + e);
        }}
      }}
    }});
  }}

  doHook();
  const timer = setInterval(() => {{
    if (hooked) {{
      clearInterval(timer);
      return;
    }}
    doHook();
  }}, 200);
}})();
"""


def run_attach_mode(pid):
    sess = frida.attach(pid)
    script = sess.create_script(build_script())

    def on_message(msg, data):
        if msg.get('type') == 'send':
            print(msg.get('payload'))
        elif msg.get('type') == 'error':
            print(msg)

    script.on('message', on_message)
    script.load()
    print(f'[info] attached pid={pid}, waiting...')
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass
    sess.detach()


def run_spawn_mode():
    dev = frida.get_local_device()
    pid = dev.spawn([QQ_EXE])
    sess = dev.attach(pid)
    script = sess.create_script(build_script())

    def on_message(msg, data):
        if msg.get('type') == 'send':
            print(msg.get('payload'))
        elif msg.get('type') == 'error':
            print(msg)

    script.on('message', on_message)
    script.load()
    print(f'[info] spawned pid={pid}, resuming...')
    dev.resume(pid)

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass

    sess.detach()


if __name__ == '__main__':
    pid = try_attachable_main_pid()
    if pid:
        run_attach_mode(pid)
    else:
        run_spawn_mode()

3. 运行

确保 QQ 已登录(或让脚本自动拉起 QQ):

py .\qqnt_get_key_9926.py

出现类似输出即成功:

[dbName] main
[nKey] 16
[key] 5a6b457d79777b2e6150376e582d6572

或:

[dbName] main
[nKey] 20
[key] 4244313536443637313044353444383738324634

4. key 如何使用

  • 两种长度都可能出现,取你目标库对应的那组。
  • key 是十六进制字符串;有时本质是 ASCII 文本的十六进制表示。

PowerShell 转 ASCII 示例:

[Text.Encoding]::ASCII.GetString([Convert]::FromHexString('4244313536443637313044353444383738324634'))

5. 数据库常见路径

C:\Users\你的用户名\Documents\Tencent Files\你的QQ号\nt_qq\nt_db\nt_msg.db

如果只看到 dbName=main,这是 SQLite 连接别名,不代表数据库文件名错误。

6. 版本兼容说明

本教程里的 TARGET_RVA = 0x021A5BF0 仅对应 QQNT 9.9.26-44343。后续版本若失效,需要重新定位偏移。

补一个我实际踩到的坑,避免大家反复试错:

  1. pcqq_get_key.py 是旧 PCQQ 逻辑,直接拿来打 QQNT 基本会翻车(模块和调用链不一样)。
  2. QQNT 是多进程,随便 attach 一个 QQ.exe 很容易报 ProcessNotRespondingError。优先主进程(常见是 cmdline 只有 1 个参数的那个)。
  3. 如果在 wrapper.node 还没加载时就 hook,会报 unable to find module 'wrapper.node'。解决方法是轮询模块加载后再挂钩,或者用 spawn 方式尽早注入。
  4. dbName=main 不是异常,它是 SQLite 连接别名,不是数据库文件名。

一句话总结:版本、进程、时机这三个条件必须同时对上,才能稳定拿到 key。


化腾 你现在赶紧求我别走 喝喝