解密安卓微信聊天信息存储

准备工作

(当前微信版本是:8.0.18)

收集数据

需要收集的数据有:

  • image2 文件夹:里面存放着所有的微信聊天图片,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/image2
  • voice2 文件夹:里面存放着所有的微信语音,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/voice2
  • voide 文件夹:里面存放着所有的微信视频,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/voide
  • avatar 文件夹:里面存放着所有的微信头像,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/avatar
  • Download 文件夹: 微信的聊天发送的文件存放在这里,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/Download
  • EnMicroMsg.db: 微信的数据库文件,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/EnMicroMsg.db
  • WxFileIndex.db: 微信的文件索引数据库文件,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/WxFileIndex.db

在上面的这些文件中,需要注意的是路径中有个 32 位字母的路径,这个是微信通过某种算法生成的,每个号的路径都不一样。其中 voice2voideDownload 这三个文件夹在 /sdcard 目录下,其他的在系统目录 /data 下。

把上面收集的所有文件放在电脑的同一个文件夹中,接下来对这些数据进行处理。

获取 DB 访问密码

EnMicroMsg.dbWxFileIndex.db 是经过加密的,需要获得访问密码来解密。

方法一

可以直接通过 MD5(IMEI+uin) 取前 7 位即是访问密码,如果是大写的要转换成小写字母。(注意:拼接两个数据时不需要用 + 号)

其中 IMEI 是手机的 IMEI 码,可以在手机设置中查看。如果手机刷过机,IMEI 有可能是空白的,或者像 MIUI 系统一样应用无法真正获取到 IMEI,这时可以用 1234567890ABCDEF 这个字符串来代替。

uin 可以通过 adb 来查看:

adb shell
su
cat /data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml

文件内容:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <boolean name="auth_info_prefs_use_new_ecdh" value="true" />
    <int name="_auth_uin" value="272136722" />
    ...
</map>

其中 _auth_uin 的 value 值就是 uin。

方法二

通过 Frida 来获取访问密码,可以直接得到密码,不用手动拼接,绝对正确。

首先安装 Frida:

pip install frida
pip install frida-tools

查看手机架构:

adb shell getprop ro.product.cpu.abi
# arm64-v8a

https://github.com/frida/frida/releases 下载对应架构的 frida-server,注意版本号要和电脑安装的 frida 一致。

通过 adb 传到手机并运行:

adb push frida-server-<版本>-android-arm64 /data/local/tmp
adb shell
su
cd /data/local/tmp
chmod 777 frida-server-<版本>-android-arm64
./frida-server-<版本号>-android-arm64

另开一个终端,转发端口并验证:

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
frida-ps -U

输出进程列表说明环境搭建成功。然后运行下面的 Python 脚本:

import frida
import sys

jscode = """
    Java.perform(function(){
        var utils = Java.use("com.tencent.wcdb.database.SQLiteDatabase");

        utils.openDatabase.overload('java.lang.String', '[B', 'com.tencent.wcdb.database.SQLiteCipherSpec', 'com.tencent.wcdb.database.SQLiteDatabase$CursorFactory', 'int', 'com.tencent.wcdb.DatabaseErrorHandler', 'int').implementation = function(a,b,c,d,e,f,g){
            console.log("Hook start......");
            var JavaString = Java.use("java.lang.String");
            var database = this.openDatabase(a,b,c,d,e,f,g);
            send(a);
            console.log(JavaString.$new(b));
            send("Hook ending......");
            return database;
        };
    });
"""

def on_message(message, data):
    if message["type"] == "send":
        print("[*] {0}".format(message["payload"]))
    else:
        print(message)

process = frida.get_remote_device()
pid = process.spawn(['com.tencent.mm'])
session = process.attach(pid)
script = session.create_script(jscode)
script.on('message', on_message)
script.load()
process.resume(pid)
sys.stdin.read()

脚本运行后,在手机上打开微信,控制台会输出一个 7 位字母,这就是访问密码。

解密 DB

微信加密使用的是开源的 SqlCipher

Mac 直接用 brew 安装:

brew install sqlcipher

验证安装成功(括号里要有 SQLCipher 字样):

sqlcipher
# SQLite version 3.37.2 2022-01-06 13:25:41 (SQLCipher 4.5.1 community)

进入 EnMicroMsg.db 所在目录,执行解密:

sqlcipher EnMicroMsg.db
PRAGMA key = '上面得到的密码';
PRAGMA cipher_use_hmac = off;
PRAGMA kdf_iter = 4000;
PRAGMA cipher_page_size = 1024;
PRAGMA cipher_hmac_algorithm = HMAC_SHA1;
PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;
ATTACH DATABASE 'plaintext.db' AS plaintext KEY '';
SELECT sqlcipher_export('plaintext');
DETACH DATABASE plaintext;

执行完后会生成 plaintext.db,这就是解密后的数据库文件。对 WxFileIndex.db 进行同样的操作(注意第二次解密时换个输出文件名,避免冲突)。

EnMicroMsg.db 解析

主要数据表

  • userinfo 表:存储个人信息,其中 id 为 2 的 value 是个人的微信 id
  • message 表:存储所有的聊天记录
  • chatroom 表:存储所有群聊信息
  • img_flag 表:存储所有用户的在线头像信息,reserved2 是缩略图,reserved1 是高清图
  • rcontact 表:存放所有的好友信息

消息类型

message 表中,type 字段表示消息类型:

  • 1:文本消息
  • 3:图片消息
  • 34:语音消息
  • 43:视频消息
  • 47:大表情消息
  • 49:分享卡片信息
  • 1000:撤回消息提醒
  • 436207665:微信红包
  • 419430449:微信转账
  • 1090519089:文件消息

媒体类型的消息可以用 msgId 字段去 WxFileIndex.dbWxFileIndex2 表中查找对应的文件路径。

图片地址获取

缩略图message 表的 imgPath 字段值类似 THUMBNAIL_DIRPATH://th_5a24c5d362dae72b0ad52d78767ba883,其中 5a24 代表 /5a/24 文件夹,文件名是 th_5a24c5d362dae72b0ad52d78767ba883,父目录是 image2 文件夹。

原图

  1. 发送的图片:文件名是 自己的wxid+_+talker值+_+msgSvrid+_backup
  2. 接收的图片:文件名是 talker值+_+自己的wxid+_+msgSvrid+_backup

路径是文件名的前两个字母,每两个字母代表一个文件夹层级。

视频地址获取

通过 message 表的 imgPath 字段在 video 文件夹中查找,封面图后缀为 .jpg,视频后缀为 .mp4

语音地址获取

imgPath 字段经过 MD5 加密后,前 4 个字母代表两级文件夹名,最终文件名是:msg_imgPath的值.amr

文件地址获取

通过 msgId 字段去 WxFileIndex.dbWxFileIndex2 表中查找对应路径,文件存放在 /sdcard/Android/data/com.tencent.mm/MicroMsg/Download

本地头像获取

头像存放在 avatar 文件夹下,微信 ID 经过 MD5 加密后,前 4 个字母代表两级文件夹名,文件名格式:user_md5字符串.png

例如微信 id weixin 经过 MD5 加密后是 C196266F837D14E0B693F961BEE37B66,头像地址是:avatar/c1/96/user_c196266f837d14e0b693f961bee37b66.png

语音文件处理

微信语音使用 SILK v3 编码,一般播放器无法播放,需要用 silk-v3-decoder 解码。需要先安装 GCC、ffmpeg 等工具,具体查看开源工具说明。

转码后,获取语音文件地址时记得把后缀改为转码后的格式,例如转码成 mp3 格式,后缀就是 mp3,不是 amr