Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: 读取新版QQNT数据库 #38

Open
ygjsz opened this issue Apr 25, 2023 · 77 comments
Open

Feature request: 读取新版QQNT数据库 #38

ygjsz opened this issue Apr 25, 2023 · 77 comments

Comments

@ygjsz
Copy link

ygjsz commented Apr 25, 2023

现在腾讯在推NT架构的QQ,要搞全平台统一,然后手机版的NT架构的QQ也开始内测了。
最新的手机版内测QQNT上的数据库架构已经大改了,变得和Windows/Mac/Linux版QQNT一样了。
版本:8.9.58.11050
如图 老版本的数据库仍然存在,但是很明显聊天记录已经不存放在老库里面了
image
S30425-20053693

现在新QQNT聊天记录数据库的位置是/databases/nt_db
S30425-20055692
S30425-20054788

从文件名来看这个数据库架构和电脑版QQNT是一样的
image
(Windows版QQNT数据库)
目前还没研究出新数据库密钥存放的位置以及新数据库的格式
image
(从文件头来看是SQLite3?)

不知道有没有希望搞定新版的数据库解密

另:手机版QQNT内测包下载链接:https://downv6.qq.com/qqweb/QQ_1/android_apk/qq_8.9.58.11050_64.apk
(就算没有内测资格也能用,在弹出内测活动已结束的窗口的时候按两下返回就可以把那个窗口关掉)
(不建议用大号测试,这个内测QQNT一旦登录之后就会把所有的老库里的聊天记录全都迁移进新库,无法撤销)

另2:MacQQNT的数据库位置:/Users/{用户名}/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_{一串ID}/nt_db
目测数据库格式和其他平台是一样的
image

@ygjsz ygjsz changed the title 关于新版QQ NT数据库的变化问题 Feature request: 读取新版QQNT数据库 Apr 25, 2023
@Young-Lord
Copy link
Member

Electron那个?有计划,有时间可能去试一试
运气好的话跟msg3.0.db一样的加密方式就会很轻松

@ygjsz
Copy link
Author

ygjsz commented Apr 25, 2023

是的 大佬加油!

@Young-Lord
Copy link
Member

目前是定位到了相关代码在libkernel.so里,也是用的和Msg3.0.db一个思路的 sqlite3 加密。就是具体的算法可能有改动
周末试着动态调试一下吧
IDA中libkernel.so的字符串列表

