Android 手机QQ聊天记录导出(NTQQ),解密聊天数据库

发布于:2025-03-23 ⋅ 阅读:(40) ⋅ 点赞:(0)

先看结果:
在这里插入图片描述
方法源自Github大佬项目:地址在这
本教程导出的聊天记录QQ版本的NT版本的,即QQ8.9之后的版本,我的QQ版本是9.1.50.23520,QQ8.9之后的版本为NT架构,导出比较困难,如果是之前的版本,建议查看另一个大佬的项目:地址
以下教程仅是记录了我自己的实际过程,结合了自身的实际情况,过程与大佬的项目不完全一样

获取UID

项目的方法:

1、将data/user/0/com.tencent.mobileqq/databases/beacon_db_com.tencent.mobileqq文件作为纯文本文件打开,查找你的 QQ 号对应的uid,形式如 “home_uin”: “390251789”,“uid”:“u_mIicAReWrdCB-kST6TXH7A”,其中u_mIicAReWrdCB-kST6TXH7A即为uid。
2、在/data/user/0/com.tencent.mobileqq/files/uid/目录下,可见到文件名形如390251789###u_mIicAReWrdCB-kST6TXH7A的若干个文件,其中u_mIicAReWrdCB-kST6TXH7A即为uid。
3、若使用了QAuxiliary模块,可以通过打开[辅助功能]聊天和消息-[消息]转发消息点头像查看详细信息功能,合并转发由自己发送的消息,查看消息的senderUid属性获取,详见#32

我的方法

其实我就是用了上面的方法2,只是我在手机文件管理器查看的时候,并未发现uid目录,然后我想到可能要root才能看见这个目录,于是在电脑上用模拟器(自己电脑上本来就有模拟器-雷电模拟器)开启root模式,然后就看到了uid目录:
在这里插入图片描述在这里插入图片描述
打开到目录:data/data/com.tencent.mobileqq/files/uid
可以看到:
在这里插入图片描述

形如 u_mIicAReWrdCB-kST6TXH7A 的就是uid
对uid进行MD5加密即可得到QQ_UID_hash在线MD5加密,取小写的值
如:u_mIicAReWrdCB-kST6TXH7A进行 MD5加密得到 255c42fc0f4d295678e6ff0135fcf5dd

获取聊天记录文件

模拟器这里也是可以获取的,位置在一下路径:

/data/user/0/com.tencent.mobileqq/databases/nt_db/nt_qq_{QQ_path_hash}/nt_msg.db

但是这个路径的记录肯定是不全的,当然如过在模拟器QQ这里一直拉取聊天记录,还是会有相关记录的。
我的手机是Redmi K70 Ultra ,在手机备份与恢复中,把QQ进行了备份,得到了QQ的备份文件,我的是在MIUI文件夹下,backup→AllBackup下面的文件夹就是,里面有个QQ(com.tencent.mobileqq).bak的文件,这个就是了,把ta搞到电脑上,然后解压,得到apps文件夹,进入到apps\com.tencent.mobileqq\db\nt_db目录下,如
在这里插入图片描述
QQ_UID_hash进行如下运算即可得到QQ_path_hashQQ_path_hash = md5(md5(uid) + “nt_kernel”) = md5(“255c42fc0f4d295678e6ff0135fcf5ddnt_kernel”) = “b69bfb8e74137f4e4253d1af3e99493a”

则聊天记录路径

/data/user/0/com.tencent.mobileqq/databases/nt_db/nt_qq_b69bfb8e74137f4e4253d1af3e99493a/nt_msg.db

nt_msg.db就是我们要解密的数据库文件,建议备份一份,防止意外。
在这里插入图片描述

获取密钥

使用HxD或者其他二进制查看工具打开nt_msg.db文件,将文件头部跟随在QQ_NT DB后的可读字符串复制,形如6tPaJ9GP,记为rand

此时可以计算出数据库密钥key:key = md5(QQ_UID_hash + rand) = md5(“255c42fc0f4d295678e6ff0135fcf5dd6tPaJ9GP”) =+“71c0dfcef3b5ceae7c4a1c68ca662f4a”。

则数据库密钥为71c0dfcef3b5ceae7c4a1c68ca662f4a。

另外,文件头部还可能含有cipher_hmac_algorithm的值(如HMAC_SHA1)等与解密相关的信息,可被解析为Protobuf数据,详见#29 (comment)

HxD下载 往下滑动就能看到了
在这里插入图片描述
下载后,打开HxD,点击右上角 文件→打开,选择nt_msg.db文件,即可看到如下内容:
在这里插入图片描述
其中,8MuONS9V就是randHMAC_SHA1就是下面等会就需要用到的cipher_hmac_algorithm的值。
!!!再次提醒,记得备份nt_msg.db文件!!!

移除无关文件头

首先,将nt_msg.db文件删除前1024字节,这可以通过以下方式完成:

使用二进制编辑器:Android 下的 MT 管理器(需要付费)、Windows 下的 HxD 等软件均可使用,细节从略。

使用tail命令(仅 Linux):tail -c +1025 nt_msg.db > nt_msg.clean.db

使用 Python:python -c “open(‘nt_msg.clean.db’,‘wb’).write(open(‘nt_msg.db’,‘rb’).read()[1024:])”

