文章目錄
-
- 寫在前面
- 備份函數
- 編寫測試程式
-
- 配置環境
- 編譯
- 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的源碼包,解壓後将其整理一下,标頭和源碼分開:
現在,使用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中找到
的位址是100,sqlite3_open
位址是300,在IDA中找到的sqlite3_close
位址是1000,那麼IDA中sqlite3_open
的位址應該在1200附近,雖然無法确定具體位址,但是這個相對距離可以幫助我們在一堆函數中快速篩選。sqlite3_close
結果
剩下的函數就不寫具體的找法了,都可以通過函數名在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
寫在後面
本文介紹了如何通過特征碼等方式定位備份所需的函數,下一篇文章,将使用找到的資訊,褪去微信資料庫的神秘面紗,完成資料庫線上備份!