全局钩子DLL注入

全局钩子注入介绍背景

Windows下的应用程序大部分都是基于消息机制的,它们都会有一个消息过程函数,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制就是用来截获和监视这些系统中的消息。

按照钩子作用的范围不同,又可以分为局部钩子和全局钩子。局部钩子是针对某个线程的;而全局钩子则是作用于整个系统中基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。

接下来,本文将介绍利用全局钩子来进行DLL注入。

主要步骤

1.在DLL中设置钩子回调函数、设置全局钩子和卸载钩子三个模块

(1)设置全局钩子

既然叫全局钩子那么就要作用于整个系统的基于消息的应用,我们知道进程的地址空间都是相互独立的,之间互不影响,那么发生对应事件的进程不能调用其他进程地址空间的钩子函数,所以钩子函数必须在一个DLL中,这样当成功设置全局钩子之后,只有进程产生设置对应的事件的时候系统才会将指定的DLL模块加载到该进程中,实现DLL注入。

为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子,这是因为所有进程都会有一个消息队列,当与系统有消息交互的时候,都会加载WH_GETMESSAGE类型的全局钩子DLL。

将程序定义的钩子函数安装到挂钩链中需要用到SetWindowsHookEx函数

函数声明

HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

idHook [in]
要安装的钩子程序的类型。 该参数可以是以下值之一。

VALUEMEANING
WH_CALLWNDPROC安装钩子程序,在系统将消息发送到目标窗口过程之前监视消息。
WH_CALLWNDPROCRET安装钩子程序,在目标窗口过程处理消息后监视消息。
WH_CBT安装接收对CBT应用程序有用的通知的挂钩程序。
WH_DEBUG安装可用于调试其他挂钩程序的挂钩程序。
WH_FOREGROUNDIDLE安装将在应用程序的前台线程即将变为空闲时调用的钩子过程。该挂钩对于在空闲时执行低优先级任务很有用。
WH_GETMESSAGE安装一个监视发送到消息队列的消息的挂钩过程。
WH_JOURNALPLAYBACK安装一个挂钩过程,用于发布先前由WH_JOURNALRECORD挂钩过程记录的消息。
WH_JOURNALRECORD安装一个挂钩过程,记录发布到系统消息队列的输入消息。这个钩子对于录制宏很有用。
WH_KEYBOARD安装监视按键消息的挂钩过程。
WH_KEYBOARD_LL安装监视低级别键盘输入事件的挂钩过程。
WH_MOUSE安装监视鼠标消息的挂钩过程。
WH_MOUSE_LL安装监视低级别鼠标输入事件的挂钩过程。
WH_MSGFILTER安装钩子程序,用于监视在对话框,消息框,菜单或滚动条中由于输入事件而生成的消息。
WH_SHELL安装接收对shell应用程序有用的通知的挂钩程序。
WH_SYSMSGFILTER安装钩子程序,用于监视在对话框,消息框,菜单或滚动条中由于输入事件而生成的消息。钩子程序监视与调用线程相同的桌面中的所有应用程序的这些消息。

设置WH_GETMESSAGE全局钩子具体实现的代码如下:

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

第一个参数设置为WH_GETMESSAGE,它可以监视发送到消息队列的消息,第二个参数表示钩子回调函数,第三个参数表示包含钩子回调函数的DLL模块句柄,即我们需要DLL注入的那个DLL;第四个参数直接设为0表示为全局钩子关联所有线程,返回值是钩子句柄HHOOK类型。

(2)设置回调函数

回调函数即是钩到进程时需要注入的函数,当成功设置全局钩子后,只有进程有消息发送到消息队列中时,系统才会自动将指定的DLL模块加载到进程中实现DLL注入。

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

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

注意一定要用CallNextHookEx函数让钩子传递,不然在一个钩子响应之后在钩子链中位于它之前的钩子们无法相应,这样就做不到全局注入。

当钩子不再使用,可以对钩子进行卸载操作。卸载全局钩子,那么已经加载包含钩子回调函数的DLL模块的进程,将会释放DLL模块。那么,卸载全局钩子的具体实现代码如下所示。

(3)卸载全局钩子

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

上述代码中,UnsetGlobalHook函数用来卸载指定钩子,参数便是卸载钩子的句柄。卸载成功后,所有加载了全局钩子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数据段设置为可读、可写、可共享的共享数据段。

 

2.创建进程控制DLL注入

#include<Windows.h>

int main()
{
	typedef BOOL(*typedef_SetGlobalHook)();
	typedef BOOL(*typedef_UnsetGlobalHook)();
	HMODULE hDll = NULL;
	typedef_SetGlobalHook SetGlobalHook = NULL;
	typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
	BOOL bRet = FALSE;

	do 
	{
		hDll = ::LoadLibrary("Global_Dll.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;
}

测试

本文创建一个名为GlobalHook_Test的DLL工程项目,将上述的设置钩子、钩子回调函数以及卸载钩子的代码均编写在同一DLL中,并对设置钩子以及卸载钩子部分的函数进行导出,方便外部程序的调用。然后,创建名为Test的控制台项目,对GlobalHook_Test.dll进行加载调用,测试全局钩子注入。测试流程如下所示。

首先,运行Test.exe程序加载GlobalHook_Test.dll并调用导出函数设置全局钩子,提示钩子创建成功后,使用进程查看器ProcessExplorer.exe查看进程520.exe进程的加载模块,发现加载了GlobalHook_Test.dll模块,说明DLL注入成功。

继续执行Test.exe程序卸载全局钩子,提示钩子卸载成功后,再使用进程查看器ProcessExplorer.exe查看进程520.exe进程的加载模块,发现GlobalHook_Test.dll模块已经不存在了,说明DLL释放成功。

小结

主要通过调用SetWindowsHookEx函数设置全局钩子,完成DLL注入。通过调用CallNextHookEx函数传递钩子,让进程继续运行。通过调用UnhookWindowsHookEx函数卸载钩子,实现DLL释放。

在调用SetWindowsHookEx函数设置全局钩子的时候,一定要将钩子回调函数编写在DLL模块当中,并指定该DLL模块的句柄。

通过在DLL中通过#pragma data_seg()指令创建共享内存,那么,加载了该DLL的进程,共享一块内存,只要其中一个进程修改了内存区域的数据,其他进程对应内存区域的数据也会改变。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值