解密安卓微信聊天信息存储
准备工作
(当前微信版本是:8.0.18)
- 一台 Root 的手机(手机不能 Root 的话用安卓模拟器,然后安卓模拟器获取 Root 应该也是可以的,不过我没试过)
- DB Browser for SQLite
- SQLCipher
- silk-v3-decoder
收集数据
需要收集的数据有:
- 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 位字母的路径,这个是微信通过某种算法生成的,每个号的路径都不一样。其中 voice2、voide、Download 这三个文件夹在 /sdcard 目录下,其他的在系统目录 /data 下。
把上面收集的所有文件放在电脑的同一个文件夹中,接下来对这些数据进行处理。
获取 DB 访问密码
EnMicroMsg.db、WxFileIndex.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.db 的 WxFileIndex2 表中查找对应的文件路径。
图片地址获取
缩略图:message 表的 imgPath 字段值类似 THUMBNAIL_DIRPATH://th_5a24c5d362dae72b0ad52d78767ba883,其中 5a24 代表 /5a/24 文件夹,文件名是 th_5a24c5d362dae72b0ad52d78767ba883,父目录是 image2 文件夹。
原图:
- 发送的图片:文件名是
自己的wxid+_+talker值+_+msgSvrid+_backup - 接收的图片:文件名是
talker值+_+自己的wxid+_+msgSvrid+_backup
路径是文件名的前两个字母,每两个字母代表一个文件夹层级。
视频地址获取
通过 message 表的 imgPath 字段在 video 文件夹中查找,封面图后缀为 .jpg,视频后缀为 .mp4。
语音地址获取
imgPath 字段经过 MD5 加密后,前 4 个字母代表两级文件夹名,最终文件名是:msg_imgPath的值.amr。
文件地址获取
通过 msgId 字段去 WxFileIndex.db 的 WxFileIndex2 表中查找对应路径,文件存放在 /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。