写在前面
上一篇文章,实现了定位保存微信数据库句柄的容器和微信内部的sqlite3_exec函数地址,这一篇文章,尝试使用得到的数据库句柄和sqlite3_exec,来查询数据库中的内容。
回顾
首先回顾一下上篇文章:PC微信逆向–定位sqlite3_exec和数据库句柄得到的结果,IDA中sqlite3_exec的地址是0x11356570
,对应的偏移是0x11356570 - 0x10000000 = 0x1356570
,对应微信中的地址:WeChatWin.dll + 0x1356570
保存数据库句柄的容器首地址:[[WeChatWin.dll + 0x222F3FC] + 0x1888]
保存数据库句柄的容器尾地址:[[WeChatWin.dll + 0x222F3FC] + 0x188C]
sqlite3_exec
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
sqlite3*:通过sqlite3_open打开的数据库句柄
const char *sql:要执行的sql语句
int (*callback)(void*,int,char**,char**):执行sql语句时对应的回调函数
void *:回调函数的参数
char **errmsg:存放错误信息
上一篇文章只关注了参数个数,以及默认调用约定,这一次需要理解每个参数的作用,才能在调用的时候不出错。
第一个参数是数据库句柄,已经拿到了。
第二个参数是要执行的SQL语句,为了必定执行成功,以下面这句作为测试:
select * from sqlite_master where type='table';
第三个参数是回调函数,每查询出一条数据,都会调用一次回调函数,毫无疑问,这个参数是最重要的,等下单独讲解。
第四个参数是传递给回调函数的参数,这是一个void*参数,必须在回调函数里面解引用,如果不需要,可以写0。
第五个参数是错误信息,这个对我们来说不重要,可以写0。
最后还要说一下返回值,如果SQL执行成功,sqlite3_exec返回0,如果失败,返回非0值。
回调函数
来看一个官方的例子:
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
第一个参数是为sqlite3_exec设置的第四个参数。
第二个参数是本次查询得到的字段个数。
第三个参数是char**
,可以将其理解为保存char*
的容器,所有成员都是char*
类型,实际上保存的是字段的值。
第四个参数和第三个参数类似,并且成员一一对应,保存的是字段名。
函数指针
这个就很简单了,用typedef声明一个函数指针,并且解引用微信中的sqlite3_exec,就在不引入sqlite3库的情况下得到了sqlite3_exec函数:
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 */
);
这段代码是我抄来的,可能有人会问,第一个参数,应该是sqlite3*
类型,这里改成DOWRD
,会不会有问题。在32位汇编中,指针(不管是void*
还是其他什么*
或者**
)和DWORD
都占用4个字节,对于目标函数来说,都是地址而已,只要结构正确,就不会有问题。(如果理解有误望大佬指正)。
此外,还需要sqlite3_callback
函数指针:
typedef int(*sqlite3_callback)(
void*,
int,
char**,
char**
);
编写代码
终于到了万众瞩目的写代码环节,要实现两个功能,一个是DLL,进入微信内部干活,另外一个是注入程序,帮助DLL进入微信内部干活。
注入的DLL
相信这些代码你一看就能懂:
sqlite_exec.h
#pragma once
#include<windows.h>
#include<iostream>
#include<vector>
using namespace std;
void __stdcall execute();
sqlite_exec.cpp
#include "pch.h"
#include "sqlite_exec.h"
#define sqlite3_exec_offset 0x1356570
#define db_handles_base_offset 0x222F3FC
// 回调函数指针
typedef int(*sqlite3_callback)(
void*,
int,
char**,
char**
);
// sqlite3_exec函数指针
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 */
);
// 保存数据库句柄和数据库名的结构体
struct dbStruct {
DWORD dbhandle;
wchar_t* dbname;
};
// 保存数据库信息的容器
vector<dbStruct> dbhandles;
// 创建一个Console窗口,方便观察输出结果
BOOL CreateConsole(void) {
if (AllocConsole()) {
AttachConsole(GetCurrentProcessId());
FILE* retStream;
freopen_s(&retStream, "CONOUT$", "w", stdout);
if (!retStream) throw std::runtime_error("Stdout redirection failed.");
freopen_s(&retStream, "CONOUT$", "w", stderr);
if (!retStream) throw std::runtime_error("Stderr redirection failed.");
return 0;
}
return 1;
}
// 获取WeChatWin.dll的基址
DWORD GetWeChatWinBase() {
DWORD WeChatWinBase = (DWORD)GetModuleHandle(L"WeChatWin.dll");
return WeChatWinBase;
}
// 获取数据库句柄和数据库名并存入容器
void GetHandles() {
DWORD WeChatWinBase = GetWeChatWinBase();
DWORD SqlHandleBaseAddr = *(DWORD*)(WeChatWinBase + db_handles_base_offset);
DWORD SqlHandleBeginAddr = *(DWORD*)(SqlHandleBaseAddr + 0x1888);
DWORD SqlHandleEndAddr = *(DWORD*)(SqlHandleBaseAddr + 0x188C);
wstring dbnames = L"";
while (SqlHandleBeginAddr < SqlHandleEndAddr) {
DWORD dwHandle = *(DWORD*)SqlHandleBeginAddr;
SqlHandleBeginAddr += 0x4;
// 做一下简单的去重
if (dbnames.find((wchar_t*)(*(DWORD*)(dwHandle + 0x78)), 0) != wstring::npos)
continue;
dbStruct db = { 0 };
db.dbname = (wchar_t*)(*(DWORD*)(dwHandle + 0x78));
dbnames += (wchar_t*)(*(DWORD*)(dwHandle + 0x78));
db.dbhandle = *(DWORD*)(dwHandle + 0x64);
dbhandles.push_back(db);
}
}
// 回调函数的实现,官方例子未做改动
static int callback(void* NotUsed, int argc, char** argv, char** azColName) {
int i;
for (i = 0; i < argc; i++) {
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
// 由dllmain调用的入口函数
void __stdcall execute() {
CreateConsole();
const char* sql = "select * from sqlite_master where type=\"table\";";
DWORD WeChatWinBase = GetWeChatWinBase();
Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)(WeChatWinBase + sqlite3_exec_offset);
GetHandles();
for (unsigned int i = 0; i < dbhandles.size(); i++) {
printf("dbname: %ws\n", dbhandles[i].dbname);
p_Sqlite3_exec(dbhandles[i].dbhandle, sql, (sqlite3_callback)callback, 0, 0);
}
}
dllmain.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "sqlite_exec.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
// 程序注入后,会执行到此处
execute();
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
注入程序
本来想发个工具,但还要弄网盘啥的,还是抄一点代码吧,轻松加愉快。
#include <iostream>
#include <windows.h>
using namespace std;
// 注入DLL到微信的进程空间
bool InjectDll(DWORD dwId, WCHAR* szPath)//参数1:目标进程PID 参数2:DLL路径
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
/*
【1.2 在目标进程的内存里开辟空间】
*/
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE);
//二、 把dll的路径写入到目标进程的内存空间中
DWORD dwWriteSize = 0;
/*
【写一段数据到刚才给指定进程所开辟的内存空间里】
*/
if (pRemoteAddress)
{
WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath) * 2 + 2, &dwWriteSize);
}
else {
printf("写入失败!\n");
return 1;
}
//三、 创建一个远程线程,让目标进程调用LoadLibrary
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, NULL, NULL);
if (hThread) {
WaitForSingleObject(hThread, -1);
}
else {
printf("调用失败!\n");
return 1;
}
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 0;
}
// 目标进程卸载DLL,释放Console窗口
BOOL RemoveDll(DWORD dwId,wchar_t* dllname) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE);
DWORD dwWriteSize = 0;
HANDLE hThread = NULL;
DWORD dwHandle, dwID;
LPVOID pFunc = NULL;
if (pRemoteAddress)
WriteProcessMemory(hProcess, pRemoteAddress, dllname, wcslen(dllname) * 2 + 2, &dwWriteSize);
else {
printf("写入失败!\n");
return 1;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetModuleHandleW, pRemoteAddress, 0, &dwID);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, &dwHandle);
}
else {
printf("GetModuleHandleW调用失败!\n");
return 1;
}
CloseHandle(hThread);
// 释放console窗口,不然关闭console的同时微信也会退出
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeConsole, NULL, 0, &dwID);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
}
else {
printf("FreeConsole调用失败!\n");
return 1;
}
CloseHandle(hThread);
// 使目标进程调用FreeLibrary,卸载DLL
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)dwHandle, 0, &dwID);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
}
else {
printf("FreeLibrary调用失败!\n");
return 1;
}
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 0;
}
// 获取当前工作目录,拼接DLL绝对路径
wchar_t* GetDllPath(wchar_t* dllname) {
wchar_t* dllpath = new wchar_t[MAX_PATH];
wchar_t workPath[MAX_PATH];
wchar_t* pworkPath = _wgetcwd(workPath, MAX_PATH);
swprintf_s(dllpath, MAX_PATH, L"%ws%ws%ws", pworkPath, L"\\", dllname);
return dllpath;
}
// 主函数
int main(int nargv, WCHAR* argvs[])
{
HWND hCalc = FindWindow(NULL, L"微信");
DWORD dwPid = 0;
DWORD dwRub = GetWindowThreadProcessId(hCalc, &dwPid);
if (!dwPid) {
printf("%s\n","请先启动目标进程!");
return 1;
}
wchar_t* dllname = (wchar_t*)L"DbTest.dll";
wchar_t* dllpath = GetDllPath(dllname);
InjectDll(dwPid, dllpath);
delete[] dllpath;
dllpath = NULL;
system("pause");
RemoveDll(dwPid, dllname);
return 0;
}
平台配置为win32(x86),笔者编译环境为VS 2019社区版。
输出结果
写在后面
本篇文章使用上一篇文章:PC微信逆向–定位sqlite3_exec和数据库句柄找到的sqlite3_exec函数地址和数据库句柄,成功绕过加密执行了SQL,下一篇文章,介绍如何寻找微信内部的sqlite3 API,为在线备份做铺垫。