微信Hook实战记录2:动手实现恶意dll内存插入器

4,157 阅读8分钟

Hook任何软件,整体思路都是通过内存调试软件对软件运行时内存进行断点调试,找到想要hook的内存地址,转为可以通过程序主dll可以获得的相对地址,然后再此处插入自己的恶意汇编代码,如果代码比较复杂,还需要编写寄存器保护逻辑,避免自己的恶意代码修改了寄存器中的值后,程序正常的逻辑无法运行。

前言

在上一篇文章「微信Hook实战记录 1:找到个人信息」中,介绍了如何使用OD与CE来找到微信中存放个人信息的内存位置,本篇文章尝试通过C++编写一个简单的内存注入工具,将我们自己的恶意dll注入到微信的内存中。

本文记录了大量细节,完全可以模仿复现相同的效果。

内存注入工具的编写

打开 Visual Studio 2019(下载的时候勾选 C++ 桌面支持,6.9G左右),选择创建新项目

选择创建 Windows 桌面向导

然后创建就可以了,这里创面项目名为 Project1

创建完后,VS 会为我们创建一个默认的界面,我们在 解决方案 -> 源文件 中找到 Project1.cpp,这个文件就是我们要写逻辑的主要界面

将其中多余的代码删除,就留下如下代码

// Project1.cpp : 定义应用程序的入口点。
//

#include "framework.h"
#include "Project1.h"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{
    return 0;
}

wWinMain 是界面程序的入口,所有不必删除

接着将 解决方案 --> 资源文件 中的内容删除,这是 VS 默认创建好的,删除后,自己新建一个资源文件

选择 Dialog,即弹出框

调整一下创建出的按钮位置以及整体大小

要设置按钮与整体的属性,如按钮要设置其内容与ID,而整体Dialog也要设置一下其ID,可以右键点击 --> 属性

其他控制以相同的方式去设置,设置后后,就可以写代码了

首先修改一下 Project1.h 中的内容,如下

#pragma once

#include "resource1.h"

具体内容要看你VS为你生成了什么,前面我们通过图像界面操作后,VS会生成的相应的头文件,这里我生成了 resource1.h 的头文件,其内容如下:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Project1.rc 使用
//
#define ID_MAIN                         101
#define UN_DLL                          1001
#define INJECT_DLL                      1002

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1003
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

#endif

可以看出,其实就是我们设置的ID等内容

接着来修改一下 Project1.cpp, 其 wWinMain 方法修改如下:

#include "framework.h"
#include "Project1.h"
#include <TlHelp32.h>  
#include <stdio.h>

// 微信进程名
#define WECHAT_PROCESS_NAME "WeChat.exe"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{

	// hInstance 将句柄传递给弹窗  
	// 传入 Dialgo 主体ID
	// DialogProc 回调函数,是一个指针,即该函数的地址
	DialogBox(hInstance, MAKEINTRESOURCE(ID_MAIN), NULL, &DialogProc);
	return 0;
}

因为创建的资源是 Dialog ,所以这里通过 DialogBox 来使用它,它在 Windows.h 的头文件中,如果你使用的 VS 2019,其 framework.h 文件就包含Windows.h了。

DialogBox的参数可以通过微软的文档来查看

void DialogBoxA(
   hInstance,
   lpTemplate,
   hWndParent,
   lpDialogFunc
);

hInstance,类型:HINSTANCE,包含对话框模板的模块句柄。如果此参数为 NULL,则使用当前可执行文件。 lpTemplate,类型:LPCTSTR,对话框模板。可以使用MAKEINTRESOURCE宏来创建此值。 hWndParent,类型:HWND,拥有该对话框的窗口的句柄。 lpDialogFunc,类型:DLGPROC,指向对话框过程的指针。

根据文档提示,完成代码

#include "framework.h"
#include "Project1.h"
#include <TlHelp32.h>  
#include <stdio.h>

#define WECHAT_PROCESS_NAME "WeChat.exe"

// 注册函数,方便其他模块使用
INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
VOID InjectDll();


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{

	DialogBox(hInstance, MAKEINTRESOURCE(ID_MAIN), NULL, &DialogProc);
	return 0;
}


INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
	if (uMsg == WM_INITDIALOG) {
		MessageBox(NULL, "首次加载", "标题", 0);
	}
	// 关闭界面操作
	if (uMsg == WM_CLOSE) {
		EndDialog(hwndDlg, 0);
	}

	// 所有的界面上的按钮事件都会走这个宏
	if (uMsg == WM_COMMAND) {
		if (wParam == INJECT_DLL) {
			InjectDll(); //注入 Dll
		}

		if (wParam == UN_DLL) {

		}
	}

	return FALSE;
}

接着来实现 InjectDll() 方法,完成注入 DLL 的功能,整体分 3 步走。

1.获取微信的PID,通过PID获得微信进程的句柄,从而拥有了权限 2.申请内存,内存大小为 DLL 路径大小 3.将 DLL 路径注入到微信进程中

DWORD ProcessNameFindPID(LPCSTR ProcessName)
{
	// #include <TlHelp32.h>
	// 获取进程快照
	HANDLE ProcessAll = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);// 获取整个系统的进程快照
	// 在快照中对比过 processname 进程名称
	PROCESSENTRY32 processInfo = { 0 };
	processInfo.dwSize = sizeof(PROCESSENTRY32);
	do
	{
		// WeChat.exe
		// th32ProcessID 进程ID, szExeFile进程名
		if (strcmp(ProcessName, (char*)processInfo.szExeFile) == 0) {
			return processInfo.th32ProcessID;
		}
	} while (Process32Next(ProcessAll, &processInfo));

	return 0;
}

