技术好文共享:(xxxx)十一:SQLite3的db数据库解密(三)数据库在线备份

简介: 技术好文共享:(xxxx)十一:SQLite3的db数据库解密(三)数据库在线备份

  前面两篇文章分别介绍了sqlite数据库句柄和sqlite3_exec函数调用来查找数据库内容。通过这种方式来查询,需要一直hook目标软件。如果目标软件有检测程序,就有可能被检测到。本文分享另一种读取数据库内容的办法:在线备份!


db文件存储的数据,本质上都是二进制的。db文件由于被加密,在磁盘上的那个文件必须先解密,所以要先找到密钥,这是个麻烦事!通过前面的分享可以看出:执行sqlite3_exec时其实db文件已经解密,所以才能查出来明文!这时的数据库已经存在于内存,sqlite官方提供了备份数据库的整套API(注意:完整的备份功能需要好几个API,不止一个,这也为后续我们自己写代码备份带来了很多麻烦事!),链接在这里: ,根据官网的接口,自己写一个备份的demo,先在自己本机试试看行不行!,如下:


#include


#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文件后,也能用这些专业的软件在本地轻松打开了!


上两篇文章分析了通过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了!


下面是需要挨个找的关键函数:


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,如下:


这里能看到所有的引用,根据sqlite3_open的源码,其实就是简单粗暴直接调用了openDataBase,所以第4、5两个引用最像(距离最近嘛,偏移只有D和F);先看看这个,和sqlite3.c中的源码极其类似,应该就是它了,先把函数改名标记一下,同时记住其偏移:0xA895B0


同理可以标记出另一个sqlite3_open_v2函数(其实就是紧接着上面这个函数,C语言里面是挨着的,编译器大概率也会挨着翻译成机器码,shellcode也是根据这个原理生成的!后续也会根据这个原理查找其他关键的sqlite3函数),这里不再赘述;


接着看第二个sqlite3_backup_init函数:先在sqlite3.c源文件中找到这个函数 找到了一个比较明显的特征:字符串:source and destination must be distinct


立马在IDA中查找这个字符串:还真找到了!


跳转到引用这里:


和源码一比对,参数是能符合的,应该就是了: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个地方:


和C的源码比对,明显不是,放弃;这里暂时没有更好的思路了,暂时放弃,先找其他的函数;


sqlite3_backup_finish:这里面也没找到字符串,还是根据特征码查找。同样先在本地的工程下断点,然后调试;由于没有字符串,特征码也没有匹配上(可能是xxxx用的sqlite版本和我本地的不一样,也有可能是编译器翻译成机器码不一样,总之是没匹配上),和刚才那个一样,暂时方式,继续找其他函数;


sqlite3_sleep: 既然是sleep,肯定涉及到时间的计算;从源码看,有几行比较明显,比如下面的这行:有乘法,先转换成毫秒,再除以1000,所以先根据69 45 08 E8 03 00 00 这一串特征码在IDA中查找:


rc = (sqlite3OsSleep(pVfs, 1000ms)/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


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搜查,一路跟踪到这里:从参数来看,实锤就是这里了;


先把函数名改了,再往上层层追溯,再和源代码比对,发现基址在这:0xA885D0;


sqlite3_close:从C源码看,是直接调用了sqlite3Close,遂进入sqlite3Close函数,发现了一个字符串:unable to close due to unfinalized;如法炮制,继续用这个字符串在IDA里面找: 从参数和函数调用来看,确实是这里,实锤了!


往上找到函数入口,函数名改为sqlite3Close;这个函数又被调用了好多次,只有标红的这两个最接近入口,和在C源码看到的接近,先看看这两个函数:


第一个:从对比来看应该就是sqlite3_close了,在IDA中标记,并记录下偏移: 0xA871F0;IDA中紧接这下面就是sqlite3_close_v2,也顺便标记下!


至此,还有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,如下:


我们顺着先看前两个函数: 这两个函数代码几乎是一样的,不同的仅仅是返回值,分别是【eax+20h】和【eax+24h】;没有其他任何代码了,看起来和sqlite3_backup_remaining、sqlite3_backup_pagecount很像,那么这两个是不是了? 就需要进一步验证参数了!


参数类型如下:非常凑巧的是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这两个函数没找到了;既然这个函数自身没有字符串,特征码也不对,那我们先在源码看看这些函数都在哪些地方被引用了,说不定能从这些引用的函数找到突破口了!很明显红框那个才是引用,其他的都是申明或我们自己的代码;


进入引用,发现一个有趣的现象: 我们还缺的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


//代码效果参考:http://hnjlyzjd.com/hw/wz_25152.html

* 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));


}</

相关文章
|
2天前
|
存储 SQL 关系型数据库
|
2天前
|
SQL 缓存 负载均衡
数据库设计优化:性能提升与扩展性的技术探讨
【6月更文挑战第28天】数据库设计优化聚焦性能与扩展性:SQL优化、索引策略、缓存利用及分库分表、集群技术,旨在平衡处理速度与系统稳定性。通过智能SQL、复合索引、查询缓存减少数据库压力,垂直/水平拆分与集群实现数据分布式处理,提升并发能力。
|
1天前
|
存储 Java Linux
SQLite3数据库的安装与使用教程
SQLite3数据库的安装与使用教程
|
2天前
|
数据库
|
2天前
|
存储 NoSQL 关系型数据库
技术心得:常用数据库有哪些
技术心得:常用数据库有哪些
|
2天前
|
SQL 缓存 Java
必知的技术知识:hsql数据库使用详解(入门)及快速使用
必知的技术知识:hsql数据库使用详解(入门)及快速使用
|
3天前
|
存储 关系型数据库 MySQL
|
2天前
|
存储 关系型数据库 MySQL
|
3天前
|
SQL 运维 关系型数据库
|
3天前
|
存储 关系型数据库 MySQL