涉及到的LLDB命令
- br: 设置断点
- memory read: 读取内存原始值
- po: 打印变量,也可以执行函数并且获得返回值
- bt: 打印当前调用栈
- thread step over/in/out: 单步跳过/进入/跳出
- register: 寄存器操作
- next/ni/n/step/si: 同上
参考链接
0x00 准备工作
查看WCDB所用的SQLite的加密方式,直接在WCDB的README里写了:
Encryption Support: WCDB supports database encryption via SQLCipher.
于是查看SQLCipher的API,看到用的是sqlite3_key()
和sqlite3_key_v2()
这2个函数,在源码里搜索,找到调用,一共有两处,在WCTDatabase+Database.mm
文件里
- (void)setCipherKey:(NSData *)cipherKey
{
_database->setCipher(cipherKey.bytes, (int) cipherKey.length);
}
- (void)setCipherKey:(NSData *)cipherKey andCipherPageSize:(int)cipherPageSize
{
_database->setCipher(cipherKey.bytes, (int) cipherKey.length, cipherPageSize);
}
然后是下面的C寄存器的含义对照表。其中函数参数也可以不用寄存器表示,可以直接$arg1/$arg2/$arg3
来表示 。
0x01获取保存目录
开始用LLDB动态调试,首先Hook微信进程
$ ps aux | grep WeChat
// 25132 6.7 1.0 7341704 170876 ?? S Fri04PM 8:36.19 /Applications/WeChat.app/Contents/MacOS/WeChat 进程id是25132
$ lldb -p 25132 //开始hook进程
(lldb) process attach --pid 25132
Process 25132 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff6162722a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
-> 0x7fff6162722a <+10>: retq
0x7fff6162722b <+11>: nop
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x7fff6162722c <+0>: movq %rcx, %r10
0x7fff6162722f <+3>: movl $0x1000020, %eax ; imm = 0x1000020
Target 0: (WeChat) stopped.
Executable module set to "/Applications/WeChat.app/Contents/MacOS/WeChat".
Architecture set to: x86_64h-apple-macosx.
(lldb)
进入LLDB命令行模式
打断点获取微信的数据库目录,看WCDB的初始化接口,[WCTDatabase [alloc] initWithPath:path];
我们要获取path
(lldb) br set -n '[WCTDatabase initWithPath:]'
//输出
Breakpoint 1: where = WCDB`-[WCTDatabase(Database) initWithPath:], address = 0x0000000110b38676
(lldb) br list //查看断点
Current breakpoints:
1: names = {'[WCTDatabase initWithPath:]', '[WCTDatabase initWithPath:]'}, locations = 1, resolved = 1, hit count = 0
1.1: where = WCDB`-[WCTDatabase(Database) initWithPath:], address = 0x0000000110b38676, resolved, hit count = 0
点击微信的备份文件,恢复聊天记录至手机或者管理备份文件来触发断点。
Process 25132 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000110b38676 WCDB`-[WCTDatabase(Database) initWithPath:]
WCDB`-[WCTDatabase(Database) initWithPath:]:
-> 0x110b38676 <+0>: pushq %rbp
0x110b38677 <+1>: movq %rsp, %rbp
0x110b3867a <+4>: pushq %r15
0x110b3867c <+6>: pushq %r14
Target 0: (WeChat) stopped.
(lldb) po $arg3 //或者po $rdx 打印第三个参数
/Users/xxxx/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/Backup/91f05ea8dc79f929613c0beb267b789a/75563957-B5FD-4CC5-90F4-0F36D91DD190/Backup.db
获取到备份的数据库位置,直接在finder中打开,发现一共有三个文件
- Backup.db
- BAK_0_MEDIA
- BAK_0_TEXT
为什么是第三个参数呢?
OC的对象方法调用,实际调用的是底层的objc_msgSend($arg1,$arg2,...)
,其中$arg1
为调用者本身,$arg2为方法名,后面的参数表示传递的实际参数,因此是从$arg3开始的,可以打印整个寄存器和$arg1
,$arg2
出来看看
(lldb) register read
General Purpose Registers:
rax = 0x00006000027b98e0
rbx = 0x00007fff5fd16680 libobjc.A.dylib`objc_msgSend
rcx = 0x0000000000000000
rdx = 0x00006000012e63a0
rdi = 0x00006000027b98e0
rsi = 0x00007fff337c64f7 "initWithPath:"
rbp = 0x00007ffee1a3f060
rsp = 0x00007ffee1a3efb8
r8 = 0x0000cc851f0d98e0
r9 = 0x00000000000007fb
r10 = 0x0000000110c78ee8 (void *)0x001d800110c78ec1
r11 = 0x00007fcb9badab70
r12 = 0x00006000037ca9c0
r13 = 0x0000000000000000
r14 = 0x00006000027b9a40
r15 = 0x00006000037ca9c0
rip = 0x0000000110b38676 WCDB`-[WCTDatabase(Database) initWithPath:]
rflags = 0x0000000000000246
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
(lldb) po $arg1 //po $rdi
<WCTDatabase: 0x6000027b98e0>
(lldb) po $arg2 //po $rsi
140734057178359 //字符串打印出被转成数字了,可以用memory read打印
(lldb) memory read $rsi
0x7fff337c64f7: 69 6e 69 74 57 69 74 68 50 61 74 68 3a 00 73 65 initWithPath:.se
0x7fff337c6507: 74 46 69 6c 65 6e 61 6d 65 3a 00 69 6d 61 67 65 tFilename:.image
因此实际的函数参数会从第三个$arg3
开始。
用sqlitebrowser打开这个db文件,发现是SQLCipher加密,要输入密码。
0x02 获取sqlite3_key
继续加断点,如果加在sqlite3_key
上,会发现拿不到PageSize,查看源码看调用链,pageSize是在void Database::setCipher(const void *key, int keySize, int pageSize)
的时候接收的,断点打在setCipher
上
(lldb) br set -n setCipher
(lldb) c //继续执行
触发到sqlite3_key
的断点,
获取key和pageSize
(lldb) memory read $arg2
0x600003f8fa90: 64 64 30 36 33 35 65 63 65 62 35 37 39 35 32 66 dd0635eceb57952f
0x600003f8faa0: 31 62 35 65 63 31 65 64 33 37 38 30 36 65 31 30 1b5ec1ed37806e10
(lldb) po $arg3
32
(lldb) po $arg4
4096
key是dd0635eceb57952f1b5ec1ed37806e10
,取32位,也就是整个字符串,页长度是4096.
打开数据库。
分析一下表,发现文本内容存在BAK_0_TEXT
,媒体内容存在BAK_0_MEDIA
,以偏移量记录某条消息,简单查看一下这2个文件,都是写二进制数据,看来还用了某种加密方式。
0x03 Todo
- 静态分析WeChat.app,获取打开两个加密文件的方法。
0x04 Update
有网友反馈key错误,跟了下发现是sqlitebrowser更新了,支持了最新的SQLCipher4算法,但是微信依旧使用的WCDB1.0.3,还是SQLCipher3,操作的时候先选择SQLCipher3,之后再选择custom,把pageSize改成响应大小即可。