0x00 前言
之前研究过一段时间的wx聊天记录解密,以及小程序解密,但是在晚上陆续搜了几篇文章后发现解密教程要么下载Visual Studio,不然就是对web安全人员不友好的od调试,而且根据系统的不同,解密方法也不同,于是作者用为数不多的编程知识写出了这篇教程- -在这里记录一下解密的原理以及一些方法分享给大家。
项目地址 :代码有些粗糙,有精力的小伙伴们可以自行fork改造下,原先想写GO版本来着但是用python开了个头就继续写下去了
0x01 原理
SQLite 是一个轻量级的、开源的关系型数据库,是目前移动平台(如 iOS、Android)数据库的最佳选择。然而免费版的 SQLite 是不支持加密的(官网下的默认就是免费版的) ,这就导致了存储在 SQLite 中的数据很容易被人查看到,不法分子可能会利用数据库表结构及内容字段分析我们的应用,进而发起攻击。出于安全的考虑,我们当然要对数据库文件进行加密。
微信数据库的类型是sqlite,不管是ios还是mac还是win或者安卓,区别在于每个数据库都加密了但是加密的密钥不同,这里win和mac主要采用从远程服务器获取一些信息加上本地的某些信息通过一系列算法生成的密钥通过AES加密的,AES的密钥是32位,而且所有数据库文件共用一个密钥,我们需要找到那个AES密钥才能进行解密,然后才能对数据库文件进行操作。但是安卓和ios不同,通过翻阅一些文章发现,手机端的数据库生成的密钥是通过本地已有数据进行生成的密钥,仅有7位,我们可以通过暴力破解的方式获取密钥。
解密PC数据库 一般都是通过od附加微信进行动态调试 https://bbs.pediy.com/thread-251303.htm
还有一种办法就是通过寻找微信模块的地址然后加上偏移量就能获取密钥以及一些用户信息,但是这也有个问题就是当每次微信版本更新时偏移量也会随之改变,我看到解决方法是作者需要每个版本获取偏移量,然后更新工具这样也很麻烦。
于是根据别人的文章分析了一下,如果说每次偏移量都会变但是相关信息的数据结构的相对便宜量不会变,这种方式可以全版本通用,通俗一点来讲就是,每次私钥和wxid的字符串地址虽然会变但是相对位置不变,我可以通过wxid的字符串加减多少一定能找到私钥的地址。
0x02 分析
这里我首先通过往常一样的动调发现,在密钥出现的上下地址中存在另一种公钥和私钥的字符串
-----BEGIN PUBLIC KEY-----\n...这不就是一个很好的特征吗
于是我们首先通过python中的pymem模块来操作内存,至于为什么要用这个模块的,因为他对初学者比较友好里面封装了很多函数,很方便调用适合我这种对win系统不了解的人,按照这个思路我们先找到-----BEGIN PUBLIC KEY-----字符串的地址,再去反向搜索这个地址的地址,根据测试这个地址一般是大于WeChatWin.dll 但也不是绝对有些情况会导致搜不到这个字符串地址。
但是这里出现了一个问题,我在学习pymem的过程中通过翻阅文档发现有一个全局搜索函数pattern_scan_all,但是通过实际下载pymem模块时候却没有了这个函数,于是我这里只好自己把这个函数再重新写一遍。
```def pattern_scan_all(handle, pattern, *, return_multiple=False):
from pymem.pattern import scan_pattern_page
next_region = 0
found = []
user_space_limit = 0x7FFFFFFF0000 if sys.maxsize > 2 ** 32 else 0x7fff0000
while next_region < user_space_limit:
next_region, page_found = scan_pattern_page(
handle,
next_region,
pattern,
return_multiple=return_multiple
)
if not return_multiple and page_found:
return page_found
if page_found:
found += page_found
if not return_multiple:
return None
return found
再根据分析整体数据结构的偏移量就可获得很多信息了
![img_v2_65fcc1df-bf24-40a9-8eeb-081da6b8608g.jpg](https://tttang-web.oss-cn-zhangjiakou.aliyuncs.com/media/attachment/2022/07/15/c9c26324-e393-4893-aa6e-663be81a9139.jpg)
# 0x03 解密
上面已经获取了很多信息,接下来我准备开始解密,但是有一个问题,如果我们通过下载Visual Studio的c++库编写解密脚本而且还要自行编译,本身我也不会c++,而且Visual Studio太过臃肿,我这里在网上搜了好半天,看看有没有什么python解密sqlite的库结果发现[pysqlcipher3](https://pypi.org/project/pysqlcipher3/)可以用来解密,但是在安装的过程中,发现安装失败,结果去网上论坛搜索还是需要静态编译链接,我尝试了几次发现还是要下载Visual Studio,于是这个库我只好放弃,但是在解密mac的过程中可以使用上述的这个模块我们只需要在mac系统中安装相应工具就可以使用该模块了
brew install sqlcipher
回到windows,我实在找不到结局方案,只好使用比较low的方法,通过subprocess去调用工具,工具还是蛮小的方法可行。
if not db_file:
raise ValueError("db_File is not defined!")
if not secret_key:
raise ValueError("secret_key is not defined!")
salt = open(db_file, 'rb').read(16)
dk = hashlib.pbkdf2_hmac('sha1', secret_key, salt, 64000, dklen=32)
exe_cmd = "%s %s" % (get_exe_file(), db_file)
p2 = Popen(exe_cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True)
cmd_sql = '''PRAGMA key = "x'%s'";PRAGMA cipher_page_size=4096; ATTACH DATABASE 'decrypt_%s' AS plaintext KEY ''; SELECT sqlcipher_export('plaintext'); DETACH DATABASE plaintext;''' % (binascii.hexlify(dk).decode(),os.path.basename(db_file))
code, message = p2.communicate(bytes(cmd_sql, encoding='utf-8'))
```
这里我们写好了相关解密脚本结果mac数据库和win数据库采用的加密方式不同,于是我又去了解了一下
传入密钥
通过Rand_bytes算法生成16个字节的salt,并存储在数据库第一页的头部(SQLite3的db文件,头部前16个字节固定为SQLite 3 format,所以可以利用文件头来存储一些数据)。
通过PKCS5_PBKDF2_HMAC_SHA1算法将密钥和salt一起加密并多次迭代,生成AES加密所用的key;此处是对key的加密,即使原始的密码泄露,也无法解密数据。
通过AES对称加密算法对每一页的文件内容(有效的内容,不包含文件头和reserved字段)进行加密。
加密时,文件执行过AES加密后,对文件内容,通过Hmac算法,获取文件校验码,填充在page尾部(SQLite3提供了reserved字段,自动在page尾部预留一段空间)。
解密时,先调用Hmac算法获取文件标识码,与page尾部的数据进行对比,如果数据一致,则证明文件没有被篡改过,不然证明文件已经被篡改,则抛出异常。
PS:以上为默认的算法,sqlCipher没有固定算法,用户可以自己设置。
经测试windows系统的wx数据库就是用上述加密方式,首先会通过读取文件头生成16个字节的salt,再使用PBKDF2_HMAC_SHA1算法设置迭代次数为64000次生成的密钥这样的话解密就很简单了
salt = open(db_file, 'rb').read(16)
dk = hashlib.pbkdf2_hmac('sha1', secret_key, salt, 64000, dklen=32)
exe_cmd = "%s %s" % (get_exe_file(), db_file)
p2 = Popen(exe_cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True)
cmd_sql = '''PRAGMA key = "x'%s'";PRAGMA cipher_page_size=4096; ATTACH DATABASE 'decrypt_%s' AS plaintext KEY ''; SELECT sqlcipher_export('plaintext'); DETACH DATABASE plaintext;''' % (binascii.hexlify(dk).decode(),os.path.basename(db_file))
这里也遇到了坑 key可以有两种表现方式,一种是单纯字符串口令,另一种就是原始密钥,我们从内存中获取的是原始密钥所以要输入的是PRAGMA key = "x'密钥'"
我们回到mac密钥的获取,因为mac的权限管理做的很死,我们首先需要关闭SIP(系统完整性保护)
系统完整性保护(SIP)是 OS X El Capitan 及更高版本所采用的一项安全技术,旨在帮助防止潜在恶意软件修改 Mac 上受保护的文件和文件夹,但这也造成了安装某些特殊版本软件的或者做特殊修改的时候权限不足。在这里就体现在使用 LLDB 调试时候,所有的调试语句都会被系统拒绝,因此在正式进行调试之前,一个重要的准备工作就是检查系统完整性保护(SIP)的开启状态,如果开启的话,要把它关闭。
检查 SIP 的开启状态
在终端里输入 csrutil status 回车,如果看到:
System Integrity Protection status: enabled.
这说明的 SIP 已经开启,如果要继续调试的话,需要关闭。如果是 System Integrity Protection status: disabled.
则说明 SIP 已经处于关闭状态,可以直接进行调试。
- 重启,并在开机的时候长按 Command 和 R
- 进入系统恢复状态
- 点击屏幕顶部工具栏上的 实用工具,选择终端
- 在终端中输入
csrutil disable
回车,会出现下述字符串,再次重启生效
Successfully disabled System Integrity Protection. Please restart the machine for the changes to take effect.
然后我们就可以开始调试微信了
1. 打开电脑端微信(不要登陆)
2. 在Terminal输入命令lldb -p $(pgrep WeChat)
3. 输入br set -n sqlite3_key
,回车
4. 输入c
,回车
5. 手机扫码登陆电脑端微信
6. 这时候电脑端微信是会卡在登陆界面的,不需要担心,回到Terminal
7. 输入memory read --size 1 --format x --count 32 $rsi
,回车就可以获取到了密钥
0x6000003624e0: 0x54 0x60 0x97 0x05 0xb5 0x09 0x43 0x9f
0x6000003624e8: 0x94 0xe8 0x38 0x09 0xdc 0x5e 0x79 0x53
0x6000003624f0: 0x4f 0xdc 0xa1 0x66 0x8e 0x96 0x4a 0x98
0x6000003624f8: 0x9a 0x72 0xa6 0x17 0xe0 0x17 0x7c 0x56
这里我们可以直接PRAGMA cipher_compatibility = 3
就可以设置好解密参数了 ,这是一个标准
解密效果如下
0x04 一些其它功能
微信在2022.06月之后更改了传输文件存储位置,和每个人聊天所传输的文件会被放在不同的文件夹下,大大提升了我们寻找历史文件的难度,于是我写了个整合文件名输出的功能
还有就是压缩功能可以分别压缩数据库,图片,历史传输文件
0x05 碎碎念
原先想继续完善安卓和ios的解密脚本来着,但是作者马上要开始准备秋招了没时间写,等过段时间再更新下,写这篇文章的目的是想让大家接触下电子取证这个方向的知识,不仅仅是微信还有一些社交软件都可以尝试去分析下,例如soul,陌陌,qq等。不限于app甚至是阿里云镜像取证,这个方向也有很多知识值得学习。
参考:
https://bbs.pediy.com/thread-250714.htm
https://www.zetetic.net/sqlcipher/sqlcipher-api/
https://github.com/allen1881996/WeChat-Data-Analysis