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

先看结果:

方法源自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代码:

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定义

go 复制代码
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; }
相关推荐
硬件学长森哥26 分钟前
Android音视频多媒体开源库基础大全
android·计算机视觉·开源·音视频
开开心心就好33 分钟前
免费提供多样风格手机壁纸及自动更换功能的软件
android·python·网络协议·tcp/ip·macos·智能手机·pdf
他人是一面镜子,保持谦虚的态度2 小时前
零、ubuntu20.04 安装 anaconda
开发语言·python
数学人学c语言2 小时前
通义千问模型微调——swift框架
python·深度学习·swift
cfjybgkmf2 小时前
Python列表2
开发语言·python
范哥来了2 小时前
python 游戏开发cocos2d库安装与使用
开发语言·python·cocos2d
怀璧其罪2 小时前
安装 OpenSSL 1.1.1 的完整脚本适用于 Ubuntu 22.04 系统
linux·服务器·数据库
酷酷的崽7984 小时前
如何在SQL中高效使用聚合函数、日期函数和字符串函数:实用技巧与案例解析
服务器·数据库·sql
只做开心事4 小时前
MySQL索引
数据库·mysql
字节王德发5 小时前
Django CSRF验证失败请求为什么会中断?
python·django·csrf