写在前面

上一篇文章,实现了定位保存微信数据库句柄的容器和微信内部的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,为在线备份做铺垫。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