(xxxx)十:SQLite3的db数据库解密(二)数据库内容查找
上一篇(xxxx)九介绍了sqlite数据库句柄的查找方法,句柄的查找细节估计不好懂,这里再详细说说!
对于逆向人员而言,sqlite最大的优点就是开源。初次逆向sqlite,不知道目标函数在哪,可以先把开源的代码sqlite3.h和sqlite3.c放入自己的工程(官网上有),编译前再命令行这里增加/FAs选项,如下:
配置好后再重新编译(过程优点慢,需要等大约1分钟),目录下就多了两个asm文件:
先打开我们自己的sqlite3demo.asm文件,看看编译器是怎么把这些C代码转成汇编的:这里可以方便地看到sqlite3_open、sqlite3_exec等关键函数对应的汇编代码;
; 15 : int main(int argc, char** argv) { push ebp mov ebp, esp sub esp, 220 ; 000000dcH push ebx push esi push edi lea edi, DWORD PTR [ebp-220] mov ecx, 55 ; 00000037H mov eax, -858993460 ; ccccccccH rep stosd mov eax, DWORD PTR ___security_cookie xor eax, ebp mov DWORD PTR __$ArrayPad$[ebp], eax mov ecx, OFFSET __DE2F493E_sqlitedemo@c call @__CheckForDebuggerJustMyCode@4 ; 16 : char* zErrMsg = 0; mov DWORD PTR _zErrMsg$[ebp], 0 ; 17 : sqlite3* db; ; 18 : sqlite3_open(argv[1], &db); lea eax, DWORD PTR _db$[ebp] push eax mov ecx, 4 shl ecx, 0 mov edx, DWORD PTR _argv$[ebp] mov eax, DWORD PTR [edx+ecx] push eax call _sqlite3_open add esp, 8 ; 19 : sqlite3_exec(db, argv[2], callback, 0, &zErrMsg); lea eax, DWORD PTR _zErrMsg$[ebp] push eax push 0 push OFFSET _callback mov ecx, 4 shl ecx, 1 mov edx, DWORD PTR _argv$[ebp] mov eax, DWORD PTR [edx+ecx] push eax mov ecx, DWORD PTR _db$[ebp] push ecx call _sqlite3_exec add esp, 20 ; 00000014H ; 20 : sqlite3_close(db); mov eax, DWORD PTR _db$[ebp] push eax call _sqlite3_close add esp, 4 ; 21 : return 0; xor eax, eax ; 22 : }
这里的sqlite_open调用前push了2个参数,和我们上一篇文章看到的参数不一样啊,难道是前面找错了? 好在我们现在已经有sqlite3.asm了,这里继续打开这个文件,看看这个关键函数是怎么被翻译成汇编的,如下:
; COMDAT _sqlite3_open _TEXT SEGMENT _zFilename$ = 8 ; size = 4 _ppDb$ = 12 ; size = 4 _sqlite3_open PROC ; COMDAT ; 159584: ){ push ebp mov ebp, esp ; 159585: return openDatabase(zFilename, ppDb, mov edx, DWORD PTR _ppDb$[ebp] mov ecx, DWORD PTR _zFilename$[ebp] push 0 push 6 call _openDatabase add esp, 8 ; 159586: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); ; 159587: } pop ebp ret 0 _sqlite3_open ENDP _TEXT ENDS
这次能看懂了吧! sqlite3_open实际上调用了openDatabase函数,4个参数中,有两个是写死的,直接用push传参;另外两个需要开发人员指定,所以用寄存器传参的(这属于fastcall调用,不容易看出来);所以上一篇通过这里找到的数据库句柄实际上是找到了openDatabase函数,并不是sqlite3_open函数!从C的代码看,也是这样的:最后两个参数是常量,所以push了两个固定的值!
/* ** Open a new database handle. */ SQLITE_API int sqlite3_open( const char *zFilename, sqlite3 **ppDb ){ return openDatabase(zFilename, ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); } SQLITE_API int sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ){ return openDatabase(filename, ppDb, (unsigned int)flags, zVfs); }
至此,生成数据库句柄的地方已经找到了,”万里长征“终于走出了第一步!找句柄不是我们的目的,仅仅只是一个过程;句柄找到后,通过句柄打开数据库、查看数据才是最终目的!sqlite3有专门的数据库语句执行的函数:sqlite3_exec ; 上面已经有了这个函数的汇编代码,怎么才能在xxxx软件中找到sqlite3_exec的入口了?用字符串搜索!先在OD中找到这个核心dll,进入这个dll的空间!
再在cpu界面右键->中文搜索引擎->智能搜索:用create table作为关键词找到了一个建表的语句:
双击进入后,发现只有两个参数(也有可能是3个),但确实有sql语句,还是先下个断点试试:
此时选择登陆xxxx,果然断下来了:看看此时的ecx:看着像个句柄;栈里也有sql语句;只有参数个数对不上,缺了errormsg、回调函数、回调函数的参数。所以这个函数可能是吧sqlite3_exec又封装了一层:
继续单步执行,下面来到了一个关键点: 这个函数push了5个参数,前面3个都是0,第4个是sql语句,第5个是句柄(下面有截图证明);也就是说,errormg、回调函数的参数、回调函数都是0,只有sql语句和句柄,从语法和逻辑上都是行得通的!
这里可以继续查看第5个参数的结构,和sqlite3 *db的结构一摸一样!
到这里就可以实锤了:0x5864BBC0就是sqlite3_exec函数的入口地址,xxxx关键dll的基址是0x57BF0000,所以sqlite3_exec函数入口地址的偏移就是0x5864BBC0-0x57BF0000=0xA5BBC0! 记住这个偏移,后续写代码查数据库要用到!核心代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include "resource.h" #include "shellapi.h" #include <string> #include <strstream> #include <list> #pragma comment(lib, "Version.lib") using namespace std; VOID ShowDemoUI(HMODULE hModule); INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); BOOL IsWxVersionValid(); VOID UnInject(); VOID HookWx(); VOID InlinkHookJump(); VOID OutPutData(int dbAddress, int dbHandle); VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); VOID ReBootWeChat(); VOID RunSQL(); VOID AddLog(string text); typedef int (__cdecl* sqlite3_callback)(void*, int, char**, char**); 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 */ ); int __cdecl MyCallback(void* para, int nColumn, char** colValue, char** colName);//回调函数 DWORD wxBaseAddress = 0; HWND hWinDlg; const string wxVersoin = "3.1.0.41"; BOOL isWxHooked = FALSE; DWORD hookAddress = 0; DWORD overWritedCallAdd = 0; DWORD jumpBackAddress = 0; DWORD dwTimeId = 0; //定义一个结构体来存储 数据库句柄-->数据库名 struct DbNameHandle { int DBHandler; char DBName[MAX_PATH]; }; //在内存中存储一个“数据库句柄-->数据库名”的链表, list<DbNameHandle> dbList; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { HANDLE hANDLE = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ShowDemoUI, hModule, NULL, 0); if (hANDLE != 0) { CloseHandle(hANDLE); } break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } //显示操作的窗口 VOID ShowDemoUI(HMODULE hModule) { //获取WeChatWin.dll的基址 while (wxBaseAddress == 0) { wxBaseAddress = (DWORD)GetModuleHandle(TEXT("WeChatWin.dll")); Sleep(100); } //启动窗口 DialogBox(hModule, MAKEINTRESOURCE(IDD_MAIN), NULL, &DialogProc); } //窗口回调函数,处理窗口事件 INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) { hWinDlg = hwndDlg; switch (uMsg) { case WM_INITDIALOG: { //DLL加载后,就启动Inline HOOK HookWx(); HWND edit = GetDlgItem(hWinDlg, IDC_EDIT_SQL); string sql = "select * from sqlite_master"; SendMessageA(edit, WM_SETTEXT, NULL, (LPARAM)(sql.c_str())); break; } case WM_CLOSE: { //关闭窗口事件 UnInject(); EndDialog(hwndDlg, 0); break; } case WM_COMMAND: { //执行SQL if (wParam == IDC_BUTTON_SQLRUN) { RunSQL(); break; } //重启微信 if (wParam == IDC_BUTTON_WX_REBOOT) { ReBootWeChat(); break; } break; } default: break; } return FALSE; }//重新启动 VOID ReBootWeChat() { //获取程序路径 TCHAR szAppName[MAX_PATH]; GetModuleFileName(NULL, szAppName, MAX_PATH); //启动新进程 STARTUPINFO StartInfo; ZeroMemory(&StartInfo, sizeof(StartInfo)); StartInfo.cb = sizeof(StartInfo); PROCESS_INFORMATION procStruct; ZeroMemory(&procStruct, sizeof(procStruct)); StartInfo.cb = sizeof(STARTUPINFO); if (CreateProcess((LPCTSTR)szAppName, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartInfo, &procStruct)) { CloseHandle(procStruct.hProcess); CloseHandle(procStruct.hThread); } //终止当前进程 TerminateProcess(GetCurrentProcess(), 0); } //Hook数据库信息 VOID HookWx() { hookAddress = wxBaseAddress + 0x514CC3; jumpBackAddress = hookAddress + 6; BYTE JmpCode[6] = { 0 }; JmpCode[0] = 0xE9; JmpCode[6 - 1] = 0x90; //新跳转指令中的数据=跳转的地址-原地址(HOOK的地址)-跳转指令的长度 *(DWORD*)&JmpCode[1] = (DWORD)InlinkHookJump - hookAddress - 5; WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddress, JmpCode, 6, 0); } //InlineHook完成后,程序在Hook点跳转到这里执行。这里必须是裸函数 __declspec(naked) VOID InlinkHookJump() { //补充代码 __asm { //补充被覆盖的代码 mov esi, dword ptr ss : [ebp - 0x14] add esp, 0x8 //保存寄存器 pushad //参数2,数据库句柄 push[ebp - 0x14] //参数1,数据库路径地址,ASCII;我的3.1.0.14是0x24,不是0x28,这里要改 push[ebp - 0x24] //调用我们的处理函数 call OutPutData add esp, 8 //恢复寄存器 popad //跳回去接着执行 jmp jumpBackAddress } } //把内存中HOOK到的数据存储在链表中,重置定时器,5秒钟后激活定时器 VOID OutPutData(int dbAddress, int dbHandle) { DbNameHandle db = { 0 }; db.DBHandler = dbHandle; _snprintf_s(db.DBName, MAX_PATH, "%s", (char*)dbAddress); dbList.push_back(db); //定时器 dwTimeId = SetTimer(NULL, 1, 5000, TimerProc); } //定时器回调函数,把内存中HOOK来的数据保存到一个文本文件中 VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { if (dwTimeId == idEvent) { //关闭定时器 KillTimer(NULL, 1); //把“数据库句柄-->数据库名”的链表保存到CombBox中 HWND combo1 = GetDlgItem(hWinDlg, IDC_COMBO_DB); //删除全部内容 while (SendMessage(combo1, CB_DELETESTRING, 0, 0) > 0) {} //添加内容 for (auto& db : dbList) { SendMessageA(combo1, CB_ADDSTRING, NULL, (LPARAM)(db.DBName)); } } } VOID UnInject() { HMODULE hModule = NULL; //GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 会增加引用计数 //因此,后面还需执行一次FreeLibrary //直接使用本函数(UnInject)地址来定位本模块 GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPWSTR)&UnInject, &hModule); if (hModule != 0) { //减少一次引用计数 FreeLibrary(hModule); //从内存中卸载 FreeLibraryAndExitThread(hModule, 0); } } VOID RunSQL() { DWORD selectedDbHander = 0; const char* sql = NULL; //清除文本框内容 HWND edit = GetDlgItem(hWinDlg, IDC_EDIT_LOG); SendMessageA(edit, WM_SETTEXT, NULL, NULL); //获取数据库句柄 HWND combo1 = GetDlgItem(hWinDlg, IDC_COMBO_DB); int index = SendMessageA(combo1, CB_GETCURSEL, NULL, 0); char buf[MAX_PATH] = { 0 }; SendMessageA(combo1, CB_GETLBTEXT, index, (LPARAM)buf); //添加日志 string text = "查询的数据库:\r\n"; text.append(buf); text.append("\r\n"); AddLog(text); //获取查询的数据库句柄 string dbName(buf); char hexString[12] = { 0 }; for (auto& db : dbList) { string dbNameInList(db.DBName); if (dbNameInList == dbName) { selectedDbHander = db.DBHandler; sprintf_s(hexString, "0x%08X", selectedDbHander); text = "数据库句柄:\r\n"; text.append(hexString); text.append("\r\n"); AddLog(text); break; } } //SQL语句,限制为MAX_PATH个字符,可做适当调整 ZeroMemory(buf, MAX_PATH); //HWND sqlEdit = GetDlgItem(hWinDlg, IDC_EDIT_SQL); GetDlgItemTextA(hWinDlg, IDC_EDIT_SQL, buf, MAX_PATH); sql = buf; //调用sqlite3_exec函数查询数据库 //char* errmsg = NULL; Sqlite3_exec sqlite3_exec = (Sqlite3_exec)(wxBaseAddress + 0xA5BBC0); ZeroMemory(hexString, 12); sprintf_s(hexString, "0x%08X", (int)sqlite3_exec); text = "sqlite3_exec地址:\r\n"; text.append(hexString); text.append("\r\n"); AddLog(text); ZeroMemory(hexString, 12); sprintf_s(hexString, "0x%08p", MyCallback); text = "MyCallback地址:\r\n"; text.append(hexString); text.append("\r\n"); AddLog(text); DWORD i = sqlite3_exec(selectedDbHander, sql, MyCallback, NULL, NULL); } VOID AddLog(string text) { HWND edit = GetDlgItem(hWinDlg, IDC_EDIT_LOG); //获取当前文本框的字符数量 int count = SendMessageA(edit, WM_GETTEXTLENGTH, NULL, NULL); //获取当前文本框的内容 char* oldChars = new char[count + 1]{ 0 }; SendMessageA(edit, WM_GETTEXT, (WPARAM)(count + 1), (LPARAM)oldChars); string oldText(oldChars); delete[] oldChars; //添加字符 oldText.append(text); SendMessageA(edit, WM_SETTEXT, NULL, (LPARAM)(oldText.c_str())); } int __cdecl MyCallback(void* para, int nColumn, char** colValue, char** colName) { string text = "--------------------------------------------------------------------------------------\r\n"; AddLog(text); char* sOut = new char[1024 * 64]; for (int i = 0; i < nColumn; i++) { ZeroMemory(sOut, 1024 * 64); sprintf_s(sOut, 1024, "%s :%s\n", *(colName + i), colValue[i]); text = string(sOut); text.append("\r\n"); AddLog(text); } delete[] sOut; return 0; }
代码看着有点多,其实超过一般都是图形界面的处理代码,抛开这些,最最最最最核心的代码如下:先定义函数指针,然后通过偏移找到sqlite3_exec的入口,最后调用即可!
typedef int (__cdecl* sqlite3_callback)(void*, int, char**, char**); 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 */ ); int __cdecl MyCallback(void* para, int nColumn, char** colValue, char** colName);//回调函数 Sqlite3_exec sqlite3_exec = (Sqlite3_exec)(wxBaseAddress + 0xA5BBC0); DWORD i = sqlite3_exec(selectedDbHander, sql, MyCallback, NULL, NULL);
效果如下: 这是ChatMsg数据库的所有表!
这都能看到具体的消息内容了:这条消息的时间是2019-06-11 16:17:09,xxxx存储在客户端db文件都快2年了.......
至此,T厂xxxx在客户端存储的数据一览无遗,包括最隐私的聊天记录和关系链!这可是T厂最核心的数据资产啊!其他友商完全可以通过这种技术手段窃取到T厂家的核心资产!
参考:
1、https://github.com/zmrbak/SQLiteReverse/tree/master/%E9%85%8D%E5%A5%97%E4%BB%A3%E7%A0%81/SQLite_L27 C语言调用sqlite3的函数;注意:这个源代码不能直接用!因为不同xxxx版本的openDatabase、sqlite3_exec这些关键函数的偏移不一样,db句柄和路径在栈中的偏移也不一样,这些都需要改!