上篇介绍了如何通过嗅探MojoIPC抓包小程序的HTTPS数据。
本篇在博客园地址https://www.cnblogs.com/bbqzsl/p/18423518
微信PC客户端有两种主要的数据存储类型,一种是基于sqlcipher,另一种是基于protobuf。除了这两种外还有别的,不在本篇内容。
它们是对应两个主要的类,StorageBase 跟ConfigInfoStorage。StorageBase使用sqlcipher作为存储手段,ConfigInfoStorage则使用protobuf存储KeyValue。
StorageBase是一个单表操作的封装类,它包含了数据库名跟表名等信息。它封装了打开,查询等底层操作。它的StorageBase::init方法完成了打开数据库以及必要的设置,包括CipherAndKey的设置。逆向分析到现在,我才发现微信是有用混肴的代码段来做代码保护,这个段位于WeChatWin.dll的末尾。StorageBase::init调用DBFactory::openDBbyName方法完成全部打开工作。这个DBFactory::openDBbyName方法,它的日志信息全部字符串都经过混肴处理,显然是不想让人知道。并且DBFactory::openDBbyName的主执行体逻辑被编辑在混肴代码段。混肴代码的目的,不单是让人变白痴,更加重要是让逆向的工具变白痴,包括调用器。微信使用的混肴,有个特点,就是满天的call指令。只要你清楚call指令只是jmp&push的等价物,就明白它的恶心了。调用器的调用栈帧功能,只能用于分析ebp, eip这种中规中举的调用,混肴的call根本不在于call&ret,如果你认为它会在call的下一指令,ret回来执行,你可能等不到它执行。包括逆向工具的代码分析功能也同样被打成白痴。call等同jmp并push eip。这时的eip不是为了ret,而为了开辟esp,并将eip作为后面这个栈位置的内容的解码因子。或者这个栈位置的内容后面是被直接替换而丢弃的。不单代码混肴,连栈结构都被混肴。
所以碰到混肴保护的代码,Mother Mary comes to me Speaking words of wisdom let it be。虽然我假设,微信可能在这里将数据库的KEY,还在KEY的计算方法保护了起来。我承认我还停留在8年那个安卓版的魔法MD5(imei+uin)。想必PC的计算方法是Foo(myPCInfo, uin),存在于客户端代码某处。既然它们都用混肴代码保护起来了,就 so i listened to Mother Mary,let it be。正当我是这么在想的时候,一处AccountService::setDBKey 却赫然在目。唉?不对吧。于是我赶紧windbg跟踪。咦?这参数里面不就有我的数据库的KEY吗?前面我还在说,微信煞有介事地用混肴代码将sqlcipher打开数据库的一连串操作,包括KEY设定,通通都保护起来。现在却明目张胆地露出来。这是在打我脸,还是在打它自个的脸。这戏到底在唱的是哪一出,我完全看不懂。它拼命地将那里遮起来不让别人知道,但用来遮住那里的东西却有一张照片是裸露那里的。这是在玩彩蛋。直到看了它们的技术post才明白,"安全性。基于不怕被破解,但也不能任何人都能破解 的原则",https://cloud.tencent.com/developer/article/1005575。这词令有点耳熟,好像在哪里听过类似的格式的短句。微信开发团队为大家指明了方向,欢迎来破解,就怕你不会。(原来它们的数据库还有一个正名WCDB, https://cloud.tencent.com/developer/article/2406614。我却一直将它当sqlcipher在处理。WCDB是用在移动终端的,PC端的应该不是。)虽然市面上关于这个的破解已经写到烂大街,只要在github上搜wecaht Db crack关键词,就有一大堆的repos。我就简单介绍我的分析,还有方法,为观众多一种角度。AccountService 有三个成员变量,DBKey,RSA公钥,RSA私钥。它们都是std::string类型。并且有已知固定的长度。最lucky的是,它们之间的相对位置经历了这么多年还有版本都没有变化过。搜查的步骤,先通过RSA私钥字符串的地址,再将地址值结合字符串的长度值找到RSA私钥成员变量的位置,然后相对偏移后得到DBKey成员变量的位置。就如我们熟悉的几何定理三点定面一样,锁定位置。只要三条指令,就可以用windbg实现目标。
x86
s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
* theRes
s-d wechatwin L2000000 theRes 0 0 0 377 37f
* theRes2
da poi(theRes2 - 18)
* check if "-----BEGIN PUBLIC KEY-----"
db poi(theRes2 - a8) Lpoi(theRes2 - a4)
x64
s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
* theRes
s-q wechatwin L2000000 theRes 0 377 37f
* theRes2
da poi(theRes2 - 20)
* check if "-----BEGIN PUBLIC KEY-----"
db poi(theRes2 - f8) Lpoi(theRes2 - f0)
补充一下,windbg没有像gdb那样,可以赋值变量,但可以用alias代替。将theRes跟theRes2别名成结果地址, 或者手动替换指令中的theRes跟theRes2。另外一次最多只能搜索地址空间0x10000000,因些在第一个地址段找不到,请用下面命令扫描地址空间的全部用户空间。
s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 10000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 20000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 30000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 40000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 50000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 60000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 70000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
我也跟跟风,写一个https://github.com/bbqz007/CrackMicroMsgDBKey关于如何用windbg看密钥。
找到DBKey后,当然就想知道计算Key的代码。开始我以为会在混肴代码段,但在发现AccountService::setDBKey后,再去跟踪才真相大白。DBKey由服务器返回,计算不在客户端,而在服务器。
有需要的请自行探究,与Auth相关的类。
加密解密两因素,一是Key,二是Cipher。多年没碰微信数据库,我也忘了cipher的设定。随便找个最新的sqlcipher来用,key对了还是死活打不开数据库。因为每个sqlcipher版本默认的cipher不同,或者分发软件的发行者编译的选项不同,默认的cipher不同。cipher的设置必须也要匹配。我们可以直接通过微信直接查询它打开的数据库。一共有kdf_iter,cipher_page_size, cipher_use_hmac, cipher_plaintext_header_size, cipher_hmac_algorithm,cipher_kdf_algorithm六项。
微信将sqlcipher的访问封装成一个单表操作的类。StorageBase。它包含了数据库名,表名,还有最重要的数据库句柄。毕竟还是sqlite3。我们只要找到api表就可以使用所有c接口了。上图的示例就是通过api表的c接口访问StorageBase打开的数据库句柄。即使StorageBase虽然将打开数据库的流程通通用混肴代码保护起来。
得到capi表后,sqlite3_exec可以操作数据库,如上图演示,sqlite3_prapare可以跟踪sql,如下图演示。
我已经在我的KTL工具ver0.9.1(gitee仓库)添加了patch,方便有需要的人,只要在AlgoDataTool.cpp设置你的破解出来的DBKey,就可以浏览你的微信数据库。
数据库文件目录在"我的文档"下,"WeChat Files\wxid_????????\Msg"。如果想知道当前登陆的wxid,可以查看"WeChat Files\config\AccInfo.dat",字符串类型数组中编号4的数值,而编号10则是昵称。如何查看,就是接下来要介绍的另一种数据库类型,我同样也在KTL添加了patch,提供对应查看工具。在本篇,数据库是一个广泛的,包括各种格式的数据文件。比如,类mongodb同样可以用plaintext的方式作为底层存储。一种格式的文件也可以通过mysql存储引擎接口做成自定义存储引擎被mysql使用。
另一种数据库基于protobuf,类型名称为ConfigInfoStorage。它的主要数据成员是micromsg::KeyValueSetting,这是由proto文件生成的类。虽然没有KeyValueSetting的proto文件,但是由逆向内容可以分析根结构。
根级结构主要有5个成员,它们都是数组,每个数组元素皆为一个key-value对。key是整形。知道总的框架结构,在没有proto文件情况下,也可以对protobuf进行大概的分析。虽然没有proto定义文件,但仍然可以用protoc工具解码,只是不知道成员的名称,所有名称都是整形的位置号。但是有了上面的根级结构就可以开展分析。根1号位是32位整形的value数组,每个value都有一个名称对应的编号。数组每个元素是一个intkey-value的proto,所以元素的1号位是intkey,2号位是value。后面的数组以同样的方式。根2号位是buffer类型的数组,这里的buffer有些是经序列化的protobuf。可以认为是对象类型的数组。根3号位是字符串数组,所有字符串的value都在这个数组。根4号位是64位的整形数组。根5号位是浮点形数组。
protoc工具不能在没有proto文件的情况下,解码成JSON格式,只能用TextFormat。但是这个TextFormat是真的独特,似DICT或JSON,但又不同。不能方便使用python或json浏览器进行分析。所以我用我的KTL添加了一个功能,可以将protoc工具解码出来的TextFormat转换成JSON,并提供可视视图。有了转换后的JSON,可以粘贴到任意你喜欢的可视工具进行分析,最简单的就是使用chrome浏览器的devtools,粘贴到console执行。
数据库文件目录在"我的文档"下,"WeChat Files\config\"。其中AccInfo.dat对应AccoutService类,aconfig.dat对应AccoutStorageMgr类。
本篇相继介绍了微信如何使用sqlcipher,AccoutService类,基于protobuf的key-value数据库,ConfigInfoStorage类。数据库存储在我们的电脑磁盘,而类运行在我们的内存。通过扫描这些东西,就可以搜集个人信息。AccoutService类还包含了绑定的手机号。又例如,单纯扫描磁盘,不扫描微信进程,不扫描sqlcipher数据库。也可以得到当前登陆的微信号,这个微信号使用了哪些小程序,这些小程序是哪些知名的小程序应用,是哪种类型的小程序应用,这个微信号有着一些使用习惯喜好以及需求,等等。(每个小程序有一个唯一的AppID,还有绑定的公众号)。配合扫描微信进程的DBKey,就可以直接窥探更加多内容。
在我的KTL工具ver0.9.1(gitee仓库)添加了两个patch,patch.sqlcipher查看sqlcipher数据库,patch.protobuf查看protobuf数据(文件)。
另外直得一提。wechatwin.dll就像一个Library,静态编译进了不计其数的开源库。sqlcipher也是其中之一。我们也可以直接wechatwin.dll。
本篇到这里,下一篇再见。
逆向WeChat(七,查找sqlcipher的DBKey,查看protobuf文件)
逆向WeChat(六,通过嗅探mojo抓包小程序https,打开小程序devtool)
逆向通达信 x 逆向微信 x 逆向Qt (趣味逆向,你未曾见过的signal-slot用法)
逆向WeChat(三, EventCenter, 所有功能模块的事件中心)
逆向WeChat (二, WeUIEngine, UI引擎)
我还有逆向通达信系列。
我还有一个K线技术工具项目KTL,可以用C++14进行公式,QT,数据分析等开发。你的代码JustInTime运行。