irpas技术客

PC微信逆向--定位sqlite3_exec和数据库句柄_勇敢自由_pc微信逆向

未知 6936

写在前面

最近在做PC端微信逆向,搞定了基本的收发消息,通讯录获取等,这期间遇到一个小小的问题,从通讯录获取到的内容不全,除非登录后手动点击过某个好友,不然获取不到头像、V3等,所以产生了解密数据库的想法。

收集资料

首先要明确一个想法,这个世界很大,想做的事情,至少90%都可能是别人做过的,有些人会把他们的经历分享到互联网上,我们可以利用这些知识,让自己不需要从0开始。 在这个日新月异的时代,技术存在时效性,找到的资料可以作为参考,我们需要做的,是更新替换那些过期的内容,让别人分享的东西在重新跑起来。 下面是我找到的一些资料,讲的都很详细:

一个系列教程,通过已打开的数据库句柄完成在线备份 pc版微信 数据库备份调用sqlite3_exec来执行SQL 绕过加密来访问SQLite数据库定位微信数据库密码 解密数据库文件 信息提炼

通过阅读上面的文章,有些东西基本明确了:

微信使用的数据库是SQLite,并且使用AES加密解密需要用到以下工具或开源库 2.1 OllyDbg 2.2 IDA Pro 2.3 Visual Studio(2019或更高版本) 2.4 OpenSSL 2.5 SQLite3 工具下载

OllyDbg建议使用吾爱破解版,如果无法通过杀软,也可以去看雪工具下载。 OpenSSL使用别人编译好的1.1.1n安装版,尽量使用32位。 SQLite3需要3.28.0的源码包,可以去官网下载 IDA也可以在看雪工具找到。 VS需要去微软官网下载安装工具,完成在线安装。 此外,最好安装CE,可以在吾爱破解的工具包中找到,方便进行内存搜索。

如果已准备好上述工具,就让我们开始吧。

目标

需要分步实现下面的目标:

定位sqlite3_exec函数获取微信打开的数据库句柄调用微信内部的sqlite3_exec执行SQL定位sqlite3_open、sqlite3_backup_init等函数完成数据库在线备份定位数据库密码的保存位置编写数据库离线解密工具 定位sqlite3_exec函数

思路比较简单,先看一下sqlite3_exec的原型:

int sqlite3_exec( sqlite3*, /* An open database */ const char *sql, /* SQL to be evaluated */ int (*callback)(void*,int,char**,char**), /* Callback function */ void *, /* 1st argument to callback */ char **errmsg /* Error msg written here */ ); sqlite3*:通过sqlite3_open打开的数据库句柄 const char *sql:要执行的sql语句 int (*callback)(void*,int,char**,char**):执行sql语句时对应的回调函数 void *:回调函数的参数 char **errmsg:存放错误信息

这里还没有必要分析它,只需要知道这个函数需要五个参数就够了,其中第二个参数是要执行的SQL,其他的,暂时当成DWORD类型。 站在正向的角度去思考,当一个新用户下载并开始使用微信,他还没有任何数据库文件,微信一定要对数据库进行初始化,相应的建表语句是:

CREATE TABLE IF NOT EXISTS `table_name` 表结构;

当然,为了方便,这些命令每次登录都要执行一遍,因为微信也不知道你会不会把库给删了。 打开IDA,按Shift+F12打开字符串窗口,Ctrl+F搜索CREATE TABLE IF NOT EXISTS(下图): 找一个比较完整的SQL点进去,在变量名上按下x查看交叉引用(下图): 查看push这个变量的位置(下图): sub_106D11D0需要两个参数,一个是SQL,另外一个(圈出来的地方)不出意外是数据库句柄,但是这个CALL不是sqlite3_exec,因为参数太少。 双击sub_106D11D0看一下这个CALL的实现(下图): 不远处发现了这样一段汇编,传递了五个参数,末尾的add esp,14h也符合cdecl调用约定;静态调试不是特别明显,可以结合OD进行动态调试,会发现其中一个参数是SQL命令,这里就是在执行数据库的初始化,所以sub_11356570就是sqlite3_exec。 也可以查看CALL的内部(下图): 对比这些特征也能确定(比对sqlite3的源码)。

获取微信打开的数据库句柄 IDA静态分析

刚才定位sqlite3_exec是从引用SQL的地方(下图)向下追溯: 猜测[esi+0x64]是数据库句柄,不过这里没有数据库名,也找不到保存句柄的容器,所以需要向上追溯,找到句柄的来源。回到函数头查看交叉引用(下图): 什么都没有,惊喜又意外,这时候需要使用OD进行动态调试,在这之前,先说一下怎么算地址。 如上图所示,在IDA中,sub_106646500的地址是0x10664650,我的IDA基址是0x10000000(可能跟你的不同),那么sub_106646500对应的偏移就是0x664650,在OD中,需要跳转下断的位置是WeChatWin.dll + 0x664650。

OD动态调试

明确之后,我们继续,打开OD,通过OD启动微信,暂时不要点登录,按Ctrl+G,输入地址后回车(下图): 在函数头下断,然后扫码登录并在手机上点击确认,断下后查看堆栈窗口(下图): 反汇编窗口跟随(下图): 是通过寄存器调用的,IDA中找不到交叉引用也很正常。 在回到上层调用之前,先分析下数据库初始化之前的代码:

0FD3465D 56 push esi 0FD3465E 8BF1 mov esi,ecx ; esi赋值ecx ... 0FD34672 FFD2 call edx ; WeChatWi.0FD34650 ... 0FD346DF 68 50545311 push WeChatWi.11535450 ; SQL 0FD346E4 FF76 64 push dword ptr ds:[esi+0x64] ; 数据库句柄 0FD346E7 8BCE mov ecx,esi 0FD346E9 E8 E2CA0600 call WeChatWi.0FDA11D0 ; 数据库初始化

猜测esi+0x64是保存数据库句柄的指针,在函数开始时,为esi赋值了ecx,查看下ecx+0x64处的数据(下图): 现在还没有东西,按F8执行单步,看看哪个CALL执行完毕得到的数据库句柄,当步过call edx时,esi+0x64处的数据发生了变化(下图): 从0变成了0x9996AB8,再往下esi+0x78保存数据库名,esi+0x8C处保存表名,esi来自ecx,(如果想通过Hook的方式取得句柄,就要进入call edx具体分析了,本文暂不赘述)接下来回到上一层去分析ecx的来源:

0FAD721C E8 2F820600 call WeChatWi.0FB3F450 0FAD7221 8B08 mov ecx,dword ptr ds:[eax] 0FAD7223 8B51 4C mov edx,dword ptr ds:[ecx+0x4C] 0FAD7226 8D8D B0FBFFFF lea ecx,dword ptr ss:[ebp-0x450] 0FAD722C 51 push ecx 0FAD722D 8BC8 mov ecx,eax ; ecx == eax 0FAD722F FFD2 call edx

这里只能得到ecx来自eax,eax这个寄存器比较特殊,可能是前一个CALL执行完毕的返回值,所以要回到函数头下断,看看eax是怎么来的(下图): 断下后开始单步,注意给eax赋值的指令和每个CALL执行后eax的变化。发现最后改变eax的CALL:

... 1038720F E8 3C820600 call WeChatWi.103EF450 ; 最后改变eax的CALL ... 1038721C E8 2F820600 call WeChatWi.103EF450 ... 1038722C 51 push ecx 1038722D 8BC8 mov ecx,eax ; ecx == eax 1038722F FFD2 call edx

追进去(F7或Ctrl+G直接跳)看一下,建议从retn往前找:

0FAEF4DE A1 FCF38A11 mov eax,dword ptr ds:[0x118AF3FC] 0FAEF4E3 83C0 28 add eax,0x28 ... 0FAEF4F7 C3 retn

这里将一个基址保存的数据赋值给eax,然后再加0x28,查看之后也确实是接下来要用到的参数,但是不知道细心的你有没有发现一个问题,刚才我们在函数头下的断点,一直都只断下来一次。 难道微信只有一个数据库?或者为每个数据库写了单独的打开函数? 正常来说,为了提高代码的复用性,应该会对sqlite3_exec进行包装,数据库路径、表名、初始化用的SQL作为参数,写一个循环来初始化所有要用到的数据库。 所以要在刚才的函数头(下图),再向上追溯(因为多次重启微信,所以地址会不一样,可根据地址后四位判断): 断下后反汇编窗口跟随返回地址(下图): 然后怎么办呢,回函数头or单步,其实都可以,回函数头也是要单步的,就先往下走吧,后面不远处就是一个循环:

0FE5E2DF 8BB7 88180000 mov esi,dword ptr ds:[edi+0x1888] 0FE5E2E5 83C4 18 add esp,0x18 0FE5E2E8 8B87 8C180000 mov eax,dword ptr ds:[edi+0x188C] 0FE5E2EE 3BF0 cmp esi,eax ... 0FE5E301 C745 CC 0000000>mov dword ptr ss:[ebp-0x34],0x0 0FE5E30A 83C0 78 add eax,0x78 0FE5E30D 50 push eax 0FE5E30E E8 6DF9C2FF call WeChatWi.0FA8DC80 ... 0FE5E322 FF50 4C call dword ptr ds:[eax+0x4C] ... 0FE5E32E 83C6 04 add esi,0x4 0FE5E331 3BF7 cmp esi,edi 0FE5E333 ^ 75 CC jnz short WeChatWi.0FE5E301

上面这段汇编,遍历[edi+0x1888]到[edi+0x188C],还会引用[esi]+0x78处的数据,这里跟前面讲的一样,保存着数据库的名字,那[esi]+0x64处会保存,或者说,将会保存什么呢?当然是数据库句柄。 在这个循环内,步过每一个CALL后观察下[esi]+0x64的变化,发现执行call dword ptr ds:[eax+0x4C]之后得到了数据库句柄,那么问题就转化为找到edi的来源,这个就很简单了,往上找发现了这个赋值指令:

0FE5E07A 8B3D FCF3C111 mov edi,dword ptr ds:[0x11C1F3FC]

刚才找eax的时候后四位也是F3FC,其实就是同一个地方,不过加的数值不一样,这一次要加0x1888,看看数据(下图): (0x654 - 0x5F0) / 4 = 0x19 = 25,这里初始化了25个表,至于有多少库,就得去重后再统计,看一下成果:

计算偏移

理清楚之后,算一算偏移,方便后面查找句柄,此时WeChatWin.dll的基址是0x0F9F0000

Executable modules, 条目 83 基址=0F9F0000 大小=02680000 (40370176.) 入口=11337B60 WeChatWi.<ModuleEntryPoint> 名称=WeChatWi 文件版本=3.6.0.18 路径=D:\WeChat\[3.6.0.18]\WeChatWin.dll

则edi的计算公式是WeChatWin.dll + 0x11C1F3FC - 0x0F9F0000 开始遍历的地址:[[WeChatWin.dll + 0x222F3FC] + 0x1888] 结束遍历的地址:[[WeChatWin.dll + 0x222F3FC] + 0x188C] 设单个元素为esi,数据库句柄在esi+0x64,数据库名在esi+0x78

写在后面

本篇文章整理了资料和工具,并通过IDA和OD找到了微信中sqlite3_exec函数位置以及保存数据库句柄的地址,下一篇文章,介绍下如何调用sqlite3_exec函数。如果你已经迫不及待,可以参考本文提供的资料,尝试动手实现。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #pc微信逆向 #