完成后,得到nt_msg.clean.db文件。
使用Hxd删除也行的,删除到有数据这里就行,然后另存为nt_msg.clean.db
在这里插入图片描述

打开数据库

下载DB Browser for SQLiteSQLiteStudio, 其实下载其中一个就够了,我下载两个是因为SQLiteStudio能看到聊天记录的内容,DB Browser for SQLite只能看到字节
在这里插入图片描述
在这里插入图片描述
下面使用SQLiteStudio打开数据库

PRAGMA key = 'pass';    -- pass 替换为之前得到的密码key(32字节字符串)
PRAGMA cipher_page_size = 4096;
PRAGMA kdf_iter = 4000; -- 非默认值 256000
PRAGMA cipher_hmac_algorithm = HMAC_SHA1; -- 非默认值(见上文)
PRAGMA cipher_default_kdf_algorithm = PBKDF2_HMAC_SHA512;
PRAGMA cipher = 'aes-256-cbc';

将上面的内容填入下面的加密算法配置里,pass记得改成上面获取到的看key,打开刚才另存的nt_msg.clean.db文件,我图片里的是nt_msg.db是因为我重命名了
在这里插入图片描述
然后就打开数据库成功了,其中c2c_msg_table就是聊天记录内容表,点击数据即可查看
在这里插入图片描述
其中,40020字段就是发送人的uid,40021字段就是跟我们聊天的人,40080字段就是聊天的内容了
在这里插入图片描述

聊天内容导出

目前已知消息格式为protobuf,相关解密代码可以参考提取QQ NT数据库 group_msg_table 中的纯文本这份 Python 代码这份 protobuf 定义,完整实现暂无,欢迎贡献。
附上大佬的python代码:

import sqlite3
import blackboxprotobuf

conn = sqlite3.connect('plaintext.db')
c = conn.cursor()
print ("数据库打开成功")

def get_message_from_single(message):
    # print(message)
    if isinstance(message, list):
        return [get_message_from_single(m) for m in message]
    try:
        message_id = message.get("45001")
        message_type = message.get("45002")
        if message_type == 1:
            # 普通文本消息
            message_content = message.get("45101").decode("utf-8")
        elif message_type == 2:
            # 图片消息
            local_name = message.get("45402")  # ?
            if message.get("45804"):
                picture_url = "https://c2cpicdw.qpic.cn"+ message.get("45804").decode("utf-8") # 45802, 45803, 45804 区别?(可能是清晰度)
            else:
                picture_url = ""
            message_content = f"[图片消息 {picture_url}]"
        elif message_type == 3:
            # 文件消息
            file_name = message.get("45402")
            message_content = f"[文件消息 {file_name}]"
        elif message_type == 6:
            # 表情消息
            message_content = "[表情消息]" # TODO
        elif message_type == 10:
            # 应用消息
            # message_content = message.get("47901")
            message_content = "[应用消息]"
        else:
            message_content = "[未知消息类型]"
        if message_content == "[未知消息类型]":
            # print(message)
            pass
        if message_content == None:
            message_content = ""
        return message_content
    except Exception as e:
        print(e)
        return ""

def get_message_from_raw(raw_message):
    (messages, typedef) = blackboxprotobuf.decode_message(raw_message)
    if not isinstance(messages, list):
        messages = [messages]
    results = []
    for message in messages:
        message = message.get("40800")
        results.append(get_message_from_single(message))
    return results
        

cursor = c.execute("SELECT * from c2c_msg_table")
for row in cursor:
    data = row[17]
    print(get_message_from_raw(data))

conn.close()

protobuf定义

syntax = "proto3";

message Message { repeated SingleMessage messages = 40800; }

message SingleMessage {
  uint64 messageId = 45001;
  uint32 messageType = 45002;
  // 1:文字,2:图片,3:文件,6:表情,7:回复,
  // 8:提示消息(中间灰色),10:应用消息
  // 21:电话
  // 26:动态消息

  // 回复消息
  string senderId = 40020;
  string receiverId = 40021;

  // 文字消息
  string messageText = 45101;

  // 文件消息
  string fileName = 45402;
  uint64 fileSize = 45405;

  uint64 sendTimestampFile = 45505; // ?

  // 图片消息
  string imageUrlLow = 45802;
  string imageUrlHigh = 45803;
  string imageUrlOrigin = 45804;
  string imageText = 45815;

  uint32 senderUid = 47403;
  uint32 sendTimestamp = 47404;
  uint32 receiverUid = 47411;
  SingleMessage replyMessage = 47423;

  // 表情消息
  // 1: QQ 系统表情,2: emoji 表情
  // https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html
  uint32 emojiId = 47601;
  string emojiText = 47602;

  // 应用消息
  string applicationMessage = 47901;

  // 语音消息
  string callStatusText = 48153;
  string callText = 48157;

  // 动态消息
  FeedMessage feedTitle = 48175;
  FeedMessage feedContent = 48176;

  string feedUrl = 48180;
  string feedLogoUrl = 48181;
  uint32 feedPublisherUid = 48182;

  string feedJumpInfo = 48183;
  string feedPublisherId = 48188;

  // 提示消息
  string noticeInfo = 48214;
  string noticeInfo2 = 48271; // ?
}

message FeedMessage { string text = 48178; }

网站公告

今日签到

点亮在社区的每一天
去签到