使用电脑微信上的“菜单-迁移与备份-备份与恢复”功能可以将手机微信上的聊天记录存储到电脑,以后也可以恢复到手机。
如果可以将这些备份的聊天记录直接提取出来就可以随心所欲地整理、保存了。
本文使用的电脑微信(64位)、安卓微信的版本如下:
观察电脑微信的行为电脑微信的备份功能会将手机聊天记录备份到形如 C:\Users\XXX\Documents\WeChat Files\YYY\BackupFiles 目录下,其内每一个文件夹对应一个备份,文件有 : - Backup.db (似乎是SQLite数据库,但打不开可能加密了)
- BAK_0_TEXT (看文件名似乎是消息文本 )
- BAK_0_MEDIA (看文件名似乎是媒体文件)
进入“查看备份文件”,发现备份的详细信息,如机型等信息可以展示出来,猜测在加载备份文件列表时,微信就已经进行了数据库解密读取的操作。可以通过一些手段确认,微信在加载备份列表时,确实对 Backup.db有读取的动作。另外解密电脑微信的主数据库,其中也没有找到任何与我的机型相关的记录或条目。根据这两点,可以确认微信在加载备份列表时确实解密并读取了备份文件,也意味着密钥一定会加载到内存中,也就能够通过x64dbg等动态调试工具提取出来。 电脑微信可以全量备份但部分恢复。例如,可以从时间跨度很长的备份中单独摘一小段时间内的聊天记录恢复到手机,也可以从备份的所有会话中单独摘几个会话恢复到手机。 动态调试电脑微信 抓取Backup.db数据库密钥我是64位的Windows 微信,所以就用x64dbg附加到 WeChat.exe 啦。 首先,在x64dbg中寻找与密钥处理逻辑相关的字符串。进入模块 wechatwin.dll,然后列出模块中的所有字符串,可以搜索“key”这个词猜一猜。我们发现了"dbKey can't be NULL". 有点意思。 跳转到对应的汇编,这可能是一个判断语句的分支。从这条汇编向上走到其它跳转指令的紧后面,也就是这个判断分支的开头。然后通过“查找引用”功能,找到跳转到分支起始地址的两条跳转指令。 断点打在这两个je指令上。然后在微信中点击“管理备份列表”。断点命中了,RDI值为数值 0x20 即 十进制32,寄存器 R13 指向的内存存储了一段字节,这段字节似乎也以32字节长度为界而结束了。 按正常的算法解密Backup.db。用Python写的很方便使用,我稍作了修改。AES解密用的是 pycryptodome (pip install pycryptodome)。 - import hmac
- import ctypes
- import hashlib
- from Crypto.Cipher import AES
- defdecrypt_msg(path, password):
- KEY_SIZE = 32
- DEFAULT_ITER = 64000
- DEFAULT_PAGESIZE = 4096# 4048数据 + 16IV + 20 HMAC + 12
- SQLITE_FILE_HEADER = bytes("SQLite format 3", encoding="ASCII") + bytes(1) # SQLite 文件头
- withopen(path, "rb") as f:
- blist = f.read()
- salt = blist[:16] # 前16字节为盐
- key = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE) # 获得Key
- page1 = blist[16:DEFAULT_PAGESIZE] # 丢掉salt
- mac_salt = bytes([x ^ 0x3afor 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(page1[:-32])
- hash_mac.update(bytes(ctypes.c_int(1)))
- if hash_mac.digest() != page1[-32:-12]:
- raise RuntimeError("Wrong Password")
- pages = [blist[i:i+DEFAULT_PAGESIZE] for i inrange(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
- pages.insert(0, page1) # 把第一页补上
- withopen(f"{path}.dec.db", "wb") as f:
- f.write(SQLITE_FILE_HEADER) # 写入文件头
- for i in pages:
- t = AES.new(key, AES.MODE_CBC, i[-48:-32])
- f.write(t.decrypt(i[:-48]))
- f.write(i[-48:])
复制代码大致看看,发现Backup.db中没有存储真正的聊天内容。只有表Session存储了对话和群聊的名称。 数据库表MsgSegments中有几列,看字段的命名,好像是文件名称、偏移和长度,其中文件名称字段出现了 BAK_0_TEXT。可以猜测,真正的的聊天记录内容还是存储在配套的BAK_0_TEXT文件中,而这个文件由许多小段组合而来,每一小段的文件位置、偏移和长度都记录在了MsgSegments表对应的行内。类似的,通过MsgMedia和MsgFileSegments这两个表,也猜测图片视频文件也以类似的方式存储在BAK_0_MEDIA、BAK_1_MEDIA等文件中。 但是,如果我们直接把BAK_0_TEXT的小段摘出来,是看不到什么有意义的字符的,别说汉字了,连ASCII字符看着也不像是正常的聊天消息内容。肯定是又是加密过。 电脑微信可以从完整的备份数据中单独摘出部分消息恢复到手机,所以估计BAK_0_TEXT包含的每个文件片段都是相互独立的,即每个片段都可以独立解密。不然,哪怕只想解密单个片段,都得把整个大文件解密才行,相信微信的开发人员应该不会这么蠢。 手机微信静态分析把微信APK从手机中提取出来,使用Jadx反编译分析。这么多文件,从哪里看起呢,我们可以从微信的UI界面入手。 在手机上打开“开发者选项”中的“显示当前界面的包名”,然后在电脑上执行“备份至电脑”,手机上就会显示“备份聊天记录至电脑”的界面,再进入“选择聊天记录”。这个两个界面的类名分别为 BackupPcUI和PCChooseConversationUI 然后就在Jadx中查找这个类,就得开始一层一层地分析代码了。
|