Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5
1519 字
8 分钟
QQ 聊天记录数据库解密与分析
2025-07-05
无标签
统计加载中...

最近几天折腾了很久关于 QQ 和微信聊天记录数据分析的东西,小有成果。这是连鸽了四天训练赛题解的理由吗[?]

微信的聊天记录已经有相对比较成熟的办法了,但是原理不太好找。QQ 这边是有比较成熟的文档,但是现成的项目不太好找。相比之下 QQ 是折腾最久的一个,折腾完之后挺有成就感的,写一篇博客记录一下。

数据库解密#

最新的 NTQQ 支持从手机导入聊天记录了,但是可惜不能像老的 QQ 一样直接批量导出聊天记录为文本了,而且我尝试过新 QQ 的聊天记录没办法导入到老 QQ,于是想拿到聊天记录就只能尝试破解本地的数据库密码了……

关于解密的操作,我参考了 QQDecrypt(开源协议)

文档里面对解密的流程讲的很细致(我知识有限,只能认识到是给 QQ 加了一个断点,在一个函数下面查询数据库密码的变量),跟着做就可以拿到数据库密码。

下面的部分是我对自己折腾的记录的存档,系统是 macOS 15.5 24F74 arm64,建议跟着原文档做,做完之后跳到数据导出

分析#

复制 wrapper.node

cd ~/Projects
mkdir QQDecrypt
cd QQDecrypt
cp /Applications/QQ.app/Contents/Resources/app/wrapper.node .

用 hopper 打开,找到调用密码的函数的位置

记下来 0000000002e25758

调试#

运行 QQ,找进程的 pid

ps aux | grep QQ
...
username 40225 2.1 1.4 1623006720 240480 ?? S 11:45PM 0:00.83 /Applications/QQ.app/Contents/MacOS/QQ
...

记下来 40225

lldb -p 40225
(lldb) process attach --pid 40225
Process 40225 stopped
* thread #1, name = 'CrBrowserMain', queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00000001832e0c34 libsystem_kernel.dylib`mach_msg2_trap + 8
libsystem_kernel.dylib`mach_msg2_trap:
-> 0x1832e0c34 <+8>: ret
libsystem_kernel.dylib`macx_swapon:
0x1832e0c38 <+0>: mov x16, #-0x30 ; =-48
0x1832e0c3c <+4>: svc #0x80
0x1832e0c40 <+8>: ret
Target 0: (QQ) stopped.
Executable binary set to "/Applications/QQ.app/Contents/MacOS/QQ".
Architecture set to: arm64-apple-macosx-.

找到 wrapper.node 加载的位置

(lldb) image list -o -f | grep /Applications/QQ.app/Contents/Resources/app/wrapper.node
[ 0] 0x0000000128800000 /Applications/QQ.app/Contents/Resources/app/wrapper.node

算出来函数的位置

(lldb) expr 0x0000000128800000 + 0x0000000002e25758
(long) $1 = 5022832472

打上断点

(lldb) br s -a 5022832472
Breakpoint 1: where = wrapper.node`___lldb_unnamed_symbol507012, address = 0x000000012b625758
(lldb) c
Process 40225 resuming

运行 QQ,触发断点,根据地址查 x2 变量,解析 16 个字符

Process 40225 stopped
* thread #38, name = 'thread_general_fixed_2', stop reason = breakpoint 1.1
frame #0: 0x000000012b625758 wrapper.node`___lldb_unnamed_symbol507012
wrapper.node`___lldb_unnamed_symbol507012:
-> 0x12b625758 <+0>: sub sp, sp, #0x40
0x12b62575c <+4>: stp x22, x21, [sp, #0x10]
0x12b625760 <+8>: stp x20, x19, [sp, #0x20]
0x12b625764 <+12>: stp x29, x30, [sp, #0x30]
Target 0: (QQ) stopped.
(lldb) register read x2
x2 = 0x0000013c03435140
(lldb) memory read --format c --count 16 --size 1 x2 = 0x0000013c03435140
error: memory read takes a start address expression with an optional end address expression.
warning: Expressions should be quoted if they contain spaces or other special characters.
(lldb) memory read --format c --count 16 --size 1 0x0000013c03435140
0x13c03435140: Iq<B3WXjc:tgS=#3
(lldb) exit
Quitting LLDB will detach from one or more processes. Do you really want to proceed: [Y/n] Y

我的密码是 Iq<B3WXjc:tgS=#3,记下来

解密#

把数据库复制一份过来

cp -r ~/Library/Containers/com.tencent.qq/Data/Library/Application\ Support/QQ/nt_qq_168d93e33909444553576552a300ebec/nt_db .

移除文件头,复制一份

cat nt_db/nt_msg.db | tail -c +1025 > test.clean.db

用 DB Browser for SQLite 按照这个配置就可以打开了。

数据导出#

这里以私聊的数据为例,参考这个文档,把 c2c_msg_table 导出为 csv.

数据解析#

表格转载自 https://qq.sbcnm.top/view/db_file_analysis/nt_msg.db.html#c2c-msg-table

列名类型含义说明
40030int私聊对象 QQ 号对方 QQ 号(无论是对方还是自己发送的消息)
40033int发送者 QQ 号发送者的 QQ 号
40050int时间时间戳(单位为秒)
40058int日期当日 0 时整的时间戳格式
40093str消息发送者QQ 昵称或是备注名
40800bytes消息内容protobuf 格式

建议用 vscode 创建一个 ipynb 操作,个人感觉体验很好。

先用 pandas 读取 csv

import pandas as pd
df = pd.read_csv('c2c_msg_table.csv')

筛选私聊对象#

这个操作很好实现,只需要筛选 40030 列

QQ = <VICTIM_QQ_NUMBER> # 替换为目标QQ号
data = df.loc[df['40030'] == QQ].copy()
data['user'] = (data['40033'] != QQ)

这样就把你和 ta 的聊天记录单独复制成了一个新表,并且标记了消息的发送者

时间#

时间在 40050 列,是 int64 格式的时间戳,可以用 python 的 datetime 转成 datetime 对象。

from datetime import datetime
data['time'] = data['40050'].apply(datetime.fromtimestamp)

当然如果有需要,也可以用 strftime 转成字符串。

# 2024年06月11日 10:45:38
data['stime'] = data['40050'].apply(
lambda x: datetime.fromtimestamp(x).strftime('%Y年%m月%d日 %H:%M:%S'))

消息内容#

消息内容在 40080 列,是一个 protobuf 格式的二进制串

如果不想看详细过程,可以直接使用下方的批处理函数#批处理

原理#

先取第一个试一下

data['40800'].head()

我这里第一条是 gvYTMMj8Ff3lkLWWoO+zZtD8FQHqghYY77yI55yL5p2l5piv5Y+I552h5LqG77yJ8IIWAA==,导出之后这是一个 base64 的串,可以用 python decode 一下。

写一个 export.py.

import base64
raw_bytes = base64.b64decode('gvYTMMj8Ff3lkLWWoO+zZtD8FQHqghYY77yI55yL5p2l5piv5Y+I552h5LqG77yJ8IIWAA==')
with open("data.bin", "wb") as f:
f.write(raw_bytes)

开命令行 (protoc 是 anaconda 自带的)

python export.py
protoc --decode_raw < data.bin
40800 {
45001: 7379074328184500989
45002: 1
45101: "\357\274\210\347\234\213\346\235\245\346\230\257\345\217\210\347\235\241\344\272\206\357\274\211"
45102: 0
}

根据文档下面的表格 45101 里面的应该是消息内容,验证一下

python
Python 3.12.7 | packaged by Anaconda, Inc. | (main, Oct 4 2024, 08:22:19) [Clang 14.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> b'\357\274\210\347\234\213\346\235\245\346\230\257\345\217\210\347\235\241\344\272\206\357\274\211'.decode('utf-8')
'(看来是又睡了)'
>>> exit()

事实证明猜测正确,这条消息的内容是 “(看来是又睡了)”,于是可以让 GPT 写一个批处理脚本

批处理#

import base64
import subprocess
import tempfile
import re
import pandas as pd
def decode_protobuf_base64(base64_str):
if pd.isna(base64_str):
return None
try:
# Step 1: base64 解码成 Protobuf 二进制
raw_bytes = base64.b64decode(base64_str)
# Step 2: 写入临时文件供 protoc 使用
with tempfile.NamedTemporaryFile(delete=True) as tmp:
tmp.write(raw_bytes)
tmp.flush()
# 调用 protoc --decode_raw
result = subprocess.run(
['protoc', '--decode_raw'],
input=raw_bytes,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if result.returncode != 0:
print(f"protoc 错误:{result.stderr.decode()}")
return None
decoded_text = result.stdout.decode('utf-8')
# Step 3: 用正则提取 45101 字段
match = re.search(r'45101: "(.*?)"', decoded_text)
if not match:
return None
raw_str = match.group(1)
# Step 4: 将 \xxx 格式解码成中文
decoded_bytes = raw_str.encode('utf-8').decode('unicode_escape').encode('latin1')
final_str = decoded_bytes.decode('utf-8')
return final_str
except Exception as e:
print(f"解码失败: {e}")
return None

调用的时候可以加个进度条

from tqdm import tqdm
tqdm.pandas()
data['content'] = data['40800'].progress_apply(decode_protobuf_base64)

题外话: GPT 这一顿操作很难不让人想起来 Python 大作业调 subprocess 输入输出和报错的日子

到这里数据就基本上处理完了,接下来就可以做数据分析了。

最后把需要的数据切出来存个 csv。

data_clean = data[['time', 'content', 'user']]
data_clean = data_clean[data_clean['content'].notna()]
data_clean.reset_index(inplace=True, drop=True)
data_clean.to_csv('qq.csv')
QQ 聊天记录数据库解密与分析
https://starlab.top/posts/qq-data-analysis/
作者
Star
发布于
2025-07-05
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时