首页
社区
课程
招聘
[原创]PC微信逆向分析の绕过加密来访问SQLite数据库
2020-1-2 17:58 17458

[原创]PC微信逆向分析の绕过加密来访问SQLite数据库

2020-1-2 17:58
17458
PC微信逆向分析の绕过加密来访问SQLite数据库

作者:zmrbak(赵庆明老师)

前言:


微信,无疑是国内目前最为流行的应用软件,其在社交领域的霸主地位依然无人可以撼动!它不但撑起了庞大的腾讯软件帝国的一角,而且足以让腾讯傲立于BAT中国互联网三巨头之首。微信,改变了这个世界,而腾讯也成了无数优秀程序员梦想的归宿。围绕着微信这个软件的延伸,腾讯也无私地为代码世界贡献了近百个优秀的开源项目。

从古到今,任何一个巨人,都是站在前一辈巨人的肩膀上而成长为新的巨人。百度如此,阿里如此,腾讯亦是如此。SQLite数据库,一个不为人熟知,但已超过10000亿个设备在使用的开源数据库,已经渗入人们生活的各个环节。目前超过11亿个活跃的微信号,每时每刻都在与这个深藏功与名的SQLite数据库共舞着。

由于微信的巨大成功,从而成为众多痴迷技术的探秘者的实验品。同样,嵌入微信内部那个深藏功名的SQLite数据库,也成为这些人的又一个解剖对象。接下来,准备好我们的工具,让我们一起来窥探其中的奥妙!

微信中的数据库


无论是在微信安装还是运行的时候,普通用户是不会觉察到微信中居然还有一个默默无闻的SQLite数据库。就算在微信的安装目录中,你也不会发现有任何对于SQLite数据库的描述、说明和感谢(基于SQLite的许可协议,不允许以SQLite的名义来推销产品),但是却在用户的文件夹下(C:\Users\xxxxxx\Documents\WeChat Files\xxxx\Msg\)留下一堆以db为扩展名的文件。对于细心的探索者来说,微信留下来的任何蛛丝马迹,都会变成重要的探秘线索。

这些db文件,都是经过加密处理的。即使你怀疑它是一个SQLite数据库文件,但是你根本无法用任何一个SQLite管理器打开。虽然网上有不少的文章告诉你,去到微信中寻找那个密码,然后使用某某工具进行解密还原。先不说这些工具各种各样的莫名其妙的问题,就算你确实可以顺利搞到密码,再到完成了解密,再进行访问,其实已经晚了半拍。当然,你要相信,腾讯的那些顶级程序员,会在保证软件功能和性能的情况下,会为这些探秘者设置各种各样的障碍。至少我们知道,那些db文件的确是一个个SQLite数据库,只是经过了加密处理而已。

窥探SQLite数据库


SQLite数据库是一个开源的软件,也就是说,任何人都可以免费地获取它的源代码,对它进行研究、分析,当然还有修改。如果你要把你修改的内容“贡献”给SQLite官方,那么你必须申明你放弃你修改的这些代码的“版权”,不过你提供的代码基本不会原样照搬,而是被SQLite官方按照自己的标准重写一次(开源,但不开放)。当然,如果你要修改后自己用的话,那就随便你啦,SQLite官方并不会找你要钱。即便如此,谷歌依然每年给予SQLite巨额的赞助,当然国内腾讯、阿里等这些巨头也毫不吝啬。虽然这个软件包括源代码在内都是免费开放的,但由于有不少的巨头为其撑腰,就算SQLite不收你的钱,它也会活得很好。如果你非要交钱买个许可的话,那就花6000美元买一个吧,官方称这些钱会用于资助SQLite的持续改进和支持。

既然SQLite数据库是开放源代码的,而且还有来自微信开发团队的严苛加密,那么将其与微信一起研究,则更为令人振奋。登上SQLite的官方主页,http://www.sqlite.org,你可以随时把SQLite诞生以来近20年的各个版本抓来研究一番。网站上还有各种示例,教你如何使用SQLite数据库,详尽的各种解说,如果你愿意,你可以通过这个网站了解到它的各种之末细节和详尽的实现方式。虽然,网站没提供中文版,不过,语言并不是障碍,谷歌浏览器的翻译功能,可以让你了解个八九不离十。

一个简单的SQLite示例


