文章目录

  • 写在前面
  • 备份函数
  • 编写测试程序
  • 配置环境
  • 编译
  • OD调试
  • 结果
  • OD地址
  • IDA地址
  • 写在后面


写在前面

上一篇文章,介绍了如何使用找到的数据库句柄和sqlite3_exec函数执行SQL,本篇文章,来尝试定位微信中备份sqlite数据库的相关函数,为下一篇文章要实现的在线备份做铺垫。

备份函数

开始找之前,要明确需要找的目标,先看一段别人写的备份函数:

int backupDb(sqlite3* pDb, const char* szFilename,
    void(*xProgress)(int, int)
) {
    int rc;
    sqlite3* pFile;
    sqlite3_backup* pBackup;
    //打开数据库
    rc = sqlite3_open(szFilename, &pFile);
    if (SQLITE_OK == rc) {
        //初始化获取一个备份对象
        pBackup = sqlite3_backup_init(pFile, "main", pDb, "main");
        if (pBackup) {
            do {
                //每次备份5页
                rc = sqlite3_backup_step(pBackup, 5);
                //通知更新进度
                xProgress(sqlite3_backup_remaining(pBackup),//还剩余需要备份的页数
                    sqlite3_backup_pagecount(pBackup)//备份的总页数
                );
                if (SQLITE_OK == rc || SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
                    //睡眠
                    sqlite3_sleep(250);
                }
            } while (SQLITE_OK == rc || SQLITE_BUSY == rc || SQLITE_LOCKED == rc);
            //完成备份
            sqlite3_backup_finish(pBackup);
        }
        rc = sqlite3_errcode(pFile);
    }
    sqlite3_close(pFile);
    return rc;
}

这段代码需要的三个参数,分别是需要备份的数据库句柄、备份后保存的文件名,以及一个回调函数。
句柄我们已经拿到,保存的文件名和回调函数是自定义的,三个参数没有需要特别注意的,函数里面用到的几个sqlite函数是关注的重点。
作为一个半吊子,无法解释为什么用到这几个函数,也不再深究,只需要知道它可以正常工作就好。整理一下:

sqlite3_open // 打开数据库
sqlite3_backup_init // 初始化获取一个备份对象
sqlite3_backup_remaining // 还剩余需要备份的页数
sqlite3_backup_pagecount // 备份的总页数
sqlite3_sleep // 睡眠
sqlite3_backup_finish // 完成备份
sqlite3_errcode // 应该是检查是否有错误
sqlite3_close // 关闭数据库

编写测试程序

配置环境

在准备阶段已经下载了sqlite3 3.28.0的源码包,解压后将其整理一下,标头和源码分开:

sqlite 查看 journal_mode SQLITE 查看微信_sqlite


现在,使用VS2019新建一个空项目,将sqlite3中的标头和源代码都包含进去:

添加包含目录

sqlite 查看 journal_mode SQLITE 查看微信_微信_02


添加源文件

sqlite 查看 journal_mode SQLITE 查看微信_微信_03


在第一篇文章找到的资料里,前辈们帮我们踩了一些坑,在编译之前,需要在sqlite3.h中添加一处宏定义:

#ifndef SQLITE3_H
#define SQLITE3_H
// 添加这一句
#define SQLITE_CORE 1
#include <stdarg.h>     /* Needed for the definition of va_list */

此外,需要将以下文件排除出项目:

fts1.cfts2.c

fts3_tokenizer.c

geopoly.c

icu.c

tclsqlite.c

最后,关闭内联函数扩展,避免函数入口被编译器优化掉:

sqlite 查看 journal_mode SQLITE 查看微信_安全_04

编译

接下来编写测试程序,并在OD中调试找到函数特征码。测试程序如下:

#include <iostream>
#include <windows.h>
#include "sqlite3.h"

int create(sqlite3** db) {
	int rc = sqlite3_open("test.db", db);
	string sql = "CREATE TABLE IF NOT EXISTS Test (bin BLOB)";
	rc = sqlite3_exec(*db, sql.c_str(), NULL, NULL, NULL);
	return rc;
}

