Loading

第三章 注入技术---全局钩子注入

一、全局钩子注入

windows中的大部分应用程序都是基于消息机制的,根据不同的消息完成不同的功能。 钩子机制就是由windows操作系统提供的可以用来截获和监视系统中这些消息的。
钩子又可以分为局部钩子和全局钩子。其中局部钩子是针对某个线程的,全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。

二、API介绍

SetWindowsHookEx函数
https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexa?redirectedfrom=MSDN

HHOOK WINAPI SetWindowsHookEX(
    _In_ int idHook, //要安装的钩子程序的类型
    _In_ HOOKPROC lpfn, //一个指向钩子程序的指针,如果dwThreadId=0或者指定不同进程创建线程,则
                        //lpfn必须指向DLL中的钩子过程,否则lpfn可以指向与当前进程关联的代码函数
    _In_ HINSANCE hMod, //包含由lpfn参数指向的函数的DLL句柄,如果是当前进程则hMod必须为NULL
    _In_ DWORD dwThreadId) //与钩子程序关联的线程标识符。如果为0,则与所有系统中的线程相关联
 
函数成功返回钩子函数的句柄,失败返回NULL

三、实现过程

如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件的进程地址空间中,使它能够调用钩子函数进行处理。
因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动的,所以所有进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL。

//设置全局钩子
BOOL SetGlobalHook() {
	g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
	if (NULL == g_hHook) {
		return FALSE;
	}
	return TRUE;

在上述代码中,SetWindowsHookEx的第一个参数表示钩子的类型,WH_GETMESSAGE表示安装消息队列的消息钩子,它可以监视发送到消息队列的消息。第二个参数表示钩子回调函数。第三个表示钩子回调函数的DLL模块句柄。第四个参数表示与钩子关联的线程ID,0表示全局钩子,它关联所有线程。

//钩子回调函数
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
	return ::CallNextHookEx(g_hHook, code, wParam, lParam);

上述回调函数的参数和返回值的类型是固定的。其中,CallNextHookEx函数表示将当前钩子传递给钩子链中的下一个钩子,第一个参数要指定当前钩子的句柄。如果直接返回0,则表示中断钩子传递,对钩子进行拦截。

//卸载钩子
BOOL UnsetGlobalHook() {
	if (g_hHook) {
		UnhookWindowsHookEx(g_hHook);
	}
	return TRUE;
}

卸载成功后,所有加载了全局钩子DLL模块的进程,都会释放该DLL模块。
全局钩子是以DLL形式加载到其他进程空间中的,而且进程都是独立的,所以任意修改其中一个内存里的数据是不会影响另一个进程的。那么,如何将钩子句柄传递给其他进程呢?为了解决这个问题,本节采用的方式是在DLL中,创建共享内存。
共享内存是指突破进程独立性,多个进程共享同一段内存。在DLL中创建共享内存,就是在DLL中创建一个变量,然后将DLL加载到多个进程空间,只要一个进程修改了该变量值,其他进程DLL中的这个值也会改变,就相当于多个进程共享一个内存。
共享内存的实现原理
首先为DLL创建一个数据段,然后再对程序的链接器进行设置,把指定的数据段链接为共享数据段。这里就可以成功地创建共享内存了。

//共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker,"/SECTION:mydata,RWS")

在上面的代码中,使用#pragma data_seg创建了一个名为"mydata"的数据段,然后使用/SECTION:mydata,RWS把mydata数据段设置为可读。可写、可共享的共享数据段。

四、实现代码如下

001项目,生成001.dll
dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "framework.h"

HMODULE g_hDllModule = NULL;


BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	{
		g_hDllModule = hModule;
		break;
	}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

export.cpp

#include "framework.h"
extern HMODULE g_hDllModule;
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")

extern "C" {
	__declspec(dllexport) LRESULT GetMsgProc(int code,WPARAM wParam,LPARAM lParam);
	__declspec(dllexport) BOOL SetGlobalHook();
	__declspec(dllexport) BOOL UnsetGlobalHook();
}
// 钩子回调函数
LRESULT GetMsgProc(
	int code,
	WPARAM wParam,
	LPARAM lParam)
{
	return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}


// 设置全局钩子
BOOL SetGlobalHook()
{
	g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
	if (NULL == g_hHook)
	{
		return FALSE;
	}
	return TRUE;
}

// 卸载钩子
BOOL UnsetGlobalHook()
{
	if (g_hHook)
	{
		::UnhookWindowsHookEx(g_hHook);
	}
	return TRUE;
}

002项目,实现全局dll注入
002.cpp

#include <Windows.h>
#include<tchar.h>
#include<stdio.h>

int _tmain(int argc, _TCHAR* argv[]) {
	typedef BOOL(*typedef_SetGlobalHook)();
	typedef BOOL(*typedef_UnsetGlobalHook)();
	HMODULE hDll = NULL;
	typedef_SetGlobalHook SetGlobalHook = NULL;
	typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
	BOOL bRet = FALSE;

	do
	{
		hDll = LoadLibraryW(TEXT("001.dll"));
		if (NULL == hDll)
		{
			printf("LoadLibrary Error[%d]\n", ::GetLastError());
			break;
		}
		SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
		if (NULL == SetGlobalHook)
		{
			printf("GetProcAddress Error[%d]\n", ::GetLastError());
			break;
		}
		bRet = SetGlobalHook();
		if (bRet)
		{
			printf("SetGlobalHook OK.\n");
		}
		else
		{
			printf("SetGlobalHook ERROR.\n");
		}

		system("pause");

		UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
		if (NULL == UnsetGlobalHook)
		{
			printf("GetProcAddress Error[%d]\n", ::GetLastError());
			break;
		}
		UnsetGlobalHook();
		printf("UnsetGlobalHook OK.\n");

	} while (FALSE);

	system("pause");
	return 0;
}

五、小结

主要通过调用SetWindowsHookEx函数设置全局钩子,完成DLL注入。通过调用CallNextHookEx函数传递钩子,使得进程继续运行。通过调用UnhookWindowsHookEx函数卸载钩子,实现DLL释放。
在调用SetWindowsHookEx函数设置全局钩子的时候,一定要将钩子回到函数编写在DLL模块中,并指定该DLL模块的句柄。
在DLL中利用#pragma data_seg指令创建共享内存,加载该DLL的进程,共享内存。只要一个进程修改了内存中的数据,则其他数据对应内存的数据也会改变。

posted @ 2021-09-04 17:35  Ctrl_C+Ctrl_V  阅读(798)  评论(0)    收藏  举报