Hook拦截键盘输入实验
实验环境:
- VS 2022
- Windows 11
- ProcessExplorer
1. 知识储备
(1) hook(消息钩子)
Hook,是Windows消息处理机制的一个平台,应用程序可以在上面设置子程序以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
(2) 相关函数
-
SetWindowsHookEx()
消息钩子注入原理是利用Windows 系统中SetWindowsHookEx()这个API,他可以拦截目标进程的消息到指定的DLL中导出的函数,利用这个特性,我们可以将DLL注入到指定进程中。是所有类型的钩子基本上都需要的基础函数。 -
KeyboardProc()
应用程序应接受来自键盘和鼠标的用户输入。 应用程序以发布到其窗口的消息的形式接收键盘输入。是拦截键盘信息的必备函数。相关定义在:https://learn.microsoft.com/zh-cn/windows/win32/inputdev/about-keyboard-input中可以查阅。
这里给出两个必须要用到的信息首先是传入函数的参量:lParam
如下图所示,是相关信息
以及键盘信息对应的二进制数值,以便于判断并拦截相关输入信息。如下图所示。
2. Dll动态链接库编写
有了以上知识储备开始编写相关动态链接库。在vs2022中创建动态链接库项目。如下图所示,
Dlltest.cpp内容如下:
#include "pch.h"
#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)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = { 0, };
CHAR* p = NULL;
if (!(lParam & 0x80))//释放按键时
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
//比较进程名称
if (!_stricmp(p + 1, HOOK_PROCESS_NAME))
{
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;
}
}
点击生成解决方案,会得到hookkeyboard.lib以及hookkeybboard.dll两个项目。(这两个项目需要保存下来,以便后续自己编写的程序能够成功调用这个我们自己编写的动态链接库)。
3. 相关程序编写
程序编写,没有什么需要特别注意的内容,创建相关win32项目,即可。编写代码如下:
#include<stdio.h>
#include<conio.h>
#include<windows.h>
#define DLL_NAME "HookKeyBoard.dll"
#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(DLL_NAME);
//获取导出函数的地址
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;
}
完成相应编写后,需要将刚刚生成的项目放在现在这个项目的目录下,具体如下图所示。
放入目录后,在属性中添加相关信息。
此时运行这个程序,再打开记事本,向记事本内输入,无法显示(记得调成英文输入,中文输入由于输入法可能拦截不了)。