微信数据库句柄劫持与sqlite3_exec非法调用


版本号:3.4.5

前言

由于最近编写微信机器人,需要用到读取群员列表功能,所以将目标锁向了微信数据库。

技术分析

通过网络资料,我们可以清晰的查询到微信使用的是sqlite3数据库,只不过对其进行了加密处理。微信官方也是为了方式用户信息被窃取,所以对数据库密钥出现的地方做了保护,貌似使用了vm虚拟化代码。由于技术不到家所以决定用另类办法。这里笔者决定劫持微信的数据库句柄,非法调用微信的数据库执行函数。

准备步骤

1、下载sqlite3源码,下载方式:使用vcpkg进行下载安装。使用指令vcpkg install sqlite3:x86-windows-static

kywoqey3.png

2、从网上CV一个示例代码,让我们先熟悉这个库的二进制代码,寻找函数特征


#include <list>
#include <sqlcipher/sqlite3.h>

using namespace std;
sqlite3* pDB; // sqlite3为一个结构体
typedef struct tagMSG_ITEM {
public:
    tagMSG_ITEM() {
        ulUID = 0;
        szFromUser = "";
        szToUser = "";
        strDateTime = "";
        strMsg = "";
    }
    tagMSG_ITEM& operator=(const tagMSG_ITEM& rMsgItem) {
        ulUID = rMsgItem.ulUID;
        szFromUser = rMsgItem.szFromUser;
        szToUser = rMsgItem.szToUser;
        strDateTime = rMsgItem.strDateTime;
        strMsg = rMsgItem.strMsg;
    }
public:
    unsigned long ulUID;
    std::string    szFromUser;
    std::string   szToUser;
    std::string    strDateTime;
    std::string    strMsg;


}MSG_ITEM, * PMSG_ITEM;

typedef std::list<MSG_ITEM>  ListMsgItem;
ListMsgItem m_listMsg;
/*
    函数名:createTable(sqlite3 *db)
    功能:创建一张数据表Table
*/
int createTable(sqlite3* db) {
    char* zErrorMsg = NULL;
    int rc = sqlite3_exec(db, "create table msg_xuxin_table (datetime TEXT, fromid NUMERIC, message TEXT, toid NUMERIC, uid INTEGER)", NULL, 0, &zErrorMsg);
    if (SQLITE_OK != rc) {
        fprintf(stderr, "SQL error:%s\n", zErrorMsg);
        sqlite3_free(zErrorMsg);
    }
    return 0;
}
/*
    函数名:insertDB()
    功能:创建一张数据表Table
*/
int insertDB() {
    char* zErrorMsg = NULL;
    std::string str1 = "2016";
    std::string str2 = "insert into msg_xuxin_table values('2017-1-2 10:33:14', 100, '2017-1-2', 2000, 200);";
    int rc = sqlite3_exec(pDB, str2.c_str(), 0, 0, &zErrorMsg);
    if (SQLITE_OK != rc) {
        fprintf(stderr, "SQL error:%s\n", zErrorMsg);
        sqlite3_free(zErrorMsg);
    }
    return 0;
}

static int callback(void* NotUsed, int argc, char** argv, char** azColName) {
    int i;
    for (i = 0; i < argc; i++) {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return argc;
}

int selectDB() {
    char* errMsg;
    string strSQL = "select * FROM Sqlite_master;";
    // 第三个参数是一个回调函数,你可以通过这个函数得到执行的一些信息
    int res = sqlite3_exec(pDB, strSQL.c_str(), callback, 0, &errMsg);

    if (res != SQLITE_OK) {
        std::cout << "执行SQL 出错." << errMsg << std::endl;
        return -1;
    } else {
        std::cout << "SQL成功执行." << std::endl;
    }

    return 0;
}

void main() {
    // 第二步:打开数据库.1、打开失败,关闭数据库并返回;2、打开成功就继续建立表
    int res = sqlite3_open("im_msg.db", &pDB);
    if (res) {
        cout << "Cannot open database:" << sqlite3_errmsg(pDB);
        sqlite3_close(pDB);
        return;
    }

    //  第三步:创建一个数据表
    res = createTable(pDB);
    if (res) {
        return; // 如果创建表失败
    }
    //  第四步:实现基本的“增、删、改、查”的功能
    res = insertDB();//增
    res = selectDB();//查

    sqlite3_close(pDB);
}

3、将编译好的代码使用x32dbg进行调试【注意:一定要release版本,debug版本编译器会对你的代码添油加醋】

分析sqlite3_open函数

1、首先定位到demo中函数sqlite3_open的执行位置

kywqlfhp.png

2、在微信对其进行虚拟化之前此处特征码可以直接定位到我们需要的函数,但是最新版的微信已经使用虚拟化对其进行了代码保护,但是出于效率考量,微信肯定不能将整个库全部虚拟化,所以笔者准备寻找更里层的函数特征。我们将目标锁定到openDatabase函数。这里笔者采用动静结合方式去调试微信。

kywqp2ab.png

3、阅读代码后,我们发现该函数有几处明文,众所周知,我们代码尽可能的少暴漏明文,这样不好。所以我们将如下明文作为特征去微信中搜索看是否有所结果。

kywqr2xq.png

4、我这里选择NOCASE作为特征查阅,你要问我为什么,我只能说这几个明文截图中就他和RIRIM出现的最少。而通过动态调试,结果告诉我,我选择的雀氏不错,只有两个地方匹配,那么就很简单了,我们定位到他们的函数头,复制RVA去IDA中查看他的参数列表以及函数特征是否匹配。

kywqtpjl.png

5、第一个我们点进去在IDA查看,他和demo中差距较大,他有五个参数,而openDatabase中只有四个参数。有时ida的参数也不会准确,所以为了更加准确,我们对比两个函数的代码,发现也是大不相同,所以pass第一处,查看第二处。
6、第二处应该是我们需要的函数openDatabase了,首先参数列表匹配。其次函数体较为相似,如下图所示。

第一处:
kywr1v5h.png
kywr28k9.png
第二处:
kywr2k4f.png
kywr2zda.png
第三处:
kywr4k25.png
kywr4nw4.png

7、光是对比静态代码也只是缩小范围,还是不能作为确定依据,所以我们目前认定该函数就是openDatabase。所以在x32dbg中对其下断,重新登录微信,因为他查询数据库之前一定要对其解密并且打开。如果断点没有过来,则代表我们找错了。那么就需要用其他办法进行分析。但是幸运女神照顾我,他确实是openDatabase函数,返回的句柄也是数据库句柄,如下图所示。

kywrb8gc.png

8、我们将函数执行到返回地址,看看他调用它的是谁,但是很遗憾,返回回去就是虚拟化代码了,所以我们要获得数据库句柄只能从此函数下手,剩下的则为代码编写,本文就不再阐述。

分析sqlite3_exec函数

1、首先定位到demo中函数sqlite3_exec的执行位置

kywow44a.png

2、分析函数sqlite3_exec的特征,我们不难发现他会有几处字符串特性。如下所示

kywoyfhq.png

3、得到此处特征后,我们附加微信,查看微信相匹配特征函数。这里我选择字符串%s at line %d of [%.10s]为目标搜索

kywp2ivg.png

4、发现搜索到了一大堆结果,于是准备换一个字符串进行搜索,经过测试发现misuse字符串搜索结果是最少的,所以对该字符串搜索的结果一一排查,通过特征确定哪个函数是sqlite3_exec

kywpg408.png

5、首先找到该函数的函数头,再复制他的RVA,到IDA窗口中定位其位置后,进行反编译代码

kywpkdpk.png

6、很快我们找到一处与sqlite3_exec函数特征相匹配的函数。那么该函数极为有可能是sqlite3_exec函数。

kywpppxd.png

7、对该函数的a3参数改名为callback,查看是否有调用,因为回调函数是一定会调用的,如果有这个特征那么可以更加确定是该函数。经过查阅伪代码,我们可以几乎确定该函数为sqlite3_exec函数。

kywptnsd.png

kywpu7nh.png

8、静态分析完成后,我们再尝试动态分析该函数,对其进行下断,我们重新登录微信,因为微信在登录过程中一定会查询数据库,如果这个过程中没有调用该函数,证明我们寻找错误,则需要返回继续寻找相匹配函数。
9、经过动态调试,我们很明显的看到堆栈列表中的传参情况,所以该函数就是我们想要的sqlite3_exec函数,剩下则是代码编写过程,本文就不再阐述了。

kywq0jn9.png