(xxxx)九:SQLite3的db數據庫解密(一)句柄位置查找


       1、不管在windwos、android、ios或其他平台,應用程序在運行時肯定會產生或讀取很多數據(個人觀點:數據才是核心,所有的代碼都是為數據服務的),這些數據肯定要找個地方存放。比如之前破解的xxxx圖片,加密后以bat格式存放在磁盤某個特定的目錄。除了圖片,還是有其他很多數據,比如聊天記錄、語音/視頻消息等。由於這些數據涉及個人隱私,T家官方答復是用戶的這些數據都是點對點傳輸的,從來都沒經過T家的服務器,那么我們平時用xxxx軟件看到的聊天記錄肯定存放在客戶端本地,而不是從服務器下載的!這些數據都存放在本地哪了?以什么形式存放的?

            簡單的數據,比如軟件參數配置等,一般都以ini格式存放在文件。但聊天記錄、語音/視頻涉及到大量的非結構化數據,這些是沒法直接存文件的。同時文件的數據組織形式很簡單,就是順序存儲,檢索的時候效率也低,不適合大數據量的查詢。這些數據只能以另一種形式存儲了,這就是: 數據庫!

            說起數據庫,IT行業肯定是無人不知、無人不曉!大家聽說和用的最多的就關系型數據庫,比如MySQL、sqlserver、oracle; 近些年大數據和AI火熱,催生了mpp、nosql類的數據庫,比如gbase、vertica、hbase等;這些所有數據庫都有一個同樣的特點,就是很“重”!一般的生產環境,都需要單獨的服務器裝這些數據庫,還會配備專門的運維人員來維護。所以對於普通的客戶端APP,用這些數據庫存放和查詢數據肯定是不行的(不可能讓客戶端的用戶去運維數據庫吧),那么那種數據庫最適合相對輕量的APP來存放和查詢數據了? SQLite孕育而生!

       2、SQLite是一個進程內的庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。它是一個零配置的數據庫,用戶不需要在系統中配置。SQLite 引擎不是一個獨立的進程,可以按應用程序需求進行靜態或動態連接,SQLite 直接訪問其存儲文件,主要優點終結如下:

  • 不需要一個單獨的服務器進程或操作的系統(無服務器的)。

  • SQLite 不需要配置,這意味着不需要安裝或管理。

  • 一個完整的 SQLite 數據庫(就是.db文件)是存儲在一個單一的跨平台的磁盤文件。

  • SQLite 是非常小的,是輕量級的,完全配置時小於 400KiB,省略可選功能配置時小於250KiB。

  • SQLite 是自給自足的,這意味着不需要任何外部的依賴。

  • SQLite 事務是完全兼容 ACID 的,允許從多個進程或線程安全訪問。

  • SQLite 支持 SQL92(SQL2)標准的大多數查詢語言的功能。

  • SQLite 使用 ANSI-C 編寫的,並提供了簡單和易於使用的 API。

  • SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中運行。

        對於輕量級的客戶端APP,最需要功能莫過於前4點了:無需單獨服務器、不需要安裝、db文件跨平台、輕量級別不占空間

      3、xxxx存放數據的db文件都在哪了?下面這個目錄:從db文件的字面意思看,有存放聊天記錄的,有存放表情包的,有存放收藏的,有存放媒體的。

   

      先看ChatMsg.db文件,應該是存放聊天記錄的。要不先試試看能不能打開?結果卻是這樣的!

      

      從提示來看,這個文件都不是database文件,這是怎么回事了?難道我們找錯文件了?為了驗證這個想法, 自己先生成一個db文件,里面寫少量數據,比如:

       

       然后和ChatMsg.db比對一下,問題就很明顯了:我自己生成的db文件開頭就是SQLite format 3,這是個明顯的文件頭信息,說明這個文件的格式(其他格式文件諸如jpg、gif等都這樣,在文件頭就標注了文件的格式);而ChatMsg.db頭部都是“亂碼”,其他地方也都是亂碼!這么看就很明顯了:ChatMsg.db是加密的!其實回過頭來想想:xxxx這種裝機量幾十億的國民級的超級APP,怎么可能會明文存儲客戶的關鍵數據了?要是發生了大規模泄露,T廠的股價不得直接腰斬啊!

       

      既然都加密了,為了看到db的內容,肯定是要解密的!解密涉及到兩個關鍵的信息:密鑰和加密方法!目前生產環境下流行的對稱加密方法(非對稱加密算法效率低一些,這里應該不會用的,而且也沒必要保公私鑰,導致維護成本高):XOR和AES;XOR前面加密圖片時用過了,這里大概率會用AES,原因:(1)XOR用明文和密文能得到密鑰,沒有AES安全   (2)聊天記錄這些都是用戶的絕對隱私,安全級別比圖片高多了!   接下來還有一個問題:AES密鑰的長度是多少了?

       4、解密數據庫前,先簡單學習一下sqlited的使用接口。在https://www.runoob.com/sqlite/sqlite-c-cpp.html 這里有簡單的C或c++接口的demo代碼,如下:

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

