注入技术-全局钩子注入

本文最后更新于:2021-08-09 晚上

全局钩子注入

windows中大部分应用程序都是基于消息机制的,每个进程都有自己的消息队列。

局部钩子是针对某个线程的,全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL文件中实现相应的钩子函数。

函数介绍

SetWindowsHookEx函数

将程序定义的钩子函数安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程相关联。

1
2
3
4
5
6
HHOOK SetWindowsHookExA(
int idHook,
HOOKPROC lpfn,
HINSTANCE hmod,
DWORD dwThreadId
);
参数

idHook:要安装的钩子程序的类型,该参数具体可以见下表。

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

lpfn:一个指向钩子程序的指针。如果dwThreadId参数为0或指定由不同进程创建线程标识符,则lpfn参数必须指向DLL中的钩子过程。否则,lpfn可以指向与当前进程关联的代码中的钩子过程。

hMod:包含由lpfn参数指向的钩子过程的DLL句柄。如果dwThreadId参数指定由当前进程创建线程,并且钩子过程位于与当前进程关联的代码中,则hMod参数必须设置为NULL。

dwThreadId:与钩子程序关联的线程标识符。如果此参数为0,则钩子过程与系统中所有线程相关联。

返回值

如果函数成功,则返回值是钩子过程的句柄。

如果函数失败,则返回值为NULL。

实现过程

创建全局钩子,钩子函数需要在一个DLL文件中。进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子的实现在DLL中的话,则在对应事件发生的时候,系统会把这个DLL加载到发生事件的进程地址空间之中,使它能够调用钩子函数进行处理。创建一个全局钩子之后,在对应事件发生的时候,系统就会把DLL加载到发生事件的进程中,这样就实现了DLL注入。

设置idHook的值为WH_GETMESSAGE就可以让DLL注入到所有的进程中,因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动的,所有进程都会有一个自己的消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL。

DLL文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
//共享内存
#pragma data_seg("mydata")
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
HMODULE g_hDllModule = NULL;
HHOOK g_hHook = NULL;
// 钩子回调函数
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;
}
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;
}

全局钩子是以DLL形式加载到其他进程空间之中的,且进程都是独立的,任意修改其中的一个内存里的数据是不会影响另一个进程的。所在DLL中创建了共享内存,共享内存是指突破进程独立性,多个进程共享同一段内存。在DLL中创建共享内存,就是在DLL之中创建一个变量,然后将DLL加载到多个进程空间,只要一个进程修改了该变量值,其他进程DLL中的这个值也会改变,就相当于多个进程共享一个内存。

编写一个调用DLL的程序,test.exe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#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;
printf("按下回车开始设置钩子\n");
getchar();
do
{
hDll = LoadLibrary(L"Hook.dll");
if (NULL == hDll)
{
printf("加载DLL失败\n错误代码%d\n", GetLastError());
break;
}
SetGlobalHook = (typedef_SetGlobalHook)GetProcAddress(hDll, "SetGlobalHook");
if (NULL == SetGlobalHook)
{
printf("获取函数地址失败\n错误代码%d\n", GetLastError());
break;
}
bRet = SetGlobalHook();
if (bRet)
{
printf("设置钩子成功\n");
}
else
{
printf("设置钩子失败\n");
}
system("pause");
UnsetGlobalHook = (typedef_UnsetGlobalHook)GetProcAddress(hDll, "UnsetGlobalHook");
if (NULL == UnsetGlobalHook)
{
printf("获取函数地址失败\n错误代码%d\n", GetLastError());
break;
}
UnsetGlobalHook();
printf("卸载钩子成功\n");
} while (FALSE);
system("pause");
}

效果查看

可以先试用PC hunter工具进行查看,可以看到当前没有任何消息钩子存在。

打开我们的test.exe,设置好钩子之后,再进行查看,刷新一下消息钩子列表,如图可以看到已经存在了一个消息钩子。

使用OD附加打开notepad.exe,再打开模块窗口。

再打开test.exe,进行钩子设置。可以很明显看到变化,已经注入成功了。

参考

参考《Windows黑客编程技术详解》一书


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!