如果你还不知道这个SQLite数据库怎么用,网站上提供了TCL的示例代码和C语言的示例代码。现摘抄C示例代码如下(代码来源:https://www.sqlite.com/quickstart.html)。

01  #include <stdio.h>
02  #include <sqlite3.h>
03  
04  static int callback(void *NotUsed, int argc, char **argv, char **azColName){
05    int i;
06    for(i=0; i<argc; i++){
07      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
08    }
09    printf("\n");
10    return 0;
11  }
12  
13  int main(int argc, char **argv){
14    sqlite3 *db;
15    char *zErrMsg = 0;
16    int rc;
17  
18    if( argc!=3 ){
19      fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT\n", argv[0]);
20      return(1);
21    }
22    rc = sqlite3_open(argv[1], &db);
23    if( rc ){
24      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
25      sqlite3_close(db);
26      return(1);
27    }
28    rc = sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
29    if( rc!=SQLITE_OK ){
30      fprintf(stderr, "SQL error: %s\n", zErrMsg);
31      sqlite3_free(zErrMsg);
32    }
33    sqlite3_close(db);
34    return 0;
35  }

示例很简单,也很完善。由于我们的目的是为了探索和研究,因此,我们在确保程序示例可用的情况下,再继续精简。我们删除了很多代码,只剩下核心的几条:

01  #include <stdio.h>
02  #include <sqlite3.h>
03  
04  static int callback(void *NotUsed, int argc, char **argv, char **azColName){
06    for(int i=0; i<argc; i++){
07      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
08    }
10    return 0;
11  }
12  
13  int main(int argc, char **argv){
14    sqlite3 *db;
22    sqlite3_open(argv[1], &db);
28    sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
33    sqlite3_close(db);
34    return 0;
35  }

先从main函数开始,首先定义一个sqlite3的db作为数据库的句柄,然后让执行sqlite3_open打开数据库,接下来执行sqlite3_exec查询数据库,最后执行sqlite3_close关闭数据库。在sqlite3_exec执行期间,使用回调函数callback来处理查询的结果。这,就是一个完整的流程。

执行sqlite3_open打开数据库这个操作,要执行不少的操作,相对比价耗费系统资源。如果一个软件在运行过程中要不断地查询数据库,修改数据库,那么最优的方式就是打开后不要关闭,等到软件关闭的时候再关闭数据库。这样可以节约系统资源,也可以提高数据库的响应速度。

绕过加密访问SQLite数据库

无论是给数据库加上密码,还是使用其它更强有力的方式为数据库加密,无非是为了防止数据库被非法操作。但是,由于基于性能的考虑,应用软件打开SQLite数据库后,只有等到应用程序被关闭的时候,才去关闭数据库。那么在整个应用软件运行期间,SQLite数据库一旦被打开,则将一直保持打开状态。也就是说,上面示例的db,在程序还未调用sqlite3_close函数前,一直是有效的,随时可以接受sqlite3_exec函数的查询操作。这个查询并不是指select,而是指所有可能的sql语句。我们暂且不讨论具有破坏性的操作,如增加(INSERT)、修改(UPDATE)、删除(DELETE)等,在这里我们只来讨论“无破坏性”的“查”(SELECT)的操作。

数据库的加密验证,往往只在于打开数据库的时候,一旦数据库被打开,剩下的操作,则不再对加密进行验证。因此,我们等数据库打开之后,获取db这个数值,然后就可以“非法”地调用sqlite3_exec函数,来执行我们期望的操作。如果这样操作,那么无论软件设计人员设计的任何加密方式,都形同虚设。也就是说,我们可以轻松地实现“绕过加密来访问SQLite数据库”。

为了实现“绕过加密来访问SQLite数据库”,接下来需要解决三个问题。
  • 如何取得已打开的数据库的句柄db的具体数值?
  • 可被非法地调用的sqlite3_exec函数在哪里?
  • 如何非法地调用sqlite3_exec函数?
接下来,让我们依次来解决这三个问题。

如何取得打开数据库的句柄?


如果从正向编程的思维来思考的话,似乎没有答案。但是如果用逆向的思维,你会发现,其实很简单,在调用sqlite3_open函数的时候,其中一个参数就是db,这个db的数值,就是我们正在寻找的打开数据库的句柄。因此,这个问题又转换成,在什么地方调用了sqlite3_open函数。那么,只要我们在调用完sqlite3_open函数之后,马上获取db的值,这个问题就解决了。

如果你使用过OD或者IDA,你就知道,这些软件可以帮你分析出,在程序的什么地方调用了这个函数,而且每一个调用的地方都可以帮你分析出来。如果再配合inline hook,取数据就如同探囊取物一般容易。

我们知道,在调用sqlite3_open函数的时候,必然会打开一个文件。在Windows系统中,必然会调用Windows中的CreateFileW函数,而这个函数可以被OD、IDA等逆向工具所识别。因此,通过倒推的方式,就能顺藤摸瓜找到sqlite3_open函数。如果使用OD来动态调试的话,sqlite3_open就非常容易找到,而且还可以找到调用这个函数的代码段,同时还可以直观地观察到某内存地址中的具体数值。因此,db的值就可以很容易地获取。如果你再编写了inline hook后,只要程序调用了sqlite3_open函数,就可以马上获取db的值。

sqlite3_exec函数在哪里?


在软件中往往会执行一些sql语句,而sqlite3_exec函数对多条函数进行了包装,让程序员执行sql语句更为简单。因此,在应用软件中会有大量的sqlite3_exec函数调用。这恰好是一个切入点。

sqlite3_exec函数的第一个参数是数据库句柄,其实也就是一个DWORD,第二个参数是一条被执行的sql语句,其实就是一个字符串的地址。这些字符串,往往在程序员编程的时候手工写入的,并且嵌入到软件之中,而这些字符串在OD、IDA等这些软件中,可以很容易地识别出来。从而成为找到这个函数的切入点。

比如在软件中寻找“select * from”之类的sql语句,就可以简单地定位到sqlite3_exec函数,从而确定它在应用软件中的具体地址。

如何调用sqlite3_exec


既然我们已经获得到数据库的句柄db的值,而且已经找到和sqlite3_exec的地址,接下来我们就可以来“非法”地调用这个sqlite3_exec函数了。首先,我们来了解一下这个函数的所需的参数:

sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);

第二个参数,是一个查询的sql语句。我们可以定义一个sql的字符串即可,比如“select * from sqlite_master”。由于这条语句是用来查询这个数据库的结构的,因此,在任何一个sqlite数据库中都可以执行成功。当然,你也可以写一个你自己喜欢的sql语句。为了确保一定能查询到信息,我们就用这个字符串。

第三个参数,是一个回调函数,参照SQLite官方的示例代码编写。

第四个参数,是为这个回调函数提供的参数,如果没有,或者不需要提供,那么可以直接填写0或NULL。

第五个参数,返回执行的错误信息,如果不需要的话,可以填写为0或者NULL。

我们只是找到了sqlite3_exec在软件中的地址而已,但地址并不是一个函数,我们也无法直接用字符sqlite3_exec来调用这个函数。但是,我们可以通过C语言内嵌汇编来调用这个函数,也可以用“函数”指针的方式来调用。使用汇编语言,不是很直观,因此使用函数指针,是一个比较好的方法。

我们从SQLite源代码中找到sqlite3_exec函数,把它复制过来,然后做一些修改。把sqlite3_exec第一个字符变成大写Sqlite3_exec,加上typedef、__cdecl*,删除形参,再将db所在的形参改为DWORD:

typedef int(__cdecl* Sqlite3_exec)(
  DWORD,                /* The database on which the SQL executes */
  const char *,             /* The SQL to be executed */
  sqlite3_callback,          /* Invoke this callback routine */
  void *,                  /* First argument to xCallback() */
  char **                  /* Write error messages here */
);

假如sqlite3_exec的地址是:0x12345678,那么我们可以这样定义和调用:

Sqlite3_ exec p_Sqlite3_ exec = (Sqlite3_ exec) 0x12345678;
p_Sqlite3_ exec(db, zSql, callback, 0, 0);

执行结果演示 

 
 





 

总结:


很多初次接触到逆向的人,会被黑压压的汇编代码、一串串的C代码、还有风格诡异的e语言所吓倒。常常有人问我“逆向分析很难吗?”。我也套用一句经典来回答:“天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣!

逆向分析,如同玩猜谜游戏,当你看到一个个谜底慢慢浮现的时候,只有身在其中的人,才能享受其中的刺激和乐趣。作为看客,永远无缘体验这份乐趣。你并不是孤身一人,你还有我们。加入我们的QQ交流群456197310,一起在玩这个其乐无穷的猜谜游戏。

源码分享



