(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句柄和路径在栈中的偏移也不一样,这些都需要改!

posted @ 2021-03-06 19:27  第七子007  阅读(1464)  评论(0编辑  收藏  举报