写在前面
上一篇文章,介绍了如何使用找到的数据库句柄和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的源码包,解压后将其整理一下,标头和源码分开:
现在,使用VS2019新建一个空项目,将sqlite3中的标头和源代码都包含进去:
添加包含目录
添加源文件
在第一篇文章找到的资料里,前辈们帮我们踩了一些坑,在编译之前,需要在sqlite3.h
中添加一处宏定义:
#ifndef SQLITE3_H
#define SQLITE3_H
// 添加这一句
#define SQLITE_CORE 1
#include <stdarg.h> /* Needed for the definition of va_list */
此外,需要将以下文件排除出项目:
fts1.c
fts2.c
fts3_tokenizer.c
geopoly.c
icu.c
tclsqlite.c
最后,关闭内联函数扩展,避免函数入口被编译器优化掉:
编译
接下来编写测试程序,并在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;
}
平台配置选择Release,Win32(x86),然后右键项目->生成,等待编译完成。
OD调试
编译完成后,得到了测试程序,接下来要在OD中进行分析,这里不建议使用吾爱版OD,它对函数加了很多修饰符(不知道是否可以通过设置取消),而看雪提供的OllyICE不存在此问题。
程序启动后会立刻断下,不需要运行,只需按Ctrl+G,输入函数名并跳转:
跳转到目标函数入口:
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
打开二进制搜索窗口:
搜索到的地址:
双击查看:
只能说完全一样。
sqlite3_open
的地址就确定了,为WeChatWin.dll + 0x138ACD0
这个找起来是如此的简单,但并不是所有的都这么简单,特征码检索不到时,可以尝试以下办法:
- 比对特殊的立即数,比如sqlite3经常用到的
0x4B771290、0xA029A697、0xF03B7906
。 - 字符串,比如
sqlite3_backup_init
可以使用source and destination must be distinct
来定位。 - 交叉引用,比如
sqlite3_close
调用了sqlite3Close
,就可以先定位到sqlite3Close
,然后根据引用关系找到sqlite3_close
。 - 参数比对,当搜索出的地址过多时,可以根据源码中函数所需的参数数量(IDA中函数头部有arg和var,是参数和局部变量)来确定到底是哪一个。
- 地址距离比对,比如在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
写在后面
本文介绍了如何通过特征码等方式定位备份所需的函数,下一篇文章,将使用找到的信息,褪去微信数据库的神秘面纱,完成数据库在线备份!