交流QQ群:


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞3
打赏
分享
最新回复 (25)
雪    币: 15183
活跃值: (1822)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
会飞的锅 2020-1-2 18:37
2
0
受教了大佬
雪    币: 310
活跃值: (1917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2020-1-2 22:51
3
0
mark
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxzpy 2020-1-3 06:36
4
0
不错,谢谢分享
雪    币: 2435
活跃值: (525)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
FANTASYING 2020-1-3 09:28
5
0
都看过好多个版本了
雪    币: 205
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lion张 2020-1-3 09:44
6
0
可否不运行PC端wx,直接根据wx存储的本地信息获取数据库密码?
最后于 2020-1-3 09:45 被lion张编辑 ,原因:
雪    币: 768
活跃值: (226)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zmrbak 2020-1-3 17:52
7
0
lion张 可否不运行PC端wx,直接根据wx存储的本地信息获取数据库密码?
做不到
雪    币: 768
活跃值: (226)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zmrbak 2020-1-3 17:53
8
0
FANTASYING 都看过好多个版本了
像我这样干的,暂未看到其他人写出过demo
雪    币: 2435
活跃值: (525)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
FANTASYING 2020-1-3 18:05
9
0
zmrbak 像我这样干的,暂未看到其他人写出过demo
github上面diss鬼手开源的就是这个。。pcWechat
最后于 2020-1-3 18:06 被FANTASYING编辑 ,原因:
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
timoduizhang 2020-1-4 14:05
10
0
楼主, 现在sqlite官方除了一个扩展vfs,支持数据库压缩加密。
用这个方法能读取通过vfs加密的数据库吗?
雪    币: 182
活跃值: (576)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柒雪天尚 2020-1-4 14:30
11
0
openssl的那个帖子是比较有技术含量
雪    币: 768
活跃值: (226)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zmrbak 2020-1-4 20:46
12
0
FANTASYING zmrbak 像我这样干的,暂未看到其他人写出过demo github上面diss鬼手开源的就是这个。。pcWechat
鬼手同学的学习力很强,也算比较听话。现在他的新版本也已经收费了。
最后于 2020-1-4 20:53 被zmrbak编辑 ,原因:
雪    币: 768
活跃值: (226)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zmrbak 2020-1-4 20:47
13
0
timoduizhang 楼主, 现在sqlite官方除了一个扩展vfs,支持数据库压缩加密。 用这个方法能读取通过vfs加密的数据库吗?
1.backup出来。
2.dunmp成SQL语句。
雪    币: 768
活跃值: (226)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zmrbak 2020-1-4 20:48
14
0
柒雪天尚 openssl的那个帖子是比较有技术含量
达到同样的目标,有人追求简单直接,有人追求技术含量。选择一个你喜欢的就好。
雪    币: 19
活跃值: (739)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lovehuai 2020-1-6 10:56
15
0
 我用的还是把密钥dump出来
雪    币: 510
活跃值: (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hickwu 2020-1-9 21:57
16
0
思路看明白了, 不过还是没看明白是怎么通过 close 函数获得句柄的,  先 mark 再找时间学习
雪    币: 54
活跃值: (115)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
荒huang 2020-1-10 20:58
17
0
mark 666
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
huanji 2020-6-7 00:57
18
0
发现这个方式有时候还是不够稳定,偶尔微信会奔溃,偶尔有时候查不出数据
雪    币: 0
活跃值: (184)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_杨建 2021-3-15 22:43
19
0
老师,想请教下类似skyguard(天空卫士)等监控聊天内容是不是也是类似的方法呢
雪    币: 864
活跃值: (606)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
八岛 1 2021-3-16 07:11
20
0
我看过别人这么搞,但是我很确定那个人肯定是买了你的课
雪    币: 3523
活跃值: (3454)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
psycongroo 2 2021-3-16 13:49
21
0
没想到赵老师还挺喜欢的(の)这个日文
雪    币: 69
活跃值: (140)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Esc 2021-3-16 14:56
22
0
1.获取句柄db值
2.非法调用sqlite3_exec函数
写的很详细,受教了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
恒河沙数 2021-11-19 11:53
23
0

源代码里面的备份数据库对应的函数字节码,是不是应该按照wechat里面的sqlite3对的版本来确定,否则可能造成造作微信数据库崩溃?

最后于 2021-12-29 16:07 被恒河沙数编辑 ,原因:
雪    币: 1
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gwwolf 2022-4-19 14:54
24
0
受教啦
雪    币: 1524
活跃值: (3300)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 2022-4-20 09:26
25
0
文章写的不错
游客
登录 | 注册 方可回帖
返回