QQ本地缓存机制初步探寻

起因是在电脑上我想将一个QQ文件存放到其他文件夹下,而在QQ缓存文件夹下(...\Tencent Files\电脑登录的qq号\nt_qq\nt_data\,右键选择打开文件夹就可以快速找到对应文件的存放位置)对一个视频文件进行了重命名,在QQ中想要再预览同一个视频的时候发现又需要重新下载(即下图中最左与最右的视频是相同的)。

所以我又重命名了一遍(是的,相当于再次预览后有了3个相同的视频):

可以看到同一个视频在qq缓存时始终拥有一个固定的命名。而这个文件名07274e2fe9990417a1f599611cd36025,而这个命名是怎么产生的呢?在windows的cmd中输入以下指令(测试是不是MD5方式产生的):

复制代码
certutil -hashfile  "D:\...\nt_qq\nt_data\Video\2026-06\Ori\07274e2fe9990417a1f599611cd36025.mp4" MD5

得到的值是05fce820355ed39a43e61ffbef3d7f40,不匹配。

所以让AI去尝试其他常见的哈希方式,结果都不匹配:

测试项目 计算结果 是否匹配文件名
文件内容 MD5 05fce820355ed39a43e61ffbef3d7f40 ❌
文件内容 SHA1 51e24aa6f6e6e5c6ac533e03694edc8865be302b ❌
文件内容 SHA256 bdc4cd025276a4407b6d78223e4d0ab812e3b5ee... ❌
文件内容 CRC32 9513aeda ❌
文件前 1KB / 1MB / 末尾 N 字节 MD5 多种结果 ❌
文件路径 / 文件名 / 文件大小 的 MD5 多种结果 ❌
嵌套哈希 (MD5 of MD5 等) 多种结果 ❌
标准 UUID (v1/v4) version 位为 0,不符合标准 ❌

说明很有可能QQ不是在本地根据视频内容进行的计算(因为哈希会出现重复的情况),而可能是对原始下载 URL 进行哈希得到的或者是QQ 服务器直接下发的 file_id / file_uuid。不过事实并非如此。

查看缓存目录,以一个视频为例:

nt_qq/nt_data/Video/2026-06/Ori/07274e2fe9990417a1f599611cd36025.mp4 nt_qq/nt_data/Video/2026-06/Thumb/07274e2fe9990417a1f599611cd36025_0.png

可以看到视频和缩略图共享同一个 ID,说明这是 QQ NT 的标准缓存结构。

接着对同一缓存目录下的其他十六进制命名视频做 MD5 校验,发现并非所有文件都不匹配:

文件名 内容 MD5 是否匹配
7a4e67066cff62b0d634bb195c4c1b7a.mp4 7a4e67066cff62b0d634bb195c4c1b7a ✅ 匹配
35162df365e45b0b63b604db88540752.mp4 35162df365e45b0b63b604db88540752 ✅ 匹配
c34a6824666446818ff0e6524b08b239.mp4 577353b33ca4db9bc0f86d0df9f97278 ❌ 不匹配
fb48745972b2bb0f795f7d2382172996.mp4 5329bf3eeea08efeac87d66ed659965a ❌ 不匹配

所以推测某些视频(可能是本地直发文件)使用内容 MD5 命名;另一些视频在发送或者缓存过程中进行了压缩,导致前后的MD5发生了变化。

在 nt_qq/nt_db/ 下找到所有数据库文件,包括:

nt_msg.db、rich_media.db、files_in_chat.db、group_info.db ...

尝试用 sqlite3 直接打开,全部报错 Error: file is not a database。用 Python 读取文件头确认:Header: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 ASCII: SQLite format 3

表面是标准 SQLite 头,但后续页面是密文------确认是 SQLCipher 4 加密,且 QQ NT 在标准头前额外加了 1024 字节自定义头部。

复制代码
1008-1023:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   (16 字节全零)
1024-1039:  5f d9 ca 34 61 b4 8d 82 3f 69 8c 59 f4 8a 33 40   (加密数据开始)