static int callback(void *data, int argc, char **argv, char **azColName){
   int i;
   fprintf(stderr, "%s: ", (const char*)data);
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;
   char *sql;
   const char* data = "Callback function called";

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      exit(0);
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }

   /* Create SQL statement */
   sql = "SELECT * from COMPANY";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Operation done successfully\n");
   }
   sqlite3_close(db);
   return 0;
}

     上面代碼很多,除開各種容錯的代碼,核心代碼就這么幾句:先聲明一個sqlite3的指針對象db,再連接數據庫,並且把連接的相關信息保存到db指針(也有些地方叫句柄)。后續執行各種sql語句(select、delete、update等)都通過這個db指針,所有sql代碼執行完后調用cloesAPI關閉指針對象!在調用cloes函數之前,db這個指針(句柄)一直都有效,通過這個指針能調用exec方法執行任何sql語句

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

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
  for(int i=0; i<argc; i++){
    printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
  }
  return 0;
}

int main(int argc, char **argv){
  sqlite3 *db;
  sqlite3_open(argv[1], &db);
  sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
  sqlite3_close(db);
  return 0;
}

       上述代碼經過vs2019編譯后(注意選擇release模式,減少其他代碼的干擾),用IDA打開時main的代碼如下:可以看出call sqlite3_open時push的兩個參數:db是 lea     eax, [ebp-8] ;push eax;這個是非常重要的特征:db是個局部變量,本身在棧內,所以可以通過ebp-立即數的方式找到;db本身又是指針,可以保存函數執行的結果,所以可以把地址傳給open函數;后續分析xxxx打開db庫是會用到

.text:00401090 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401090 _main           proc near               ; CODE XREF: __scrt_common_main_seh+F5↓p
.text:00401090
.text:00401090 zErrMsg         = dword ptr -0Ch
.text:00401090 var_8           = dword ptr -8
.text:00401090 var_4           = dword ptr -4
.text:00401090 argc            = dword ptr  8
.text:00401090 argv            = dword ptr  0Ch
.text:00401090 envp            = dword ptr  10h
.text:00401090
.text:00401090                 push    ebp
.text:00401091                 mov     ebp, esp
.text:00401093                 sub     esp, 0Ch
.text:00401096                 mov     eax, ___security_cookie
.text:0040109B                 xor     eax, ebp
.text:0040109D                 mov     [ebp+var_4], eax
.text:004010A0                 push    esi
.text:004010A1                 mov     esi, [ebp+argv]
.text:004010A4                 lea     eax, [ebp-8]    ; sqlite3* db;
.text:004010A7                 push    eax
.text:004010A8                 mov     [ebp+zErrMsg], 0
.text:004010AF                 push    dword ptr [esi+4] ; argv[1]
.text:004010B2                 call    ds:__imp__sqlite3_open
.text:004010B8                 lea     eax, [ebp+zErrMsg]
.text:004010BB                 push    eax
.text:004010BC                 push    0
.text:004010BE                 push    offset callback
.text:004010C3                 push    dword ptr [esi+8]
.text:004010C6                 push    [ebp+var_8]
.text:004010C9                 call    ds:__imp__sqlite3_exec
.text:004010CF                 push    [ebp+var_8]
.text:004010D2                 call    ds:__imp__sqlite3_close
.text:004010D8                 mov     ecx, [ebp+var_4]
.text:004010DB                 add     esp, 20h
.text:004010DE                 xor     ecx, ebp        ; cookie
.text:004010E0                 xor     eax, eax
.text:004010E2                 pop     esi
.text:004010E3                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:004010E8                 mov     esp, ebp
.text:004010EA                 pop     ebp
.text:004010EB                 retn
.text:004010EB _main           endp

為了進一步熟悉lite相關API,也可以通過調試體驗一下;在IDA中看到main被__scrt_common_main_seh調用了,所以也可以在OD中這里下個斷點方便觀察:

  

  進一步:可以根據call main代碼在這函數內部的偏移,在OD找到main的真正入口后下斷點:

 

為了方便靜態分析,也可以在edit->segment->rebase program這里把整個exe默認的基址改成OD中實際加載的基址0xBC0000,支持IDA和OD的地址就完全對齊了!

 

  吐槽一下OD這里的寫法: 直接用local.2來表示了,平時都用epb-立即數表示局部變量的,這里都有些不習慣了!

  

  open函數執行完后,ebp-8、也就是db指針的地方出現了一個句柄,這個大概率就是sqlite3的對象了! 這個句柄很重要,后續對db數據庫所有的操作都要通過這個句柄來實現

 

