实现过程
如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件的进程地址空间中,使它能够调用钩子函数进行处理。
在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会由操作系统自动或强行地加载到该进程中。因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把DLL加载到发生事件的进程中,这样,便实现了DLL注入。
为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子。因为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模块句柄,如果要设置全局钩子,则该参数必须指定DLL模块句柄。第四个参数表示与钩子关联的线程ID,0表示为全局钩子,它关联所有线程。返回值是钩子的句柄,这个值需要保存,因为回调钩子函数以及卸载钩子都需要用到该句柄作为参数。
当成功设置全局钩子之后,只有进程有消息发送到消息队列中,系统才会自动将指定的DLL模块加载到进程中,实现DLL注入。
// 钩子回调函数
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;
}
UnsetGlobalHook函数用来卸载指定钩子,参数便是卸载钩子的句柄。卸载成功后,所有加载了全局钩子DLL模块的进程,都会释放该DLL模块。
总结
主要通过调用SetWindowsHookEx函数设置全局钩子,完成DLL注入。通过调用CallNextHookEx函数传递钩子,使得进程继续运行。通过调用UnhookWindowsHookEx函数卸载钩子,实现DLL释放。在调用SetWindowsHookEx函数设置全局钩子的时候,一定要将钩子回调函数编写在DLL模块中,并指定该DLL模块的句柄。在DLL中利用#pragma data_seg指令创建共享内存,加载该DLL的进程,共享内存。只要一个进程修改了内存中的数据,则其他进程对应内存的数据也会改变。