int main() {
    sqlite3* db = NULL;
    int rc = create(&db);
    return 0;
}

平台配置选择ReleaseWin32(x86),然后右键项目->生成,等待编译完成。

OD调试

编译完成后,得到了测试程序,接下来要在OD中进行分析,这里不建议使用吾爱版OD,它对函数加了很多修饰符(不知道是否可以通过设置取消),而看雪提供的OllyICE不存在此问题。

程序启动后会立刻断下,不需要运行,只需按Ctrl+G,输入函数名并跳转:

sqlite 查看 journal_mode SQLITE 查看微信_数据库_05


跳转到目标函数入口:

00AEBCF0 >/.  55            push    ebp
00AEBCF1  |.  8BEC          mov     ebp, esp
00AEBCF3  |.  8B55 0C       mov     edx, dword ptr [ebp+C]
00AEBCF6  |.  8B4D 08       mov     ecx, dword ptr [ebp+8]
00AEBCF9  |.  6A 00         push    0
00AEBCFB  |.  6A 06         push    6
00AEBCFD  |.  E8 4EF9FFFF   call    openDatabase
00AEBD02  |.  83C4 08       add     esp, 8
00AEBD05  |.  5D            pop     ebp
00AEBD06  \.  C3            retn

然后,使用特征码8B 55 0C 8B 4D 08 6A 00 6A 06在IDA中搜索(可以在OD中选中汇编代码,右键->二进制->二进制复制来获取特征码),IDA中可以按Alt+b打开二进制搜索窗口:

sqlite 查看 journal_mode SQLITE 查看微信_微信_06


搜索到的地址:

sqlite 查看 journal_mode SQLITE 查看微信_微信_07


双击查看:

sqlite 查看 journal_mode SQLITE 查看微信_安全_08


只能说完全一样。

sqlite3_open的地址就确定了,为WeChatWin.dll + 0x138ACD0 这个找起来是如此的简单,但并不是所有的都这么简单,特征码检索不到时,可以尝试以下办法:

  1. 比对特殊的立即数,比如sqlite3经常用到的0x4B771290、0xA029A697、0xF03B7906
  2. 字符串,比如sqlite3_backup_init可以使用source and destination must be distinct来定位。
  3. 交叉引用,比如sqlite3_close调用了sqlite3Close,就可以先定位到sqlite3Close,然后根据引用关系找到sqlite3_close
  4. 参数比对,当搜索出的地址过多时,可以根据源码中函数所需的参数数量(IDA中函数头部有arg和var,是参数和局部变量)来确定到底是哪一个。
  5. 地址距离比对,比如在OD中找到sqlite3_open的地址是100,sqlite3_close地址是300,在IDA中找到的sqlite3_open地址是1000,那么IDA中sqlite3_close的地址应该在1200附近,虽然无法确定具体地址,但是这个相对距离可以帮助我们在一堆函数中快速筛选。

结果

剩下的函数就不写具体的找法了,都可以通过函数名在OD里直接跳转。IDA中寻址就留作读者的作业吧,这里提供一份参考答案:

OD地址

在比对地址距离的时候可以使用。

sqlite3_open = 0091BBC0
sqlite3_backup_init = 008CDF40
sqlite3_backup_step = 008CE2B0
sqlite3_sleep = 0091C110
sqlite3_backup_finish = 008CE760
sqlite3_close = 0091A1B0
sqlite3_backup_remaining = 008CE830
sqlite3_backup_pagecount = 008CE840
sqlite3_errcode = 0091B090

IDA地址

微信版本:3.6.0.18

sqlite3_open = 1138ACD0
sqlite3_backup_init = 1131C110
sqlite3_backup_step = 1131C510
sqlite3_sleep = 1138B510
sqlite3_backup_finish = 1131CB50
sqlite3_close = 113880A0
sqlite3_backup_remaining = 1131CC50
sqlite3_backup_pagecount = 1131CC60
sqlite3_errcode = 11389970

写在后面

本文介绍了如何通过特征码等方式定位备份所需的函数,下一篇文章,将使用找到的信息,褪去微信数据库的神秘面纱,完成数据库在线备份!