(具体怎么找的:find . | xargs grep -Hn nt_msg.db {} 2>/dev/null

@Young-Lord
Copy link
Member

Young-Lord commented Apr 26, 2023

然后Windows版貌似是在wrapper.node里,是编译为 PE 可执行文件格式
由进程标题为QQ的进程以dll方式加载,用了sqlcipher,理论上 hook nt_sqlite3CodecAttach即可

@Young-Lord
Copy link
Member

那应该是比较简单的,用Frida hook一下就可以了
(如果没改sqlcipher算法的话)

@Young-Lord
Copy link
Member

@ygjsz
Copy link
Author

ygjsz commented May 14, 2023

试试? https://github.com/Young-Lord/qq-win-db-key/blob/master/android.md

Screenshot_20230514-163557
我这不太行 frida貌似跑不起来的样子
(SELinux已经是permissive了 有空我换个设备再试一下

@Young-Lord
Copy link
Member

我这边貌似也不太行……只要注入 Frida 就会段错误/拒绝注入,想必是有反调一类的

@Young-Lord
Copy link
Member

Young-Lord commented May 20, 2023

成了。
以及,如果你跑不起来也可以再试试,看起来这个只是在QQ未运行时不工作而已
(不过也挺奇怪的,之前好像是看到有几处地方相关代码被inline了,但也看不出逻辑)
成功时的终端输出

@alphagocc
Copy link
Contributor

alphagocc commented Jul 12, 2023

似乎 Windows 上 key 长度为 16(?
为啥感觉不是很对的样子

@Young-Lord
Copy link
Member

似乎 Windows 上 key 长度为 16(? 为啥感觉不是很对的样子

我在这边没有什么进展,要是有什么进度可以给我发发

@alphagocc
Copy link
Contributor

image

🥳🥳🥳
方法

hook nt_sqlite3_key_v2 拿 key

删掉 nt_msg.db 前 1024 个字符

打开的时候 pbkdf iter=4000

@alphagocc
Copy link
Contributor

顺带一提,windows 上 key 长就是 16

@alphagocc
Copy link
Contributor

但是我不知道 BLOB 中的内容如何解码

@Young-Lord
Copy link
Member

image 🥳🥳🥳 方法

hook nt_sqlite3_key_v2 拿 key

删掉 nt_msg.db 前 1024 个字符

打开的时候 pbkdf iter=4000

“打开”是用社区版sqlcipher就可以了吗?看起来我后面这两步都没有做XD
方便的话可以提个pr,感谢
以及,blob的话可以考虑下Protobuf,盲猜也是写在native层

@alphagocc
Copy link
Contributor

“打开”是用社区版sqlcipher就可以了吗?

是的,开源的那个就可以

Blob我明天再看看

PR 的话

好像这个函数在不同版本的 wrapper.node 里二进制不太一样,比较难写成脚本。。。。

@Young-Lord
Copy link
Member

Young-Lord commented Jul 15, 2023

PR 的话

好像这个函数在不同版本的 wrapper.node 里二进制不太一样,比较难写成脚本

有什么特征汇编指令序列一类的吗?再不济用户手动输入函数地址都没问题,毕竟这个repo本身就不是为了提供全套解决方案的
就算是丢个 教程.md 也很有用!

@alphagocc
Copy link
Contributor

那我试着写个教程

@ygjsz
Copy link
Author

ygjsz commented Sep 24, 2023

更新 已经寄了
8.9.78+上vmp了

@yllhwa
Copy link
Member

yllhwa commented Sep 28, 2023

更新 已经寄了 8.9.78+上vmp了

8.9.78+包括8.9.78.12275吗?我这儿显示是最新版本了,但是能hook到呢,只是地址变了。
就是hook出来密钥还是没法解密,这方面还需要研究。

setTimeout(function () {
  let base_addr = Module.findBaseAddress("libkernel.so");
  console.log("libkernel.so base address: " + base_addr);
  // nt_sqlite3_key_v2, sub_1CCE4DC
  let nt_sqlite3_key_v2_addr = base_addr.add(0x1cce4dc);
  console.log("nt_sqlite3_key_v2_addr: " + nt_sqlite3_key_v2_addr);
  Interceptor.attach(nt_sqlite3_key_v2_addr, {
    onEnter: function (args) {
      console.log("pKey: " + Memory.readCString(args[2]));
      console.log("nKey: " + args[3].toInt32());
    },
  });
}, 1200);

@Young-Lord
Copy link
Member

Young-Lord commented Sep 28, 2023

解密的话你看下那个仓库里的android.md,我没试过。
看看有没有什么特征字节?(也就是single_function的参数)
或者我再拉出来我之前写的一些小工具找offset吧…
@yllhwa

@yllhwa
Copy link
Member

yllhwa commented Sep 28, 2023

解密的话你看下那个仓库里的android.md,我没试过。 @yllhwa

我测试是不行的,我怀疑那是Windows的解密方式
看起来qq用的是4.5.1版本的sqlcipher,我打算bindiff下看看是不是哪儿有魔改的地方

@Young-Lord
Copy link
Member

解密的话你看下那个仓库里的android.md,我没试过。 @yllhwa

我测试是不行的,我怀疑那是Windows的解密方式

那我最近也试一试,不行的话去加个“可能不可靠”的标志

@yllhwa
Copy link
Member

yllhwa commented Sep 28, 2023

呼,搞了一晚上终于搞好了。
之前attach会出现问题应该是奇怪的权限问题引起的,将导出地址设置为公共目录即可。
以下代码对应的安卓qqnt版本为8.9.78.12275。
不保证不会对聊天记录产生影响(

// frida -U -f com.tencent.mobileqq -l final.js
const DATABASE_URI =  "/data/user/0/com.tencent.mobileqq/databases/nt_db/nt_qq_{CHNAGE_THIS_TO_YOURS}/nt_msg.db";

// FOR LOG
let SQLITE3_EXEC_CALLBACK_LOG = true;
let index1 = 0;
let xCallback = new NativeCallback(
  (para, nColumn, colValue, colName) => {
    if (!SQLITE3_EXEC_CALLBACK_LOG) {
      return 0;
    }
    console.log();
    console.log(
      "------------------------" + index1++ + "------------------------"
    );
    for (let index = 0; index < nColumn; index++) {
      let c_name = colName
        .add(index * 8)
        .readPointer()
        .readUtf8String();
      let c_value = "";
      try {
        c_value =
          colValue
            .add(index * 8)
            .readPointer()
            .readUtf8String() ?? "";
      } catch {}
      console.log(c_name, "\t", c_value);
    }
    return 0;
  },
  "int",
  ["pointer", "int", "pointer", "pointer"]
);

// CODE BELOW
let get_filename_from_sqlite3_handle = function (sqlite3_db) {
  // full of magic number
  let zFilename = "";
  try {
    let db_pointer = sqlite3_db.add(0x8 * 5).readPointer();
    let pBt = db_pointer.add(0x8).readPointer();
    let pBt2 = pBt.add(0x8).readPointer();
    let pPager = pBt2.add(0x0).readPointer();
    zFilename = pPager.add(208).readPointer().readCString();
  } catch (e) {}
  return zFilename;
};

setTimeout(function () {
  let base_addr = Module.findBaseAddress("libkernel.so");
  console.log("libkernel.so base address: " + base_addr);

  // sqlite3_exec -> sub_1CFB9C0
  let sqlite3_exec_addr = base_addr.add(0x1cfb9c0);
  console.log("sqlite3_exec_addr: " + sqlite3_exec_addr);

  let sqlite3_exec = new NativeFunction(sqlite3_exec_addr, "int", [
    "pointer",
    "pointer",
    "pointer",
    "int",
    "int",
  ]);

  let target_db_handle = null;
  let js_sqlite3_exec = function (sql) {
    if (target_db_handle == null) {
      return -1;
    }
    let sql_pointer = Memory.allocUtf8String(sql);
    return sqlite3_exec(target_db_handle, sql_pointer, xCallback, 0, 0);
  };

  // ATTACH BELOW
  Interceptor.attach(sqlite3_exec_addr, {
    onEnter: function (args) {
      // sqlite3*,const char*,sqlite3_callback,void*,char**
      let sqlite3_db = ptr(args[0]);
      let sql = Memory.readCString(args[1]);
      let callback_addr = ptr(args[2]);
      let callback_arg = ptr(args[3]);
      let errmsg = ptr(args[4]);
      let databasae_name = get_filename_from_sqlite3_handle(sqlite3_db);
      if (databasae_name == DATABASE_URI) {
        console.log("sqlite3_db: " + sqlite3_db);
        console.log("sql: " + sql);
        target_db_handle = sqlite3_db;
      }
    },
  });
  setTimeout(function () {
    let ret = js_sqlite3_exec(
      `ATTACH DATABASE '/storage/emulated/0/Download/plaintext.db' AS plaintext KEY '';SELECT sqlcipher_export('plaintext');DETACH DATABASE plaintext;`
    );
    console.log("js_sqlite3_exec ret: " + ret);
  }, 4000);
}, 1200);

@Young-Lord
Copy link
Member

Young-Lord commented Sep 29, 2023

完美。
运行时的日志输出,包含了 SQLCipher 参数
发个pr?我稍微修改一下后 merge 了

主要就是几个点:

  • ===/!== 而非 ==/!=
  • databasae_name 那里直接indexOf一下基本就行了,个人认为没必要用完整路径
  • hook libkernel 的时候可以去 hook dlopen,而非等待常数时间(ref,我也不太记得这个能不能用)

@Young-Lord
Copy link
Member

下一步的话可以看看insert是怎么实现的/能不能hook到 大概

@Young-Lord
Copy link
Member

以及,丢个安装包直链:
https://downv6.qq.com/qqweb/QQ_1/android_apk/Android_8.9.78_64.apk
来源

@bczhc
Copy link
Contributor

bczhc commented Apr 12, 2024

After the decryption, it's still file is not a database. Wrong password is the only reason I can think of.

@lavmaharjan
Copy link

image 🥳🥳🥳 way

hook nt_sqlite3_key_v2 拿 key

Remove the first 1024 characters of nt_msg.db

When opened, pbkdf iter=4000

How did you get the key for the database ?

@bczhc
Copy link
Contributor

bczhc commented Apr 12, 2024

How did you get the key for the database ?

Sorry I didn't dive into these reverse engineering things at all and can't answer you anything about this project or their way to get the key. I just brute forced it by memory dumping, and if you're interested, here's the link (in Chinese) FYI: https://gist.github.com/bczhc/c0f29920d4e9d0cc6d2c49f7f2fb3a78.

@lavmaharjan
Copy link

How did you get the key for the database ?

Sorry I didn't dive into these reverse engineering things at all and can't answer you anything about this project or their way to get the key. I just brute forced it by memory dumping, and if you're interested, here's the link (in Chinese) FYI: https://gist.github.com/bczhc/c0f29920d4e9d0cc6d2c49f7f2fb3a78.

Thank you. I will try the brute force method as well.

@Young-Lord
Copy link
Member

Even though I have kept similar settings as you have mentioned, I m not able to open the database (nt_msg.db). image

Try HMAC_SHA256 instead of HMAC_SHA1, this may work for some versions.

@lavmaharjan
Copy link

Try HMAC_SHA256 instead of HMAC_SHA1, this may work for some versions.

Even this didn't work. So, I doubt the key is not correct even though I followed the same steps.

@cxapython
Copy link

cxapython commented Apr 22, 2024

js_sqlite3_exec

呼,搞了一晚上终于搞好了。 之前attach会出现问题应该是奇怪的权限问题引起的,将导出地址设置为公共目录即可。 以下代码对应的安卓qqnt版本为8.9.78.12275。 不保证不会对聊天记录产生影响(

大佬js_sqlite3_execs前段时间还能正常返回0,现在为啥是14 了

@yllhwa
Copy link
Member

yllhwa commented Apr 22, 2024

js_sqlite3_exec

大佬js_sqlite3_execs前段时间还能正常返回0,现在为啥是14 了

可能新版的结构体变了,不过我现在手机没root,没法测试。

@cxapython
Copy link

cxapython commented Apr 22, 2024

可能新版的结构体变了,不过我现在手机没root,没法测试。

也不算是新版我用的v8.9.85.12850,前几天正常来着,然后前两天刷机重新安装这个版本就突然不行了,奇怪了。

@Young-Lord
Copy link
Member

大佬js_sqlite3_execs前段时间还能正常返回0,现在为啥是14 了

@cxapython 请注意裁剪你的回复内容,不要把代码块整个引用了

@cxapython

This comment was marked as resolved.

@Pevernow
Copy link

Pevernow commented Jun 2, 2024

问一下各位大佬,目前用qqwindbkey的方法导出了安卓qqnt数据库了,之后要怎样才能导出指定某个人的聊天记录?
有没有这类功能的仓库能推荐一下的。

@yllhwa
Copy link
Member

yllhwa commented Jun 2, 2024

怎样才能导出指定某个人的聊天记录?

好像没有直接跑的仓库?你改改这个代码应该能跑东西出来

@SnowFox4004
Copy link

大佬们,想问一下已经过期只有本地缓存的图片,在解密后的数据库内要如何获取到
还有想问一下那些 45001 之类的键名是怎么看出来的,好像上面的大佬发的protobuf里面获取senderUid的那个数 (47403) 在我这里没有对应的键值

@yllhwa
Copy link
Member

yllhwa commented Jul 25, 2024

还有想问一下那些 45001 之类的键名是怎么看出来的

猜的(

protobuf里面获取senderUid的那个数 (47403) 在我这里没有对应的键值

那个不是数据库的列,而是消息列二进制解码的id

@SnowFox4004
Copy link

SnowFox4004 commented Jul 25, 2024

怎样才能导出指定某个人的聊天记录?

看了一下解密出来的数据库有一个简单的思路或许可行

可以先按 这个仓库 的指引用 decrypt.py 解密目录下的所有数据库然后得到这些数据库

all_databases

nt_msg.db内可以根据上面大佬的代码提取聊天记录,打开观察一下感觉40020这一列是发送者的uid, 40021是私聊目标的uid

因此在代码里加一个条件进行筛选私聊目标应该就可以提取出特定人的聊天记录

msgdatabase

在提取出来的数据库的这个profile_info.db内应该存有关于好友列表的信息,在这个profile_info_v6的表里面

打开后1002列应该对应QQ号, 2000220009应该是分别对应好友昵称和你的备注,1000列对应的应该是uid 所以可以读取这个数据库找到特点人物的uid然后去nt_msg.db内提取对应的聊天记录
profile_info db

@shenjackyuanjie

This comment was marked as off-topic.

@LingeringAutumn
Copy link

请问现在有可以直接运行的仓库吗?我已经能解密数据库了,包括发送人接受人这些信息都能读取到,但是得到的聊天信息确实一串乱码,好像是probotuf格式的。请问聊天信息要怎么解密呢?

@SnowFox4004
Copy link

SnowFox4004 commented Aug 13, 2024

请问现在有可以直接运行的仓库吗?我已经能解密数据库了,包括发送人接受人这些信息都能读取到,但是得到的聊天信息确实一串乱码,好像是probotuf格式的。请问聊天信息要怎么解密呢?

#38 (comment)

这个大佬有给出一些protobuf的定义

@NicoOrz
Copy link

NicoOrz commented Aug 13, 2024

请问现在有可以直接运行的仓库吗?我已经能解密数据库了,包括发送人接受人这些信息都能读取到,但是得到的聊天信息确实一串乱码,好像是probotuf格式的。请问聊天信息要怎么解密呢?

#38 (comment)

这个大佬有给出一些protobuf的定义

这个应该怎么用呢)

@Young-Lord
Copy link
Member

Young-Lord commented Aug 16, 2024

这个应该怎么用呢)

@NicoOrz #38 (comment)
微调并运行此Python代码,比较简便的方式目前还没有。

@shenjackyuanjie

This comment was marked as off-topic.

@Young-Lord Young-Lord transferred this issue from QQBackup/QQ-History-Backup Aug 17, 2024
@alphagocc
Copy link
Contributor

https://t.me/rvalue_daily/4862
image
疑似 proto 定义流出
pb.zip

@alphagocc
Copy link
Contributor

好像是从 Harmony Next 版 QQ 解包的

@shenjackyuanjie
Copy link
Contributor

现在就看看能不能给pcqq也有点好处了()

@shenjackyuanjie
Copy link
Contributor

好像是从 Harmony Next 版 QQ 解包的

不用好像,就是的

@yllhwa
Copy link
Member

yllhwa commented Oct 15, 2024

看起来和数据库中的protobuf定义对不上,可能是通信的协议,不过对字段的确定可能有一些参考价值

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests