前言
本文介绍微信本地数据库的导出和部分数据表字段分析。
起因是看到一篇文章,博主用微信聊天记录和博客文章,配合ChatGPT克隆了一个数字版自己。俺没这么高级,单纯想看看某个闲聊群谁是龙王。
废话不多说,下面进行实操
数据库解密
环境如下:win10 + pc版微信(3.9.6.33)
数据取证
微信数据库进行了加密,需要用对客户端进行取证得到解密的秘钥。解密工具用 SharpWxDump ,需要自行编译,得到SharpWxDump.exe
接下来正式取证,首先登录微信,然后在命令提示符(CMD)中执行SharpWxDump.exe即可得到信息,WeChatKey就是秘钥,记得复制下等会要用
数据库文件
微信本地数据库用的是SQLite,文件存放在 $WeChat Files/wxid_xxxxxxx\Msg\Multi
中。
$WeChat Files
:表示微信的文件存放路径,在微信 → 设置 → 文件管理中可以查看
wxid_xxxxxxx
:当前电脑登录过多少个微信,就有多少个wxid_xxxxxxx文件夹,可以进入wxid_xxxxxxx文件夹,查看第一个文件夹account_xxxx确认是哪个微信,格式是 account_微信号
在 Multi 文件夹中有很多文件,我们需要用到的数据库文件格式为 MSGx.db ,这里的表示0123,例如 MSG0.db MSG1.db等。接下来的操作,我们需要关闭微信,不然复制的文件的时候可能会有问题。符合条件的我们都复制到新的文件夹,以便后续使用。
解密脚本
解密数据库文件需要用到WeChatKey + python解密脚本,以下是脚本代码
# jiemi.py
from Crypto.Cipher import AES
import hashlib, hmac, ctypes, sys, getopt
SQLITE_FILE_HEADER = bytes('SQLite format 3', encoding='ASCII') + bytes(1)
IV_SIZE = 16
HMAC_SHA1_SIZE = 20
KEY_SIZE = 32
DEFAULT_PAGESIZE = 4096
DEFAULT_ITER = 64000
opts, args = getopt.getopt(sys.argv[1:], 'hk:d:')
input_pass = ''
input_dir = ''
for op, value in opts:
if op == '-k':
input_pass = value
else:
if op == '-d':
input_dir = value
password = bytes.fromhex(input_pass.replace(' ', ''))
with open(input_dir, 'rb') as (f):
blist = f.read()
print(len(blist))
salt = blist[:16]
key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE)
first = blist[16:DEFAULT_PAGESIZE]
mac_salt = bytes([x ^ 58 for x in salt])
mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE)
hash_mac = hmac.new(mac_key, digestmod='sha1')
hash_mac.update(first[:-32])
hash_mac.update(bytes(ctypes.c_int(1)))
if hash_mac.digest() == first[-32:-12]:
print('Decryption Success')
else:
print('Password Error')
blist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
with open(input_dir, 'wb') as (f):
f.write(SQLITE_FILE_HEADER)
t = AES.new(key, AES.MODE_CBC, first[-48:-32])
f.write(t.decrypt(first[:-48]))
f.write(first[-48:])
for i in blist:
t = AES.new(key, AES.MODE_CBC, i[-48:-32])
f.write(t.decrypt(i[:-48]))
f.write(i[-48:])
使用方法
python3 .\jiemi.py -k WeChatKey -d .\MSGx.db
jiemi.py:就是python解密脚本
WeChatKey:数据取证得到
MSGx.db:上一步得到的数据库文件(多个文件就执行多次)
数据库分析
用数据库工具以SQLite方式打开MSG0.db,可以看到4个表,此处我们关注 MSG 表,这是保存聊天记录的表。
表字段很多,我们重点关注其中几个字段
- Type:消息类型
- SubType:消息类型子分类
- IsSender:是否是自己发出的消息(1是 0否)
- CreateTime:消息创建时间的秒级时间戳
- StrTalker:聊天对象标识(如果是群里,格式就是xxx@chatroom)
- StrContent:字符串格式的数据
- CompressContent:压缩的数据(引用回复的消息会放在这里,StrContent为空)
详细的分析可以看这篇文章:微信PC端各个数据库文件结构与功能简述
如果只需要看文本消息,则可以如下搜索
SELECT * FROM MSG WHERE Type=1 OR (Type=49 AND SubType=57)
Type=1 :表示普通文本消息
Type=49 AND SubType=57 :带有引用的文本消息(这种类型下 StrContent 为空,发送和引用的内容均在 CompressContent 中)
CompressContent 的内容是被压缩过的,压缩算法用的是lz4
,解压出来的内容是XML格式。以下是解压代码
import lz4.block as lb
unzipStr = lb.decompress(CompressContent, uncompressed_size=0x10004)
text = unzipStr.decode('utf-8')
print(text )
代码中CompressContent就是压缩后的内容,text是解压后的。
还有一个最重要的问题,如何确认哪条消息是谁发的。
一对一私聊很简单,通过StrTalker确认聊天对象,IsSender判断是不是自己发的消息即可
在群聊中,通过StrTalker只能锁定群里消息,至于是哪个群友发的消息,则需要通过BytesExtra字段中的内容判断。而开心的是,这个字段也是BLOB类型,我也找不到格式化的方法。
我判断是谁发的消息只能对BytesExtra进行iso-8859-1
解码,然后判断BytesExtra是否包含了wxid_xxxx这样的方式来判断。。。
感谢评论区的热心大神 景 , BytesExtra 是 protobuf 格式的数据,解析可以参考这个 无源protobuf二进制流反序列化学习
总结
那么问题来了,谁是龙王。。。
参考文章:
大佬 BytesExtra字段的格式化方法有了解到嘛,我也好奇,感谢感谢呀
BytesExtra 是proto,用proto.exe解析出来就能看到是哪一个群友发的消息了
学到了,非常感谢
看来我搜索的正是时候,一搜就看到了BytesExtra的压缩方式