备份微信聊天记录-android篇

存储结构

多用户

uin是微信对账号生成的一个 id,存储在/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml中的_auth_uin

聊天记录

聊天记录放在/data/data/com.tencent.mm/MicroMsg/{md5('mm'+uin)}/EnMicroMsg.db数据库中。

图片

图片存放在/data/data/com.tencent.mm/MicroMsg/{md5('mm'+uin)}/image2目录下。

图片存放路径是两级目录,比如cd/6c/th_cd6cd62684419459895117183a26978b,看上去像是 hash 后的两次索引。可能是因为图片太多,为了提高查找速度。

微信收到一张别人发过来的图片后,会有 3 种形式:

  1. 缩略图,不管有没有查看,都会存一份,体积最小最模糊;
  2. 大图,点开查看才会开始下载,超时就打不开了;
  3. 原图,要手动点击查看原图才会下载。

视频

视频存放在/data/data/com.tencent.mm/MicroMsg/{md5('mm'+uin)}/video目录下。

多半是因为视频不像图片那么多,所以没有像图片那样分路径,全部一股脑的存放在这个目录下。

文件命名规则是yyMMddHHmmss再加 4 位数字,比如2405141411232641。扩展名为.mp4

微信收到一个别人发过来的视频后,也会有 3 种形式:

  1. 视频预览缩略图{file_id}.jpg,不管有没有查看,只要收到就会存一份,体积很小;
  2. 点击查看后,会下载{file_id}.mp4视频文件;
  3. 点击查看原视频后,会下载原视频{file_id}origin.mp4

语音

语音存放在/data/data/com.tencent.mm/MicroMsg/{md5('mm'+uin)}/voice2目录下。

语音也像图片那样,做了索引,存放在两级目录下。扩展名是.amr。比如e4/26/msg_13141905142479bebcac2e0102.amr

虽然扩展名是.amr,但实际上却使用了skype同款编码格式SILK,一般的播放器都不支持,所以需要使用silk-v3-decoder进行转码。

文件

文件索引数据库在/data/data/com.tencent.mm/MicroMsg/{md5('mm'+uin)}/WxFileIndex.db。索引到 Download 文件夹。

Download

Download 文件夹与其它目录不同,它在所有应用可访问的sdcard区,而不是微信私有的/data/data/com.tencent.mm区。

/sdcard/Android/data/com.tencent.mm/MicroMsg/Download。存放着当前手机上所有微信聊天时发送的文件,这里文件例如:文档,安装包、压缩包等。

数据库解密

移动端的解密则相对简单。

先拿到上面说过的uin值,然后在uin前面贴上IMEI,再将这个字符串做md5,将得到的md5值前7位转成小写就是解密使用的密钥。
用代码来表示类似md5(imei + uin).substring(0, 7).toLowerCase()

这段操作可以在使用ApkTool反编译微信apk包后的smali代码中搜索Lcom/tencent/mm/storagebase/IMEISave;->a()Ljava/util/Collection;查到。
有兴趣的同学可以自行逆向分析一下。

微信使用的IMEIcom.tencent.mm.storagebase.IMEISave类中读取,如果IMEI读取失败,默认值是1234567890ABCDEF

IMEI在手机上可以通过拔号*#06#获得,或者进入设置下的关于手机查看。

可以参考这篇博客:解密安卓微信聊天信息存储

解密后查看

可以使用非官方的 docker 镜像来查看:

1
2
docker run -it --rm --entrypoint /bin/bash -v ${pwd}/EnMicroMsg.db:/encrypted.db yspreen/sqlcipher
sqlcipher /encrypted.db
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PRAGMA key = '2592bdf';
ok

PRAGMA cipher_compatibility = 1;

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;

虽然sqlitebrowser3.9.0开始就支持了 sqlcipher,但不能用来查看 android 导出的微信数据库,因为微信使用的 sqlcipher 配置不是标准的。

微信使用的 sqlcipher 配置是:

1
public static final SQLiteCipherSpec l = (new SQLiteCipherSpec()).setPageSize(1024).setSQLCipherVersion(1);

SQLiteCipherSpec代码片断

1
2
3
4
5
6
7
8
9
10
11
12
13
public SQLiteCipherSpec setSQLCipherVersion(int version) {
switch (version) {
case 1:
cipherVersion = 1;
hmacEnabled = false;
kdfIteration = 4000;
hmacAlgorithm = HMAC_SHA1;
kdfAlgorithm = HMAC_SHA1;
break
// ...
}
return this;
}