1、函数准备
通过查阅资料可知,实现消息钩子的核心函数是SetWindowsHookEx() ,通过查阅官方文档,可知:
HHOOK SetWindowsHookExA(
[in] int idHook, //要安装的挂钩过程的类型
[in] HOOKPROC lpfn, //指向挂钩过程的指针
[in] HINSTANCE hmod, //包含 lpfn 参数指向的挂钩过程的 DLL 的句柄
[in] DWORD dwThreadId //要与挂钩过程关联的线程的标识符
);
具体如下:
idHook有许多取值,可以看到2是我需要的:
剩下3个参数的解释:
根据上图红框内的提示可知,还有一个核心的函数:
LRESULT CALLBACK KeyboardProc(
_In_ int code, //决定hook如何处理消息,可取0、3
_In_ WPARAM wParam, //击键对应系统中的虚拟代码,如Tab键对应0x09
_In_ LPARAM lParam //击键消息的标志位们
);
IParam:
我们想要在击键松开的时候拦截消息,所以IParam对应的值为0x80000000。
根据官方文档,安装钩子需要两个必要的部分:一个安装钩子的程序和一个全局钩子处理DLL。
2、编写DLL
DLL部分实现hook到消息之后,如何对消息进行处理。
DLL可以定义两类函数,导出函数和内部函数。导出函数可以被其他模块调用,也可以在定义它们的DLL中调用,而内部函数只能在定义它们的DLL中调用。
通过官方的example,可以看到对键盘进行拦截,核心就是实现KeyboardProc()函数:
在VS2022中新建一个DLL项目名为HookKeyBoard,在属性中c/c++->预编译中选择不使用编译头。在DLLMAIN.cpp中写入如下代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include<stdio.h>
#include<windows.h>
#define HOOK_PROCESS_NAME "notepad.exe"
HINSTANCE hInstance = NULL;
HHOOK hHook = NULL;
HWND hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID IpvReserved)//DLL入口
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = { 0, };
CHAR* p = NULL;
if (!(lParam & 0x80000000))//释放按键时
{
GetModuleFileNameA(NULL, szPath, MAX_PATH );//获取当前可执行程序或DLL的路径
p = strrchr(szPath, '\\');
//比较进程名称是否为记事本
if (!_stricmp(p + 1, "notepad.exe"))
{
return 1;
}
}
//不是记事本则传递给下一个hook
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void HookStart()
{
hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hInstance, 0);
}
extern "C" __declspec(dllexport) void HookStop()
{
if (hHook)
{
UnhookWindowsHookEx(hHook);
hHook = NULL;
}
}
Hookstart和Hookstop不是必须的,这里只是做了封装,直接给应用程序提供调用接口。
生成解决方案后,可在如下位置找到.dll文件:
3、编写安装程序
安装应用程序必须具有 DLL 模块的句柄,然后才能安装挂钩。要检索 DLL 模块的句柄,需使用 DLL 的名称调用 LoadLibrary函数。获取句柄后,可以调用 GetProcAddress 函数来检索指向挂钩过程的指针。最后,使用 SetWindowsHookEx 将挂钩过程地址安装在相应的挂钩链中。SetWindowsHookEx 传递模块句柄、指向挂钩过程入口点的指针和线程标识符的 0,指示挂钩过程应与调用线程位于同一桌面中的所有线程相关联。
官方示例如下:
新建一个控制台项目,我编写的程序如下:
//#include<stdio.h>
//#include<conio.h>
//#include<windows.h>
//
//HHOOK hHook = NULL;
//HINSTANCE hInstance = NULL;
//
//int main()
//{
// HMODULE hDll = NULL;
// char ch = 0;
//
// //加载dll
// hDll = LoadLibraryA("HookKeyBoard.dll");
// if (hDll == NULL) exit(0);
// //获取导出函数的地址
//
// FARPROC KeyboardProc = (FARPROC)GetProcAddress(hDll, "KeyboardProc");
//
// //开始hook
// hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, hInstance, 0);
//
// //输入Q退出hook
// printf("输入q退出hook!\n");
// while (1)
// {
// char h = _getch();
// _putch(h);
// if (h == 'q')
// break;
// }
// //结束hook
// if (hHook)
// {
// UnhookWindowsHookEx(hHook);
// hHook = NULL;
// }
// //卸载dll
// FreeLibrary(hDll);
// return 0;
//}
#include<stdio.h>
#include<conio.h>
#include<windows.h>
#define HOOK_START "HookStart"
#define HOOK_STOP "HookStop"
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();
int main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;
//加载dll
hDll = LoadLibraryA("HookKeyBoard.dll");
//获取导出函数的地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, HOOK_START);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, HOOK_STOP);
//开始hook
HookStart();
//输入q退出hook
printf("输入q退出hook!\n");
while (1)
{
char h = _getch();
_putch(h);
if (h == 'q')
break;
}
//结束hook
HookStop();
//卸载dll
FreeLibrary(hDll);
return 0;
}
将第二步中生成的.dll复制到项目目录下,运行。
4、效果
可以看到,记事本中无法键入内容
而其他软件成功键入(word不行,推测是调用了记事本?)
输入q退出,恢复。