导出微信聊天记录

前言

最近和妹子聊天,感觉自己聊天的方式有些愚蠢,内容和上下文不符,于是决定将微信聊天记录导出来。
好好复盘下,到底是哪里出了问题,要怎么改进

多平台聊天记录导出

macOS查看聊天记录

聊天记录文件位于~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/*/*/Message/*.db

首先将上面的数据库文件备份出来,可以使用命令:

1
2
mkdir -p ~/Tmp/WeChat
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/*/*/Message/*.db|xargs -I % cp % ~/Tmp/WeChat

由于微信的聊天记录是存放在sqlite数据库里面的, 数据库文件是加密过的,去sqlcipher项目主页的介绍得知,
打开数据库后需要调用sqlite3_key函数, 其原型如下
int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
我们需要得到pKey指针指向的地址,以及nKey的值,这样就能从内存中读出密钥
因此我们需要调试微信,在macOS上面调试应用需要关闭SIP,具体方法可以网上搜索

可以使用Xcode断点调试,也可以使用lldb命令行设置
我习惯用Xcode设置断点,下面介绍这两种方法

由于我的机器处理器架构是armv8a, 64位的通用寄存器编号x0~x30,函数的第一个参数存放在x0, 第二个参数存放在x1,第三个参数存放在x2。而x64架构里面,第一个参数是存放在rdi,第二个参数是存放在rsi, 第三个参数存放在rdx中。关于arm架构的说明可以查看官方的参考手册C1.2.6 Register names

通用寄存器名称

Xcode调试微信

  1. 打开微信和Xcode,设置Xcode调试微信进程

xcode-debug-wechat

  1. Xcode设置符号断点sqlite3_key以及断点后的行为

    xcode-symbol-debug

其中断点后的命令为memory read --size 1 --format x --count 32 $x1 这里只所以读32字节,是因为x2里面存的就是32

xcode-symbol-debug3

  1. 微信扫码登录,此时Xcode已经断点到了sqlite3_key并且在控制台打印了密钥

    xcode-symbol-debug4

lldb命令调试

  1. 打开微信,但不要登录

    wechat-debug-0

  2. 终端输入lldb -p $(pgrep WeChat)调试微信

    wechat-debug-0a

  3. 设置符号断点br set -n sqlite3_key

  4. 输入c以继续程序执行

  5. 扫码登录或者点击登录

  6. 不出意外,这时候在sqlite3_key执行前断点了

  7. 执行memory read --size 1 --format x --count 32 $x1 这里只所以读32字节,是因为x2里面存的就是32

    wechat-debug-1b

整理密钥

通过上面两种方法,我们得到了密钥在内存里面的值,需要整理下

1
2
3
4
5
6
7
8
9
const text = `
0x600001e33120: 0xfe 0x98 0xb2 0x60 0x49 0x98 0x4e 0xb8
0x600001e33128: 0xa4 0xf3 0xd8 0x93 0x96 0xaf 0x8c 0x25
0x600001e33130: 0xb8 0xbc 0x66 0x55 0xcc 0x1b 0x45 0x5b
0x600001e33138: 0xb2 0x9c 0x0f 0x0e 0x6a 0x6f 0x54 0xd1
`
const key = text.split("\n").filter(array => array.length > 1).map(line => line.split(" ").filter(item => item.length == 4).map(item => item.replace("0x", "")).join("")).join("").toUpperCase()
const passwd = `x'${key}'`
console.log(passwd)

将上面的代码在浏览器的控制台执行即可,text的内容要替换成上一步得到的

控制台输出结果为

1
x'FE98B26049984EB8A4F3D89396AF8C25B8BC6655CC1B455BB29C0F0E6A6F54D1'

image-20230115上午112548661

查看微信聊天记录

下面用TablePlus来查看数据库

通过下面👇的命令

1
2
mkdir -p ~/Tmp/WeChat
ls ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/*/*/Message/*.db|xargs -I % cp % ~/Tmp/WeChat

把数据库文件拷贝到了*~/Tmp/WeChat*下面

image-20230115上午113235249

查看聊天记录

image-20230115上午113754578

将聊天记录导出到mysql

没必要对sqlite3的这个数据库文件进行解密,毕竟聊天软件天天都使用,每天都会产生聊天数据,最便捷的方法应该是使用脚本对这个微信数据库里面的数据备份到其它数据库。

由于加密功能是sqlcipher实现的,所以使用导出脚本前,需要安装一些依赖

1
2
3
brew install sqlcipher
pip install sqlalchemy
pip install sqlcipher3

我这里使用的venv创建的python虚拟环境,编译时会找不到头文件和库,需要pip install前设置环境变量

1
2
3
export SQLCIPER_HOME=/opt/homebrew/Cellar/sqlcipher/4.5.3
export CPPFLAGS="-I$SQLCIPER_HOME/include" LDFLAGS="-L$SQLCIPER_HOME/lib"
pip install sqlcipher3

这里还用了一个ORM框架sqlalchemy

image-20230115下午40618103

写了一部分代码仓库地址,完成大概50%

image-20230115下午65336505

iOS聊天记录导出

其实聊天记录最全的应该是手机上的数据库,毕竟大家都习惯在手机上聊天,在iOS上面,由于iOS系统15.0~15.4.1存在漏洞,不需要越狱,可以无视签名安装应用,比如TrollStore,还有些应用可以访问沙盒目录,比如Apps Manager,我的系统版本是15.4.1,下面用Apps Manager备份出微信数据

IMG_BE7C16B4EF13-1

点击备份后会生成adbk的文件,这个文件其实是一个zip的压缩文件,发送到电脑解压以后,目录结构大概如下:

image-20230116上午110631414

其中有几个文件夹比较特别,像这种类似字母数字混合字符串命名的文件夹一般是代表某个账户的数据,毕竟微信可以切换登录到不同的账户,数据还是保留的

聊天数据库文件在账户文件夹/DB目录下面,不需要密码,可以直接打开查看

利用微信备份到电脑

这种方式应该是比较方便的,微信聊天记录备份到电脑后,沙盒目录下面会有一个Backup,里面的文件大小符合预期,不出意料,数据库是加密的。下面我们尝试得到密钥

image-20230117上午94140316

思路很简单,这个数据库文件要么是桌面端的微信创建的要么是移动端的微信创建的,我更倾向于桌面端,毕竟这个文件可不小,放在移动端创建并组织数据结构,先不说对手机存储空间的占用,就是维护的难度也不小。既然猜测是桌面微信创建的,那么我们就调试sqlite3_open这个创建数据库文件的函数,它的第一个参数是数据库文件的路径,第二个是数据库实例的指针。我们的目标是第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ lldb -p $(pgrep WeChat)
(lldb) process attach --pid 1324
Process 1324 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x000000018227a8b0 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
-> 0x18227a8b0 <+8>: ret

libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x18227a8b4 <+0>: mov x16, #-0x20
0x18227a8b8 <+4>: svc #0x80
0x18227a8bc <+8>: ret
Target 0: (WeChat) stopped.
Executable module set to "/Applications/WeChat.app/Contents/MacOS/WeChat".
Architecture set to: arm64e-apple-macosx-.
(lldb) br set -n sqlite3_open
Breakpoint 1: 2 locations.
(lldb) br com add 1
Enter your debugger command(s). Type 'DONE' to end.
> p (char *)$x0
> c
> DONE

这里用lldb来调试了,因为我装的虚拟机,Xcode太大了

image-20230117上午95543911

(🐶魏王待续)