既然数据库加密,必须获取密钥。由于无法下载到现成的 PowerShell 脚本,自行编写了 find_key_from_memory.py:

  1. 枚举所有 QQ.exe 进程 PID
  2. 用 Windows API (OpenProcess → VirtualQueryEx → ReadProcessMemory) 遍历内存
  3. 在内存中搜索 nt_msg.db 和 对应的QQ 号 作为线索
  4. 在线索附近提取 16 字节可打印 ASCII 字符串作为候选密钥
  5. 对每个候选密钥,尝试连接剥离头部后的数据库并执行 SELECT count(*) FROM sqlite_master;

扫描结果:

  • 扫描 7 个 QQ 进程,共 3.8 GB 内存
  • 收集到 40,001 个候选密钥,验证到第 1,500 个左右时命中,成功提取密钥

接着按剥离 1024 字节头部 → 生成 → 逐表导出 → 生成明文数据库 的过程进行

在 c2c_msg_table 和 group_msg_table 的 40800 列(Protobuf BLOB)中搜索字节串 b"07274e2fe9990417a1f599611cd36025"。在 c2c_msg_table 中找到 1 条 匹配记录,group_msg_table 中未找到。

复制代码
$07274e2fe9990417a1f599611cd36025.mp4
$07274E2FE9990417A1F599611CD36025.png
fEhRag8Q-jb6FPILieEkWWDWf-8xdUxic2Y4dIIULKJGAuq6X-ZQDMgRwcm9kUID1JFoQDn4OJuj0FqmYXaS4jYYW3XoCsK2CAQJuag
dEhQ4k-yIPDEk7SC5169Krv88fxHQLRjNhgMghgso7-O8rpf5lAMyBHByb2RQgPUkWhAlwP3hH60xpVJhi9ABSKO-egLrWYIBAm5q

是 Protobuf 二进制,需要解析才能读出结构化字段。将Protobuf 解析为结构化 JSON,在messages 表中搜索目标字符串,得到如下结果:

复制代码
{
  "type": "video",
  "filename": "07274e2fe9990417a1f599611cd36025.mp4",
  "filesize": 61058204,
  "md5_hex": "07274e2fe9990417a1f599611cd36025",
  "duration": 49997,
  "cdn_url": null,
  "local_path": null
}

结合本地文件比对大小即可得到:

维度 消息记录中的原始文件 本地缓存文件
md5_hex 07274e2fe9990417a1f599611cd36025 ---
filesize 61,058,204 bytes (58.2 MB) 51,226,940 bytes (48.8 MB)
内容 MD5 --- 05fce820355ed39a43e61ffbef3d7f40
文件名 07274e2fe9990417a1f599611cd36025.mp4 07274e2fe9990417a1f599611cd36025.mp4

说明文件名是发送方原始视频文件的 MD5**。** QQ 用这个 MD5 作为本地缓存文件名。但 在本地存储时对视频进行了压缩/转码,导致文件变小、内容 MD5 改变,因此本地的 .mp4 文件内容 MD5 与文件名不一致。

之所以改名后会重新下载,是因为和浏览器缓存类似,QQ 识别本地文件是否已存在,通常靠的是文件路径和文件名,而不是文件内容的 MD5 哈希值。文件名一变,索引就失效了。虽然视频内容完全没变(MD5 值相同),但 QQ 并不会在打开前先做完整的内容校验。它只查"之前有没有下载过这个名称的文件",查不到就重新拉取。

参考资料

https://qqbackup.github.io/QQDecrypt/

相关推荐
蜕变的土豆9 小时前
工业监控RTSP取流-实践指南
c++·视频
阿标在干嘛9 小时前
缓存架构的“场景适配“:我们为什么在部分服务中采用了本地缓存?
缓存·架构
需要8269 小时前
缓存与数据库一致性:延迟双删与订阅 binlog 的取舍
数据库·缓存
VidDown9 小时前
自动给视频挑一张能看的封面:帧评分、构图与批量生成
python·ffmpeg·音视频·视频
CV工程师丁Sir12 小时前
ArkWeb 手记 04|DevTools 调试与缓存清理
java·spring·缓存·harmonyos
志尊宝20 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
禾小西21 小时前
Redis:从两大维度和三大主线建立知识体系
数据库·redis·缓存
进击的横打1 天前
【人工智能】缓存命中率:从原理到业务场景的全面解读
人工智能·缓存
change_fate1 天前
升级依赖后,vite会自动重建缓存吗
缓存
饺子大魔王的男人1 天前
LitePan多网盘影音中枢:WebDAV挂载、STRM生成与缓存管理
缓存·dubbo