繼續順着這個句柄跟蹤內存,這里明顯可以發現單個對象的大小是D4-80=0x54字節;把內存網上滑,還能看到好幾個同樣結構的句柄!

 

 上面鋪墊了這么多,都是為了后續在xxxx中快速找到sqlite3_open函數,這個函數有兩個非常明顯的特征參數:(1)db文件的路徑  (2)sqlite3 *db這個句柄是由lea 的方式保存到寄存器,再傳遞給函數使用的!  這是兩個非常重要的特征,一定要牢記! 

   5、現在正式開始分析xxxx的sqlite數據庫!數據庫以db文件形式存放在磁盤。xxxx剛開始運行時,肯定會從磁盤讀出來、解密,用戶才能看到聊天記錄等關鍵內容。從磁盤讀文件,win32編程必然涉及到CreateFileW(注意:xxxx全球通用,為了兼容肯定會用w方法),這里先在CreateFileW下斷點:(注意: 本人調試期間失敗了無數次,並非一氣呵成,所以關鍵數據的地址有可能每次都不一樣)

 

   已經開始讀磁盤的文件了,不過這個是log日志,明顯不是我們想要的,直接放過!

   

   用OD的時候出了一些bug,這里換成x32dbg繼續:找到db文件,就是這里了!

    

    從調用堆棧看,有10來個函數。這里沒有啥特殊的技巧,只能挨個都看看,直到第4個函數:

    

     這里非常可疑:lea edx, [ebp-0x14]像極了傳sqlite3 *db的句柄對象;這個句柄經過call處理后,又付給了esi,進一步說明句柄是有用的;先下個斷點試試:

    

     這明顯是這個dll里面的,可以記住偏移:0x7a494cbe-0x79f80000=0x514CBE, 以后調試直接一步到位!

     

     x32dbg出現了異常,繼續換回OD調試:ebp-0x14這里已經生成了句柄,棧上不遠處還有db文件的完整路徑,這里越看越像;

    

    繼續追蹤這個句柄:怎么樣,和我們自己寫的demo的sqlite3 *db句柄是不是很像啊!!! 0x64-0x10=0x54,連長度都是一樣的,這里就可以實錘這就是sqlite3 *db句柄了!后續寫代碼hook的時候,可以直接在0x7A494CC3(也就是call的下一行代碼,已經生成了句柄!寫代碼時需要動態獲取,hook點相對dll基址的偏移是0x7A494CC3 - 0x79f80000 = 0x514CC3)這里下斷點,然后取[ebp-0x14]就是sqlite3的句柄了!還有另一個重要的信息:[ebp-0x24] 數據庫路徑了! 偏移、db句柄的位置、db文件的位置這3個信息非常重要,后續寫代碼要用到!

    

    現在已經確認call 7AA090A0初始化了sqlite3 *db句柄,那么進一步跟蹤進入這個函數瞅瞅:一行一行地跟蹤太累;由於我們需要找到哪些代碼改寫了[ebp-0x14]、也就是sqlite3* db句柄的值,所以對這個地址下個寫入斷點,斷到了這里:這里eax存放了句柄地址,先給地址清零,然后調用7AA0959A函數,所以這個函數有重大“嫌疑”:放過后發現並未改變句柄指針的值,無奈繼續;

    

    當走到這里時:句柄的值被改變了,就是圖中標紅的這兩行代碼!esi存放了句柄的值,此時重點變成了回溯esi是怎么來的了!這里的偏移0x7AA09577-0x79F80000=0xA89577可以記住,下次直接到這里

    

   繼續往上回溯: 發現好多地方都在讀寫esi,如果下個要搞定出到底是那行代碼生成和句柄,需要繼續逐行分析esi值的改變!

   

    其實到此為止,利用找到的句柄位置完全可以通過調用sqlite3_open、sqlite3_exec函數讀寫db文件了!下次繼續分享怎么通過代碼遠程調用句柄讀寫db的數據!

   

 查找小技巧:

1、右邊是棧視圖:棧本質上也是一塊內存,里面存的都是各種二進制數據,這些數據都有可能是什么數據了?

  • 數字從幾到幾萬、幾十萬的:這些數字比較常規,游戲中可能是血量、魔法、距離、坐標、葯品數量等;其他軟件大概率都是常規的數字,具體含義根據軟件業務意義確定
  • 數字很大,4字節至少占用了3個字節表示,這種數字大概率是地址、指針或句柄,怎么區分這3者了?
    •  如果是地址:OD會標記返回到xxxxx來自xxxxx:通過棧回溯找call就是這個原理!
    •     如果是字符串指針:OD會標時出字符串
    •     如果是句柄(或則說結構體/對象指針):OD棧視圖中雙擊這個句柄,內存視圖也會如下標記 

                   

 

 心得:

       要想逆向做的好,首先要有正向開發的經驗和思維,逆向的時候才知道去哪找關鍵的call!逆向分析目標exe前,最好自己寫個簡單的demo,分析一下核心函數被編譯器翻譯成匯編時的指令,找到這些指令的特征后再去逆向,事半功倍!

 

參考:

1、https://www.runoob.com/sqlite/sqlite-intro.html  SQLIite簡介

2、https://blog.csdn.net/qq_38474570/article/details/96606530  PC xxxx逆向:兩種姿勢教你解密數據庫文件

3、https://bbs.pediy.com/thread-257028.htm  PC xxxx逆向分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM