zoukankan      html  css  js  c++  java
  • (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 = "查询的数据库:
    ";
        text.append(buf);
        text.append("
    ");
        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 = "数据库句柄:
    ";
                text.append(hexString);
                text.append("
    ");
                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地址:
    ";
        text.append(hexString);
        text.append("
    ");
        AddLog(text);
    
        ZeroMemory(hexString, 12);
        sprintf_s(hexString, "0x%08p", MyCallback);
        text = "MyCallback地址:
    ";
        text.append(hexString);
        text.append("
    ");
        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 = "--------------------------------------------------------------------------------------
    ";
        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
    ", *(colName + i), colValue[i]);
            text = string(sOut);
            text.append("
    ");
            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句柄和路径在栈中的偏移也不一样,这些都需要改!

  • 相关阅读:
    iOS-汽车品牌app
    iOS-英雄联盟人物展示
    工作与梦想中的工作:给计算机专业学生的建议
    c++ 宏的使用
    c++ 预处理的应用
    iOS-UItableView 多组表格
    ios-代理模式 协议小结
    c++有符号变量和无符号变量相加的结果
    ios-图片轮播器
    ios-倒计时
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14490326.html
Copyright © 2011-2022 走看看