Windows消息钩子
API介绍
SetWindowsHookEx
是 Windows API 中用于安装挂钩(Hook)的函数,它允许一个应用程序在特定事件发生时拦截系统消息或输入(例如键盘、鼠标输入),并对其进行处理。通常用于捕获、监控或修改应用程序中的消息流。
1 2 3 4 5 6
| HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId );
|
参数解释:
idHook
(挂钩类型): 表示要安装的挂钩类型,它决定了系统在什么事件发生时调用钩子函数。常见的挂钩类型有:
WH_KEYBOARD
: 捕获键盘消息(不区分按键的目标窗口)。
WH_KEYBOARD_LL
: 捕获低级别的全局键盘消息,可以用于记录按键等。
WH_MOUSE
: 捕获鼠标消息。
WH_MOUSE_LL
: 捕获低级别的全局鼠标消息。
WH_CALLWNDPROC
: 监控发送给目标窗口的消息。
WH_GETMESSAGE
: 监控从消息队列中获取消息的操作。
WH_CBT
: 监控系统中一些特定的事件(如窗口的创建、销毁、最小化、最大化等)。
lpfn
(挂钩回调函数): 挂钩处理程序的指针,定义了挂钩触发时要执行的逻辑。这个回调函数将由系统在挂钩事件发生时调用。其签名通常是这样的:
1 2 3 4 5
| LRESULT CALLBACK HookProc( int nCode, WPARAM wParam, LPARAM lParam );
|
挂钩函数可以对系统消息进行修改或传递给下一个挂钩函数。
hMod
(模块句柄): 指向包含 lpfn
钩子函数的模块句柄(即DLL或EXE的实例句柄)。如果挂钩是在一个动态链接库(DLL)中实现的,就需要提供这个 DLL 的 HINSTANCE
,否则可以传入 NULL
表示当前进程的实例句柄。
- 如果
dwThreadId
参数指定了 0,则 hMod
必须为挂钩所在的 DLL 模块句柄。
- 如果是线程挂钩,则可以传递
NULL
。
dwThreadId
(线程标识符): 指定要挂钩的线程的标识符(Thread ID
)。有以下两种用法:
- 如果指定一个线程 ID,则挂钩只在该线程的上下文中生效,这是一个线程级挂钩。
- 如果设置为 0,则挂钩将在全局范围内生效,处理系统中的所有消息。
返回值:
成功时,返回挂钩的句柄(HHOOK
);失败时,返回 NULL
,可以使用 GetLastError
获取错误信息。
GUI线程
主线程(GUI线程):主线程通常用于处理用户界面相关的逻辑,例如创建和显示窗体、处理用户输入、更新UI等操作。主线程是一个单线程的执行环境,所有的UI控件都必须在主线程上创建和更新,否则会引发跨线程访问的异常。因此,我们应该尽可能地将长时间运行的任务和耗时的操作移动到其他线程上执行,以避免阻塞主线程。
GUI线程与设置Windows消息钩子的关系:
如果SetWindowHookEx
的线程ThreadId参数不是GUI线程的话,将无法完成钩子
原因也非常简单
SetWindowsHookEx
的实现原理与 消息队列 密切相关。它的核心机制是通过拦截和监视系统消息或特定线程的消息,并在消息到达目标窗口过程之前或之后执行自定义的钩子处理函数。
而不是GUI线程的普通线程根本没有消息队列,所以想要利用Windows消息钩子完成注入是不可能的
代码demo:
设置Hook的Exe代码:
首先输入一个线程的ID号,因为如果SetWindowsHookEx
的最后一个参数不是0(全局挂钩),那么就是挂钩指定的线程,在这里我们加载我们写的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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| #include <Windows.h> #include<TlHelp32.h> #include<iostream>
using namespace std;
DWORD getThreadID(ULONG32 ulTargetProcessID); BOOL InjectDllBySetWindowsHook(ULONG32 ulTargetProcessID); bool IsGUIThread(DWORD dwThreadID); DWORD getThreadID(ULONG32 ulTargetProcessID);
BOOL InjectDllBySetWindowsHook(ULONG32 ulTargetProcessID) { HANDLE TargetProcessHandle = NULL; TargetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);
if (NULL == TargetProcessHandle) { printf("Couldn't get Target Process Handle\r\n"); return FALSE; }
HMODULE DllModule = LoadLibrary("Dll.dll");
if (DllModule == NULL) { printf("cannt find dll\r\n"); return FALSE; }
HOOKPROC Sub_1Address = NULL; Sub_1Address = (HOOKPROC)GetProcAddress(DllModule, "MyMessageProcess"); if (Sub_1Address == NULL) { printf("cannt found MyMessageProcess"); return FALSE; }
DWORD ThreadID = getThreadID(ulTargetProcessID);
HHOOK Handle = SetWindowsHookEx(WH_KEYBOARD, Sub_1Address, DllModule, ThreadID);
if (Handle == NULL) { printf("cannt hook\r\n"); return FALSE; } printf("hook success\r\n"); getchar(); getchar(); getchar(); UnhookWindowsHookEx(Handle);
FreeLibrary(DllModule); }
bool IsGUIThread(DWORD dwThreadID) { GUITHREADINFO guiInfo; guiInfo.cbSize = sizeof(GUITHREADINFO); if (GetGUIThreadInfo(dwThreadID, &guiInfo)) { return true; } return false; }
DWORD getThreadID(ULONG32 ulTargetProcessID) { HANDLE Handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (Handle != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(Handle, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { if (te.th32OwnerProcessID == ulTargetProcessID) { if (IsGUIThread(te.th32ThreadID)) { CloseHandle(Handle); cout << "已找到GUI线程" << endl; return te.th32ThreadID; } } } } while (Thread32Next(Handle, &te)); } } CloseHandle(Handle); cout << "没找到GUI线程" << endl; return (DWORD)0; }
int main() { ULONG32 ulTargetProcessID; cout << "请输入目标进程ID:"; cin >> ulTargetProcessID;
if (!InjectDllBySetWindowsHook(ulTargetProcessID)) { cout << "Set Hook Unsuccess!\r\n" << endl; return 0; } cout << "Inject Success!\r\n" << endl; return 0;
}
|
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
| #include <windows.h>
#pragma data_seg(SHARD_SEG_NAME) static HHOOK g_hHook; #pragma data_seg()
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { MessageBox(NULL, L"Inject Success!", L"1", 0);
switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { }
case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
extern "C"
__declspec(dllexport)LRESULT MyMessageProcess(int Code, WPARAM wParam, LPARAM lParam) { MessageBox(NULL, L"GetMessage!", L"Message", 0); return CallNextHookEx(g_hHook, Code, wParam, lParam); }
|
需要注意的点:
来自微软官方文档:
Windows 应用商店应用开发 如果 dwThreadId 为零,则 Windows 应用商店应用进程和 Windows 运行时代理进程不会在进程内加载窗口挂钩 DLL,除非它们由 UIAccess 进程(辅助功能工具)安装。 通知在安装程序的线程上传送这些挂钩:
例如
- Microsoft Edge:
- 照片:
- 进程名:
Microsoft.Photos.exe
。
- 计算器:
- 邮件和日历: