天天看點

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

   前面兩篇文章分别介紹了sqlite資料庫句柄和sqlite3_exec函數調用來查找資料庫内容。通過這種方式來查詢,需要一直hook目标軟體。如果目标軟體有檢測程式,就有可能被檢測到。本文分享另一種讀取資料庫内容的辦法:線上備份!

     db檔案存儲的資料,本質上都是二進制的。db檔案由于被加密,在磁盤上的那個檔案必須先解密,是以要先找到密鑰,這是個麻煩事!通過前面的分享可以看出:執行sqlite3_exec時其實db檔案已經解密,是以才能查出來明文!這時的資料庫已經存在于記憶體,sqlite官方提供了備份資料庫的整套API(注意:完整的備份功能需要好幾個API,不止一個,這也為後續我們自己寫代碼備份帶來了很多麻煩事!),連結在這裡: https://www.sqlite.org/backup.html  ,根據官網的接口,自己寫一個備份的demo,先在自己本機試試看行不行!,如下:

#include <stdio.h>
#include "sqlite3.h"

int backupDb(
    sqlite3* pDb,               /* Database to back up */
    const char* zFilename,      /* Name of file to back up to */
    void(*xProgress)(int, int)  /* Progress function to invoke */
);

void XProgress(int a, int b);

int main()
{
    printf("Hello World!\n");
    sqlite3* db = NULL;
    int result = sqlite3_open("testDB.db", &db);

    const char* zFilename = "testDB_back.db";
    backupDb(db, zFilename, XProgress);

    sqlite3_close(db);
}

int backupDb(
    sqlite3* pDb,               /* Database to back up */
    const char* zFilename,      /* Name of file to back up to */
    void(*xProgress)(int, int)  /* Progress function to invoke */
) {
    int rc;                     /* Function return code */
    sqlite3* pFile;             /* Database connection opened on zFilename */
    sqlite3_backup* pBackup;    /* Backup handle used to copy data */

    /* Open the database file identified by zFilename. */
    rc = sqlite3_open(zFilename, &pFile);
    if (rc == SQLITE_OK) {

        /* Open the sqlite3_backup object used to accomplish the transfer */
        pBackup = sqlite3_backup_init(pFile, "main", pDb, "main");
        if (pBackup) {

            /* Each iteration of this loop copies 5 database pages from database
            ** pDb to the backup database. If the return value of backup_step()
            ** indicates that there are still further pages to copy, sleep for
            ** 250 ms before repeating. */
            do {
                rc = sqlite3_backup_step(pBackup, 5);
                xProgress(
                    sqlite3_backup_remaining(pBackup),
                    sqlite3_backup_pagecount(pBackup)
                );
                if (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
                    sqlite3_sleep(250);
                }
            } while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);

            /* Release resources allocated by backup_init(). */
            (void)sqlite3_backup_finish(pBackup);
        }
        rc = sqlite3_errcode(pFile);
    }

    /* Close the database connection opened on database file zFilename
    ** and return the result of this function. */
    (void)sqlite3_close(pFile);
    return rc;
}

void XProgress(int a, int b)
{
    printf("%d,%d\n",a,b);
}      

     确實生成了db檔案:用navicat也能順利查到表和資料,說明備份是成功的!先在新的問題來了:怎麼才能線上備份xxxx的db檔案了? 從記憶體備份完整的db檔案後,也能用這些專業的軟體在本地輕松打開了!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

      上兩篇文章分析了通過openDataBase找到db句柄,在那裡hook的話可以順利得到資料庫句柄。但是從上面的備份代碼看,還涉及到很多sqlite3開頭API的調用,這就麻煩了:

  • 因為在目标程序空間執行,需要這些API在目标程序的位址,而不是上面那麼備份demo程序的位址,這就要像上次找sqlite3_exec函數的入口位址一樣從新開始查找了,真麻煩!
  • 原xxxx軟體大機率是沒用到備份功能的,是以這些備份的代碼應該是沒有的。如果要備份,這些備份的代碼都要自己在dll裡面添加進去,裡面涉及到sqlite3的API函數都要挨個從目标dll裡面找到!

      因為IDA分析PE檔案時會增加引用、F5反編譯等功能,相對OD這種動态調試工具會友善一些,是以這裡用IDA靜态查找這些關鍵函數的偏移位置;先用IDA打開關鍵的dll(這個dll放了很多函數,打開非常慢):dll原本隻有27M,經過IDA分析,添加了好多查找功能,最後膨脹到650M了!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

    下面是需要挨個找的關鍵函數:

DWORD address_sqlite3_open = wxBaseAddress + ;
    DWORD address_sqlite3_backup_init = wxBaseAddress + ; 
    DWORD address_sqlite3_backup_step = wxBaseAddress + ; 
    DWORD address_sqlite3_sleep = wxBaseAddress + ;
    DWORD address_sqlite3_backup_finish = wxBaseAddress + ;
    DWORD address_sqlite3_close = wxBaseAddress + ;
    DWORD address_sqlite3_backup_remaining = wxBaseAddress + ;
    DWORD address_sqlite3_backup_pagecount = wxBaseAddress + ;
    DWORD address_sqlite3_errcode = wxBaseAddress + ;      
  •         先看第一個sqlite3_open: 前面已經找到了openDataBase的偏移,這裡先根據偏移定位到openDataBase調用的地方,然後選擇jmp to xref,如下:
(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

     這裡能看到所有的引用,根據sqlite3_open的源碼,其實就是簡單粗暴直接調用了openDataBase,是以第4、5兩個引用最像(距離最近嘛,偏移隻有D和F);先看看這個,和sqlite3.c中的源碼極其類似,應該就是它了,先把函數改名标記一下,同時記住其偏移:0xA895B0

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

    同理可以标記出另一個sqlite3_open_v2函數(其實就是緊接着上面這個函數,C語言裡面是挨着的,編譯器大機率也會挨着翻譯成機器碼,shellcode也是根據這個原理生成的!後續也會根據這個原理查找其他關鍵的sqlite3函數),這裡不再贅述;

  •      接着看第二個sqlite3_backup_init函數:先在sqlite3.c源檔案中找到這個函數   找到了一個比較明顯的特征:字元串:source and destination must be distinct
(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

       立馬在IDA中查找這個字元串:還真找到了!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

        跳轉到引用這裡:

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

       和源碼一比對,參數是能符合的,應該就是了:call sub_10A083F0應該就是sqlite3ErrorWithMsg了,這裡先标記一下;

sqlite3ErrorWithMsg(
        pDestDb, SQLITE_ERROR, "source and destination must be distinct"
    );      

      往上溯源,找到函數入口,把函數名改成sqlite3_backup_init即可,記下這裡的偏移:0xA26980

  •     接着找sqlite3_backup_step函數

       從sqlite3.c通讀額整個函數,沒有找到任何字元串,看來直接用字元串定位是不行的,隻能換個思路:要麼通過其他函數找(比如這個函數調用了很多其他函數,如果我們先找到了其他函數,就能根據調用關系、順藤摸瓜找到這個函數了),要麼通過機器碼定位!;這裡我們先用機器碼試試。由于是開源的,我們在自己本地先在sqlite3_backup_step函數入口下個斷點,再運作,然後轉到反彙編,提取一些機器碼,比如下面這個:先用前面6個byte的機器碼試試;

#ifdef SQLITE_ENABLE_API_ARMOR
  if( p==0 ) return SQLITE_MISUSE_BKPT;
#endif
  sqlite3_mutex_enter(p->pSrcDb->mutex);
010603C0 8B 45 08             mov         eax,dword ptr [p]  
010603C3 8B 48 14             mov         ecx,dword ptr [eax+14h]  
010603C6 8B 51 0C             mov         edx,dword ptr [ecx+0Ch]  
010603C9 52                   push        edx  
010603CA E8 C8 40 FF FF       call        _sqlite3_mutex_enter (01054497h)  
010603CF 83 C4 04             add         esp,4        

    在IDA中菜單中選擇search->sequence of byte, 輸入8B 45 08 8B 48 14,找到了這3個地方:

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

   和C的源碼比對,明顯不是,放棄;這裡暫時沒有更好的思路了,暫時放棄,先找其他的函數;

  •        sqlite3_backup_finish:這裡面也沒找到字元串,還是根據特征碼查找。同樣先在本地的工程下斷點,然後調試;由于沒有字元串,特征碼也沒有比對上(可能是xxxx用的sqlite版本和我本地的不一樣,也有可能是編譯器翻譯成機器碼不一樣,總之是沒比對上),和剛才那個一樣,暫時方式,繼續找其他函數;
  •      sqlite3_sleep: 既然是sleep,肯定涉及到時間的計算;從源碼看,有幾行比較明顯,比如下面的這行:有乘法,先轉換成毫秒,再除以1000,是以先根據69 45 08 E8 03 00 00 這一串特征碼在IDA中查找:
    rc = (sqlite3OsSleep(pVfs, 1000*ms)/1000);
    0105C66F 69 45 08 E8 03 00 00 imul        eax,dword ptr [ms],3E8h  
    0105C676 50                   push        eax  
    0105C677 8B 4D F8             mov         ecx,dword ptr [pVfs]  
    0105C67A 51                   push        ecx  
    0105C67B E8 D0 94 07 00       call        sqlite3OsSleep (010D5B50h)  
    0105C680 83 C4 08             add         esp,8      
     還真找到了:和本地彙編代碼比雖說不完全一樣,但邏輯結果是一樣的;再結合前面的代碼對比,就是這裡了!記住函數入口的偏移:0xA89C80
(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份
  •  sqlite3_errcode:從源碼看,函數比較簡單,沒有字元串,但是調用了sqlite3SafetyCheckSickOrOk這個函數;
SQLITE_API int sqlite3_errcode(sqlite3 *db){
  if( db && !sqlite3SafetyCheckSickOrOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
  if( !db || db->mallocFailed ){
    return SQLITE_NOMEM_BKPT;
  }
  return db->errCode & db->errMask;
}      

 繼續進入sqlite3SafetyCheckSickOrOk函數,發現有invalid字元串了,但是比較短,感覺不夠;繼續進入logBadConnection函數:

SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){
  u32 magic;
  magic = db->magic;
  if( magic!=SQLITE_MAGIC_SICK &&
      magic!=SQLITE_MAGIC_OPEN &&
      magic!=SQLITE_MAGIC_BUSY ){
    testcase( sqlite3GlobalConfig.xLog!=0 );
    logBadConnection("invalid");
    return 0;
  }else{
    return 1;
  }
}      

    這次就有明顯的字元串了:API call with %s database connection pointer

static void logBadConnection(const char *zType){
  sqlite3_log(SQLITE_MISUSE, 
     "API call with %s database connection pointer",
     zType
  );
}      

   放入IDA搜查,一路跟蹤到這裡:從參數來看,實錘就是這裡了;

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

      先把函數名改了,再往上層層追溯,再和源代碼比對,發現基址在這:0xA885D0;

  •     sqlite3_close:從C源碼看,是直接調用了sqlite3Close,遂進入sqlite3Close函數,發現了一個字元串:unable to close due to unfinalized;如法炮制,繼續用這個字元串在IDA裡面找: 從參數和函數調用來看,确實是這裡,實錘了!
(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

      往上找到函數入口,函數名改為sqlite3Close;這個函數又被調用了好多次,隻有标紅的這兩個最接近入口,和在C源碼看到的接近,先看看這兩個函數:

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

      第一個:從對比來看應該就是sqlite3_close了,在IDA中标記,并記錄下偏移: 0xA871F0;IDA中緊接這下面就是sqlite3_close_v2,也順便标記下!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

    至此,還有sqlite3_backup_step、sqlite3_backup_finish、sqlite3_backup_remaining、sqlite3_backup_pagecount 4個函數沒找到,原因都一樣:(1)沒有字元串  (2)特征碼沒比對上(可能是xxxx用的sqlite版本和我本地做demo的sqlite不一樣,也有可能是編譯器翻譯成機器碼不一樣);這該怎麼辦了?繼續從C源碼入手,找打了一個新的突破口:

    sqlite3_backup_init已經找到了,剩下這4個函數互相挨着的,sqlite3_backup_step在最前面,sqlite3_backup_pagecount在最後面;sqlite3_backup_init和sqlite3_backup_step之間隻間隔了4個函數;前面說過了:編譯器會按照順利編譯(可以利用此特性生成shellcode),也就是說這sqlite3_backup_init後面第5個函數很有可能就是sqlite3_backup_step,然後緊接着就是sqlite3_backup_finish、sqlite3_backup_remaining、sqlite3_backup_pagecount;在隻讀的資料段找到sqlite3_backup_init,如下:

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

         我們順着先看前兩個函數: 這兩個函數代碼幾乎是一樣的,不同的僅僅是傳回值,分别是[eax+20h]和[eax+24h];沒有其他任何代碼了,看起來和sqlite3_backup_remaining、sqlite3_backup_pagecount很像,那麼這兩個是不是了? 就需要進一步驗證參數了!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

    參數類型如下:非常湊巧的是nRemaining偏移在0x20處,nPagecount偏移在0x24h處,那麼這裡實錘了這兩個就是sqlite3_backup_remaining、sqlite3_backup_pagecount;偏移分别是0xA275C0、0xA275D0;

struct sqlite3_backup {
  sqlite3* pDestDb;        /* Destination database handle */
  Btree *pDest;            /* Destination b-tree file */
  u32 iDestSchema;         /* Original schema cookie in destination */
  int bDestLocked;         /* True once a write-transaction is open on pDest */

  Pgno iNext;              /* Page number of the next source page to copy */
  sqlite3* pSrcDb;         /* Source database handle */
  Btree *pSrc;             /* Source b-tree file */

  int rc;                  /* Backup process error code */

  /* These two variables are set by every call to backup_step(). They are
  ** read by calls to backup_remaining() and backup_pagecount().
  */
  Pgno nRemaining;         /* Number of pages left to copy */
  Pgno nPagecount;         /* Total number of pages to copy */

  int isAttached;          /* True once backup has been registered with pager */
  sqlite3_backup *pNext;   /* Next backup associated with source pager */
};      

        先在隻剩sqlite3_backup_step、sqlite3_backup_finish這兩個函數沒找到了;既然這個函數自身沒有字元串,特征碼也不對,那我們先在源碼看看這些函數都在哪些地方被引用了,說不定能從這些引用的函數找到突破口了!很明顯紅框那個才是引用,其他的都是申明或我們自己的代碼;

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

      進入引用,發現一個有趣的現象: 我們還缺的sqlite3_backup_step、sqlite3_backup_finish居然在同一個函數被調用了,這個函數就是sqlite3BtreeCopyFile;也就是說隻要找到sqlite3BtreeCopyFile,就找到了我們想要的函數;

/* 0x7FFFFFFF is the hard limit for the number of pages in a database
  ** file. By passing this as the number of pages to copy to
  ** sqlite3_backup_step(), we can guarantee that the copy finishes 
  ** within a single call (unless an error occurs). The assert() statement
  ** checks this assumption - (p->rc) should be set to either SQLITE_DONE 
  ** or an error code.  */
  sqlite3_backup_step(&b, 0x7FFFFFFF);
  assert( b.rc!=SQLITE_OK );

  rc = sqlite3_backup_finish(&b);
  if( rc==SQLITE_OK ){
    pTo->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
  }else{
    sqlite3PagerClearCache(sqlite3BtreePager(b.pDest));
  }      

    繼續查找sqlite3BtreeCopyFile的引用:發現在sqlite3RunVacuum函數内,更讓人驚喜的是,這個函數内部有大量的sql查詢語句:

rc = execSqlF(db, pzErrMsg,
      "SELECT sql FROM \"%w\".sqlite_master"
      " WHERE type=\'index\' AND length(sql)>10",
      zDbMain
  );
  if( rc!=SQLITE_OK ) goto end_of_vacuum;
  db->init.iDb = 0;

  /* Loop through the tables in the main database. For each, do
  ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy
  ** the contents to the temporary database.
  */
  rc = execSqlF(db, pzErrMsg,
      "SELECT\'INSERT INTO vacuum_db.\'||quote(name)"
      "||\' SELECT*FROM\"%w\".\'||quote(name)"
      "FROM vacuum_db.sqlite_master "
      "WHERE type=\'table\'AND coalesce(rootpage,1)>0",
      zDbMain
  );
  assert( (db->flags & SQLITE_Vacuum)!=0 );
  db->flags &= ~SQLITE_Vacuum;
  if( rc!=SQLITE_OK ) goto end_of_vacuum;

  /* Copy the triggers, views, and virtual tables from the main database
  ** over to the temporary database.  None of these objects has any
  ** associated storage, so all we have to do is copy their entries
  ** from the SQLITE_MASTER table.
  */
  rc = execSqlF(db, pzErrMsg,
      "INSERT INTO vacuum_db.sqlite_master"
      " SELECT*FROM \"%w\".sqlite_master"
      " WHERE type IN(\'view\',\'trigger\')"
      " OR(type=\'table\'AND rootpage=0)",
      zDbMain
  );
  if( rc ) goto end_of_vacuum;

  /* At this point, there is a write transaction open on both the 
  ** vacuum database and the main database. Assuming no error occurs,
  ** both transactions are closed by this block - the main database
  ** transaction by sqlite3BtreeCopyFile() and the other by an explicit
  ** call to sqlite3BtreeCommit().
  */
  {
    u32 meta;
    int i;

    /* This array determines which meta meta values are preserved in the
    ** vacuum.  Even entries are the meta value number and odd entries
    ** are an increment to apply to the meta value after the vacuum.
    ** The increment is used to increase the schema cookie so that other
    ** connections to the same database will know to reread the schema.
    */
    static const unsigned char aCopy[] = {
       BTREE_SCHEMA_VERSION,     1,  /* Add one to the old schema cookie */
       BTREE_DEFAULT_CACHE_SIZE, 0,  /* Preserve the default page cache size */
       BTREE_TEXT_ENCODING,      0,  /* Preserve the text encoding */
       BTREE_USER_VERSION,       0,  /* Preserve the user version */
       BTREE_APPLICATION_ID,     0,  /* Preserve the application id */
    };

    assert( 1==sqlite3BtreeIsInTrans(pTemp) );
    assert( 1==sqlite3BtreeIsInTrans(pMain) );

    /* Copy Btree meta values */
    for(i=0; i<ArraySize(aCopy); i+=2){
      /* GetMeta() and UpdateMeta() cannot fail in this context because
      ** we already have page 1 loaded into cache and marked dirty. */
      sqlite3BtreeGetMeta(pMain, aCopy[i], &meta);
      rc = sqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta+aCopy[i+1]);
      if( NEVER(rc!=SQLITE_OK) ) goto end_of_vacuum;
    }

    rc = sqlite3BtreeCopyFile(pMain, pTemp);      

     繼續如法炮制,根據這些語句先找到sqlite3RunVacuum函數,再進一步找到sqlite3BtreeCopyFile函數(從源碼看,這個函數調用了memset,這也是比較明顯的特征之一);

text:10A276E8 E8 13 FA 86 00                          call    _memset
.text:10A276ED 8B 07                                   mov     eax, [edi]
.text:10A276EF 83 C4 0C                                add     esp, 0Ch
.text:10A276F2 89 45 DC                                mov     [ebp+var_24], eax
.text:10A276F5 8B 47 04                                mov     eax, [edi+4]
.text:10A276F8 89 7D E0                                mov     [ebp+var_20], edi
.text:10A276FB 89 75 CC                                mov     [ebp+var_34], esi
.text:10A276FE C7 45 D8 01 00 00 00                    mov     [ebp+var_28], 1
.text:10A27705 8B 08                                   mov     ecx, [eax]
.text:10A27707 8B 46 04                                mov     eax, [esi+4]
.text:10A2770A 8B 10                                   mov     edx, [eax]
.text:10A2770C 0F B7 81 8E 00 00 00                    movzx   eax, word ptr [ecx+8Eh]
.text:10A27713 66 39 82 8E 00 00 00                    cmp     [edx+8Eh], ax
.text:10A2771A 74 24                                   jz      short loc_10A27740
.text:10A2771C 8B 8A D4 00 00 00                       mov     ecx, [edx+0D4h]
.text:10A27722 66 89 82 8E 00 00 00                    mov     [edx+8Eh], ax
.text:10A27729 85 C9                                   test    ecx, ecx
.text:10A2772B 74 13                                   jz      short loc_10A27740
.text:10A2772D 98                                      cwde
.text:10A2772E 50                                      push    eax
.text:10A2772F FF B2 98 00 00 00                       push    dword ptr [edx+98h]
.text:10A27735 FF B2 DC 00 00 00                       push    dword ptr [edx+0DCh]
.text:10A2773B FF D1                                   call    ecx
.text:10A2773D 83 C4 0C                                add     esp, 0Ch
.text:10A27740
.text:10A27740                         loc_10A27740:                           ; CODE XREF: sqlite3BtreeCopyFile+BA↑j
.text:10A27740                                                                 ; sqlite3BtreeCopyFile+CB↑j
.text:10A27740 8D 45 C8                                lea     eax, [ebp+var_38]
.text:10A27743 68 FF FF FF 7F                          push    7FFFFFFFh
.text:10A27748 50                                      push    eax
.text:10A27749 E8 E2 F5 FF FF                          call    sub_10A26D30
.text:10A2774E 8D 45 C8                                lea     eax, [ebp+var_38]
.text:10A27751 50                                      push    eax
.text:10A27752 E8 69 FD FF FF                          call    sub_10A274C0      

   再對比源碼,根據0x7FFFFFFF很容易找到sqlite3_backup_step和sqlite3_backup_finish,偏移分别是0xA26D30、0xA274C0

memset(&b, 0, sizeof(b));
  b.pSrcDb = pFrom->db;
  b.pSrc = pFrom;
  b.pDest = pTo;
  b.iNext = 1;

#ifdef SQLITE_HAS_CODEC
  sqlite3PagerAlignReserve(sqlite3BtreePager(pTo), sqlite3BtreePager(pFrom));
#endif

  /* 0x7FFFFFFF is the hard limit for the number of pages in a database
  ** file. By passing this as the number of pages to copy to
  ** sqlite3_backup_step(), we can guarantee that the copy finishes 
  ** within a single call (unless an error occurs). The assert() statement
  ** checks this assumption - (p->rc) should be set to either SQLITE_DONE 
  ** or an error code.  */
  sqlite3_backup_step(&b, 0x7FFFFFFF);
  assert( b.rc!=SQLITE_OK );

  rc = sqlite3_backup_finish(&b);      

   至此,所有關鍵函數的偏移都已經找到,總結如下:

//.text:10A895B0
    DWORD address_sqlite3_open = wxBaseAddress + 0xA895B0;
    //.text:10A26980 
    DWORD address_sqlite3_backup_init = wxBaseAddress + 0xA26980;
    //.text:10A89C80
    DWORD address_sqlite3_sleep = wxBaseAddress + 0xA89C80;
    //.text:10A871F0 
    DWORD address_sqlite3_close = wxBaseAddress + 0xA871F0;
    //.text:10A26D30
    DWORD address_sqlite3_backup_step = wxBaseAddress + 0xA26D30;
    //.text:10A274C0 
    DWORD address_sqlite3_backup_finish = wxBaseAddress + 0xA274C0;
    //.text:10A275C0
    DWORD address_sqlite3_backup_remaining = wxBaseAddress + 0xA275C0;
    //.text:10A275D0
    DWORD address_sqlite3_backup_pagecount = wxBaseAddress + 0xA275D0;
    //.text:10A885D0
    DWORD address_sqlite3_errcode = wxBaseAddress + 0xA885D0;      

   效果展示:能hook到所有的db資料庫:

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

   選擇msg0導出,然後放入sqlite export:所有表、字段和資料都能看到了!使用者的隐私蕩然無存!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

最後,做了這麼久的逆向,自己總結的要點如下:關于調試和反調試,xxxx逆向時并未遇到,後續逆向過TP時再分享!

(xxxx)十一:SQLite3的db資料庫解密(三)資料庫線上備份

參考:

1、https://github.com/zmrbak  2019 PC xxxx探秘/SQLite_L37; 注意:不同版本中函數的偏移是不一樣的,不能直接照抄,需要重新找偏移!