上述方法,需要 windows 平台 C++ 基础。其实主要就是对 Windows.h 中方法的调用,接着完成如下效果

VOID InjectDll()
{
  // 自己恶意dll的路径
	CHAR pathStr[0x100] = { "D://GetWeChatInfo.dll" };
	// 获取微信PID
	DWORD PID = ProcessNameFindPID(WECHAT_PROCESS_NAME);
	if (PID == 0) {
		MessageBox(NULL, "没有找到微信进程或微信没有启动", "错误", 0);
		return;
	}
	// 通过PID获取句柄 C++中句柄类型为 HANDLE
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
	if (NULL == hProcess) {
		MessageBox(NULL, "进程打开失败,可能权限不足或关闭了微信应用", "错误", 0);
		return;
	}

	//申请内存,内存大小为我们要注入 DLL 的大小
	LPVOID dllAdd = VirtualAllocEx(hProcess, NULL, sizeof(pathStr), MEM_COMMIT, PAGE_READWRITE);
	if (NULL == dllAdd) {
		MessageBox(NULL, "内存分配失败", "错误", 0);
		return;
	}

	// 将 DLL 写入到对应的内存地址中
	if(WriteProcessMemory(hProcess, dllAdd, pathStr, strlen(pathStr), NULL) == 0) {
		MessageBox(NULL, "路径写入失败", "错误", 0);
		return;
	}

	// 创建一块内存用来打印一段文本,方便看效果
	CHAR test[0x100] = { 0 };
	sprintf_s(test, "写入的地址为:%p", dllAdd);
	OutputDebugString(test);

}

完成后,直接编译运行,如果编译后的效果依旧是之前代码的效果,你可以 重新生成解决方案,然后再编译。

点击注入,获得注入的位置,通过 Cheat Engine 来查看该位置的内容

注入dll成功

注入成功后,还需要调用 Kernel32.dll 中的方法,调用该路径下的 dll 就完成 dll的注入了,代码如下:

  // 省略显示前面的代码
  
  // 创建一块内存用来打印一段文本,方便看效果
	CHAR test[0x100] = { 0 };
	sprintf_s(test, "写入的地址为:%p", dllAdd);
	OutputDebugString(test);

	//将地址写入后,通过 Kernel32.dll 调用该地址的 DLL 就完成了
	HMODULE k32 = GetModuleHandle("Kernel32.dll");
	LPVOID loadAdd = GetProcAddress(k32, "LoadLibraryA");
	// 远程执行我们的dll,通过注入的dll地址以及CreateRemoteThread方法让微信进程调用起我们的进程
	HANDLE exec = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadAdd, dllAdd, 0, NULL);
	if (exec == NULL) {
		MessageBox(NULL, "远程注入失败", "错误", 0);
		return;
	}

此时 DLL 就相当于在微信进程中了。

DLL中的函数就可以操作微信进程中的内存了,从而实现获取微信信息的目的。

如果没有调用其DLL,则需要注意一下 LoadLibraryA 方法,该方法会调用 ASCII的路径地址,如果你是unicode的话,则需要调用 LoadLibraryW 方法,如果不成功,可以尝试替换一下。

没有Windows平台开发C++的朋友看完上面代码可能会感到疑惑,为什么将恶意dll路径插入内存后,可以让微信进程自己去载入这个恶意dll呢?

简单解释一下,在windows中,每个进程在启动时都会载入Kernel32.dll,微信进程会载入Kernel32.dll,浏览器进程也会载入Kernel32.dll,而我们可以通过Kernel32.dll中的LoadLibraryA()方法或LoadLibraryW()方法去动态载入新的dll,需要解释一下,我们在自己的进程中获取LoadLibraryA()方法的内存位置,这个内存位置对windows中其他进程而言是相同的。

但关键点是通过微信进程去使用Kernel32.dll中的LoadLibraryA()方法载入新的dll,为了实现这个目的,就需要使用CreateRemoteThread()方法,该方法可以远程调用其他进程的中的方法,前提是要有该进程的句柄(即执行权限),而我们在一开始就获得了微信进程的执行权限。

总结一下整个流程:

  • 1.将恶意dll路径插入到微信进程运行内存中
  • 2.获取当前进程(我们自己的进程,而非微信进程)Kernel32.dll中LoadLibraryA()方法或LoadLibraryW()方法,因为微信进程也载入了Kernel32.dll,所以微信进程可以在相同的内存地址处找到LoadLibraryA()方法或LoadLibraryW()方法
  • 3.通过CreateRemoteThread()方法,让微信进程去调用LoadLibraryA()方法载入恶意dll

结尾

本文并没有给出恶意dll的具体实现逻辑,后面将实现一个简单的dll,来获取微信的个人信息,其实基于上一篇文章,找到了个人信息的具体微信红藕,该dll要做的只是从中读取信息,仅此而已。

但恶意dll还可以实现很多复杂的功能,比如监控聊天信息、自动收红包、自动踢人等各种功能,这些功能在后面有机会再分享给大家。

如果本文对你有帮助,点击「在看」支持二两,下篇文章见。