一、WidnowsHOOK分类
-
系统钩子(System Hooks):系统钩子是最常见的一类钩子,用于监视和修改系统级别的事件。系统钩子可以捕获和处理各种系统事件,如键盘输入、鼠标消息、窗口消息等。系统钩子可以全局范围内生效,可以对整个系统中的事件进行拦截和处理。
-
应用程序钩子(Application Hooks):应用程序钩子是针对特定应用程序的钩子,用于监视和修改特定应用程序的行为。应用程序钩子可以捕获和处理应用程序级别的事件,如窗口消息、菜单消息、定时器消息等。应用程序钩子只对指定的应用程序生效。
-
线程钩子(Thread Hooks):线程钩子是针对特定线程的钩子,用于监视和修改特定线程的行为。线程钩子可以捕获和处理特定线程的事件,如窗口消息、键盘输入等。线程钩子只对指定的线程生效。
-
子类钩子(Subclass Hooks):子类钩子是一种特殊的钩子,用于修改指定窗口类的消息处理函数。通过子类钩子,可以在不修改原始窗口类的情况下,对窗口消息进行自定义处理。
-
WH_CALLWNDPROC钩子:这是一种特殊的系统钩子,用于监视和修改窗口消息的处理过程。WH_CALLWNDPROC钩子可以拦截和处理窗口消息的传递过程,允许对消息进行修改或拦截。
二、设键盘钩子钩子
1. SetWindowsHook可以HOOK类型
WH_KEYBOARD(键盘钩子):用于监视和处理键盘输入事件,可以拦截和修改键盘输入。
WH_MOUSE(鼠标钩子):用于监视和处理鼠标事件,包括鼠标移动、按下和释放鼠标按键等。
WH_KEYBOARD_LL(低级键盘钩子):与WH_KEYBOARD类似,但是是一个低级别的钩子,可以全局拦截键盘输入。
WH_MOUSE_LL(低级鼠标钩子):与WH_MOUSE类似,但是是一个低级别的钩子,可以全局监视鼠标事件。
WH_CALLWNDPROC(窗口消息钩子):用于监视和处理窗口消息,可以拦截和修改窗口消息的传递。
WH_GETMESSAGE(消息钩子):用于监视和处理消息队列中的消息,可以拦截和修改消息的传递。
2. 从零写一个键盘HOOK
首先需要的函数有
SetWindowsHookEx
设置一个HOOK
HHOOK SetWindowsHookExA(
[in] int idHook, //选择HOOK的目标
[in] HOOKPROC lpfn, //回调函数
[in] HINSTANCE hmod, //过于抽象
[in] DWORD dwThreadId //选择一个线程0是全局
);
回调函数
HOOK到之后需要执行的流程
RESULT CALLBACK是一种函数类型,它是Windows API中定义的一种回调函数类型。 参数是由SetWindowsHook给的
RESULT CALLBACK HookTest(
int nCode, //接收事件状态
WPARAM wParam, //wParam表示键盘事件的消息标识符
LPARAM lParam, //按下的键的虚拟键码、扫描码、扩展键标志等。
);
GetMessage
用于从消息队列中获取一个消息
BOOL GetMessage(
[out] LPMSG lpMsg, //从线程队列接收消息
[in, optional] HWND hWnd, //NULL检测所有线程窗口, -1只接受当前线程消息
[in] UINT wMsgFilterMin, //要检索的最低消息值的整数值
[in] UINT wMsgFilterMax //要检索的最高消息值的整数值
);
PostQuitMessage
发送一个退出消息
void PostQuitMessage(
[in] int nExitCode
);
UnhookWindowsHookEx
结束HOOK
BOOL UnhookWindowsHookEx(
[in] HHOOK hhk //要结束的HOOK
);
C++ 代码示例
#include <Windows.h>
#include <iostream>
using namespace std;
//钩子函数
LRESULT CALLBACK KeyboardHookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
KBDLLHOOKSTRUCT* pKeyboardHookStruct = (KBDLLHOOKSTRUCT*)lParam; //获取按下的键的虚拟键码、扫描码、扩展键标志等。
if (wParam == WM_KEYDOWN) //键盘按下
{
printf("%s\n", "你按下了");
BYTE keyState[256]; //存放整数的大小
wchar_t buffer[2]; //宽字节变量
buffer[1] = '\0'; //设置终止符
if (GetKeyboardState(keyState)) //获取按键状态 返回布尔 keyState其中的265键
{
//判断返回是不是大于0
//ToUnicode 将指定的虚拟键代码和键盘状态转换为相应的 Unicode 字符。 ToUnicode 参数 1. 要转换的虚拟密钥代码。 2. 要转换的密钥的硬件 扫描代码 。 3. keyState 前键盘状态的 256 字节数组的指针。 数组中 (字节) 的每个元素都包含一个键的状态。 4. 已接收的按键 5,接收按键的缓冲区大小 6,函数行为 ‘0’ 不受理ALT+数字键组合
if (ToUnicode(pKeyboardHookStruct->vkCode, pKeyboardHookStruct->scanCode, keyState, (LPWSTR)&buffer, 1, 0) > 0)
{
wcout << "按下:" << buffer << endl; // wcout宽字节输出
if (97 == buffer[0]) //判断符合条件退出
{
PostQuitMessage(0);
}
}
}
}
else if(wParam == WM_KEYUP) //键盘松开
{
printf("%s\n", "你松开了");
}
else
{
printf("%s\n", "键盘处于按下与松开的叠加状态????");
}
}
}
int main()
{
//安装HOOK
HHOOK hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookCallback, NULL, 0);
//判断是否HOOK成功
if (hKeyHook == NULL)
{
printf("%s\n", "获取失败");
}
//消息循环
//GetMessage 调用线程的 消息队列 里取得一个消息并将其放于指定的结构
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
//将虚拟密钥消息转换为字符消息 , 人话:把键盘的输入流转换为字符串
TranslateMessage(&msg);
//DispatchMessageW把消息发往窗口
DispatchMessageW(&msg);
}
//卸载HOOK
UnhookWindowsHookEx(hKeyHook);
return 0;
}
3. 逆向HOOK
源码
//安装HOOK
HHOOK hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookCallback, NULL, 0);
//判断是否HOOK成功
if (hKeyHook == NULL)
{
printf("%s\n", "获取失败");
}
逆向
.text:00414E10 push ebp ; Main函数
.text:00414E11 mov ebp, esp
.text:00414E13 sub esp, 0F4h ; 申请栈空间
.text:00414E19 push ebx ; 保存栈指指针装状态
.text:00414E1A push esi
.text:00414E1B push edi
.text:00414E1C lea edi, [ebp+var_34]
.text:00414E1F mov ecx, 0Dh
.text:00414E24 mov eax, 0CCCCCCCCh
.text:00414E29 rep stosd
.text:00414E2B mov eax, ___security_cookie
.text:00414E30 xor eax, ebp
.text:00414E32 mov [ebp+var_4], eax
.text:00414E35 mov esi, esp
.text:00414E37 push 0 ; dwThreadId
.text:00414E39 push 0 ; hmod
.text:00414E3B push offset fn ; lpfn 回调函数 指向虚函数表
.text:00414E40 push 0Dh ; idHook
.text:00414E42 call ds:SetWindowsHookExW
.text:00414E48 cmp esi, esp
.text:00414E4A call j___RTC_CheckEsp ; 检查溢出
.text:00414E4F mov [ebp+hhk], eax ; SetWindowsHook返回值
.text:00414E52 cmp [ebp+hhk], 0 ; if 返回值是否是0
.text:00414E56 jnz short loc_414E6A
.text:00414E58 push offset unk_41DCC4 ; char
.text:00414E5D push offset aS ; "%s\n"
.text:00414E62 call printf
.text:00414E67 add esp, 8 ; 堆栈平衡
回调函数是什么时候被调用的,在debug 中查看 在调用SetWIndowsHook中传入了push Hook技术.0041119A
,跟如 查看SetWindowsHook Call中
00414E37 6A 00 push 0x0
00414E39 6A 00 push 0x0
00414E3B 68 9A114100 push Hook技术.0041119A ; 回调函数
00414E40 6A 0D push 0xD
00414E42 FF15 5C114200 call dword ptr ds:[<&USER32.SetWindowsHookExW>] ; apphelp.6E2B1AC0
00414E48 3BF4 cmp esi,esp
00414E4A E8 EFC4FFFF call Hook技术.0041133E
进入SetWindowsHook Call查看内部执行流程
走到基址6E2B1ADC
的时候回调函数
又入栈了,如何调用?
EPB的值加上0xC的时候就是0019 FDD8
然后再取ss取值就是0041119A
在调用SetWindowsHook之前存放回调函数的位置,然后压入Call中这是产生关联不会执行。
要是说回调函数被那个函数调用了只能说:回调函数是由操作系统在特定事件发生时主动调用的,而不是由用户代码直接调用的。是在GetMessage之后被调用的
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
//将虚拟密钥消息转换为字符消息 , 人话:把键盘的输入流转换为字符串
TranslateMessage(&msg);
//DispatchMessageW把消息发往窗口
DispatchMessageW(&msg);
}
//卸载HOOK
UnhookWindowsHookEx(hKeyHook);
return 0;
.text:00414E6A mov esi, esp
.text:00414E6C push 0 ; wMsgFilterMax 四个参数
.text:00414E6E push 0 ; wMsgFilterMin
.text:00414E70 push 0 ; hWnd
.text:00414E72 lea eax, [ebp+Msg] ; 取内存地址
.text:00414E75 push eax ; lpMsg 压入指针
.text:00414E76 call ds:GetMessageW ; 四个参数压入GetMessagW
.text:00414E7C cmp esi, esp ; 检查溢出
.text:00414E7E call j___RTC_CheckEsp
.text:00414E83 test eax, eax ; 条件永远为真
.text:00414E85 jz short loc_414EAF ; ZF为1跳转
.text:00414E87 mov esi, esp ; 保存栈指针
.text:00414E89 lea eax, [ebp+Msg]
.text:00414E8C push eax ; lpMsg
.text:00414E8D call ds:TranslateMessage
.text:00414E93 cmp esi, esp ; 检查溢出
.text:00414E95 call j___RTC_CheckEsp
.text:00414E9A mov esi, esp
.text:00414E9C lea eax, [ebp+Msg]
.text:00414E9F push eax ; lpMsg
.text:00414EA0 call ds:DispatchMessageW
.text:00414EA6 cmp esi, esp
.text:00414EA8 call j___RTC_CheckEsp
.text:00414EAD jmp short loc_414E6A ; 这是一个while(True) 循环因为 只有 00414E85 能跳出 jmp 但是永远为真True
.text:00414EAF ; ---------------------------------------------------------------------------
.text:00414EAF
.text:00414EAF loc_414EAF: ; CODE XREF: _main_0+75↑j
.text:00414EAF mov esi, esp
.text:00414EB1 mov eax, [ebp+hhk]
.text:00414EB4 push eax ; hhk 压入SetWindowsHook 的句柄
.text:00414EB5 call ds:UnhookWindowsHookEx ; 退出Hook函数
KeyboardHookCallback (回调函数)
LRESULT CALLBACK KeyboardHookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
KBDLLHOOKSTRUCT* pKeyboardHookStruct = (KBDLLHOOKSTRUCT*)lParam; //获取按下的键的虚拟键码、扫描码、扩展键标志等。
if (wParam == WM_KEYDOWN) //键盘按下3
{
printf("%s\n", "你按下了");
BYTE keyState[256]; //存放整数的大小
wchar_t buffer[2]; //宽字节变量 2 给字节
buffer[1] = '\0'; //设置终止符
if (GetKeyboardState(keyState)) //获取按键状态 返回布尔 keyState其中的265键
.text:004147D3 xor eax, ebp
.text:004147D5 mov [ebp+var_4], eax ; if (nCode >= 0)
.text:004147D8 cmp [ebp+arg_0], 0
.text:004147DC jl loc_414912 ; 回调函数结束(KeyboardHookCallback)
.text:004147E2 mov eax, [ebp+arg_8] ; KBDLLHOOKSTRUCT* pKeyboardHookStruct = (KBDLLHOOKSTRUCT*)lParam;
.text:004147E5 mov [ebp+var_C], eax
.text:004147E8 cmp [ebp+arg_4], 100h ; if (wParam == WM_KEYDOWN(0x100)) //键盘按下
.text:004147EF jnz loc_4148E3
.text:004147F5 push offset unk_41DC74 ; char 字符串:你按下了
.text:004147FA push offset aS ; "%s\n"
.text:004147FF call printf
.text:00414804 add esp, 8 ; printf堆栈平衡
.text:00414807 mov [ebp+var_1EC], 2
.text:00414811 cmp [ebp+var_1EC], 4
.text:00414818 jnb short loc_41481C ; 检查堆栈溢出的Call
.text:0041481A jmp short loc_414821
.text:0041481C ; ---------------------------------------------------------------------------
.text:0041481C
.text:0041481C loc_41481C: ; CODE XREF: sub_4147B0+68↑j
.text:0041481C call j____report_rangecheckfailure ; 检查堆栈溢出的Call
.text:00414821
.text:00414821 loc_414821: ; CODE XREF: sub_4147B0+6A↑j
.text:00414821 xor eax, eax
.text:00414823 mov ecx, [ebp+var_1EC] ; wchar_t buffer[2];
.text:00414829 mov [ebp+ecx+pwszBuff], ax ; buffer[1] = '\0';
.text:00414831 mov esi, esp
.text:00414833 lea eax, [ebp+KeyState] ; BYTE keyState[256];
.text:00414839 push eax ; lpKeyState
.text:0041483A call ds:GetKeyboardState ; if (GetKeyboardState(keyState))
if (ToUnicode(pKeyboardHookStruct->vkCode, pKeyboardHookStruct->scanCode, keyState, (LPWSTR)&buffer, 1, 0) > 0)
{
wcout << "按下:" << buffer << endl; // wcout宽字节输出
.text:0041487D test eax, eax ; if (ToUnicode(pKeyboardHookStruct->vkCode, pKeyboardHookStruct->scanCode, keyState, (LPWSTR)&buffer, 1, 0) > 0)
.text:0041487F jle short loc_4148E1 ; 跳到函数结束
.text:00414881 mov esi, esp
.text:00414883 push offset sub_411505
.text:00414888 lea eax, [ebp+pwszBuff]
.text:0041488E push eax ; String buffer
.text:0041488F push offset Str ; Str "按下"
.text:00414894 mov ecx, ds:?wcout@std@@3V?$basic_ostream@_WU?$char_traits@_W@std@@@1@A ; std::wostream std::wcout
.text:0041489A push ecx ; int
.text:0041489B call sub_4111D6 ; cout << "按下:" << endl;
.text:004148A0 add esp, 8
.text:004148A3 push eax ; int
.text:004148A4 call sub_4112E4 ; wcout << buffer << endl
.text:004148A9 add esp, 8
.text:004148AC mov ecx, eax
.text:004148AE call ds:??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z ; 我也不清楚这是什么
if (97 == buffer[0]) //判断符合条件退出
{
PostQuitMessage(0);
}
.text:004148C0 imul ecx, eax, 0
.text:004148C3 movzx edx, [ebp+ecx+pwszBuff]
.text:004148CB cmp edx, 61h ; 'a' ; if (97 == buffer[0])
.text:004148CE jnz short loc_4148E1
.text:004148D0 mov esi, esp
.text:004148D2 push 0 ; nExitCode
.text:004148D4 call ds:PostQuitMessage ; 退出消息队列
if(wParam == WM_KEYUP) //键盘松开
{
printf("%s\n", "你松开了");
}
.text:004148E3
.text:004148E3 loc_4148E3: ; CODE XREF: sub_4147B0+3F↑j
.text:004148E3 cmp [ebp+arg_4], 101h ; else if(wParam == WM_KEYUP) //键盘松开
.text:004148EA jnz short loc_414900
.text:004148EC push offset unk_41DC8C ; 字符串:你松开了
.text:004148F1 push offset aS ; "%s\n"
.text:004148F6 call printf
.text:004148FB add esp, 8
.text:004148FE jmp short loc_414912 ; 回调函数结束(KeyboardHookCallback)
...