键盘钩子

    科技2026-01-16  10

    一 、总述

    0x1 消息钩子

    windows向用户提供GUI,它以事件驱动的方式工作。操作系统中借助鼠标,键盘,选择菜单,按钮,以及移动鼠标,改变窗口大小与位置等都属于事件(对此我感受最深的是学QT编程的时候,每个按钮或其他事件都会对应一个槽函数,通过槽函数的调用来实现相应的功能)。发生事件时,OS会把事先定义好的消息发送给相应的程序,应用程序收到消息后会执行相应的操作。所以,敲击键盘时,消息会从os移动到应用程序,所谓的消息钩子,就是在此间偷看这些信息。

    0x2 windows消息流

    产生消息输入,消息被加入到【OS Message queue】OS判断是哪个应用程序中发生了事件,并从【OS Message queue】,并加入到相应的应用程序的【application message queue】中应用程序监视自己的【application message queue】,发现新添加的消息,调用相应的事件处理程序进行处理

    0x3 消息钩取工作原理

    应用程序和OS之间好似有一条运输通道,而钩子,就是埋伏在这条运输通道上的小偷,他会翻看OS与应用程序之间的消息队列,必要时,还会对有些消息进行改变或者丢弃。

    再一点,如果同时加上多个钩子,钩子就会向一条链一样,链起来,形象的称为“钩链”。

    二、键盘钩子

    钩子的主程序是以dll的形式存在着,然后通过一个应用程序进行调用,应用程序作为一个启动程序,将钩子的dll注册到windows上,主体是dll文件。

    0x1 KeyHook.dll

    BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: g_hInstance = hinstDll; break; case DLL_THREAD_DETACH: break; } return true; }

    Dll文件的主函数,当dll被注入进程时,会首先调用该函数,该函数还可对一些变量进行一些初始化和变量赋值。

    #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); } __declspec (dllexport) void HookStop() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } } #ifdef __cplusplus } #endif

    导入导出函数,用于在启动程序中来启动或卸载钩子。此处还声明了钩子类型,回调函数…

    LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char szPath[MAX_PATH] = { 0, }; char* p = NULL; if (nCode == 0) { if (!(lParam & 0x80000000)) { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\'); g_hWnd = FindWindow(NULL, (LPCWSTR)DEF_PROCESS_NAME); if (!_stricmp(p + 1, DEF_PROCESS_NAME)) { return 1; } } } return CallNextHookEx(g_hHook, nCode, wParam, lParam); }

    钩子函数中最关键的回调函数,当发生键盘输入事件时,会调用该函数进行消息处理,MSDN对他的定义如下

    LRESULT CALLBACK KeyboardProc( _In_ int code, _In_ WPARAM wParam, _In_ LPARAM lParam );

    code,用来表示钩子程序如何处理消息,如果code的值小于0,则钩子程序需要直接将消息传给CallNextHookEx函数。

    wParam,传递的是虚拟键值,在这,’A‘和’a‘ 是相同的虚拟键值。

    lParam,传递一些额外扩展信息,用32比特来传递一些信息,具体情况请参考微软官网.

    0x2 HookMain.exe

    int main() { HMODULE hDll = NULL; PFN_HOOKSTART HookStart = NULL; PFN_HOOKSTOP HookStop = NULL; char ch = 0; hDll = LoadLibraryA(DEF_DLL_NAME); HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART); HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP); HookStart(); printf("press to quit!\n"); while (_getch() != 'q'); HookStop(); FreeLibrary(hDll); }

    相比之下简单多了,通过调用dll的导出函数,直接实现了钩子的注册及卸载,可以看到,在调用HookStart后,执行一个死循环,也就是说,这就是个启动程序,启动之后就没用了。

    思维扩展一点,可以将启动程序换到某个有实际功能的程序里,当该程序运行时,多起一个线程运行钩子,悄无声息的截获你的消息。:)

    三、遇到的问题

    0x1 注入失败,进程会卡死

    多方查找,这是个很傻*的错误,原因是32位程序无法注入64位程序,可能是一开始我就是用32位注入64位出错了,我的操作系统是win10,再后来,我编译了64位的程序,可还是有程序会崩溃,于是上网查资料,遇到了同样经历的一位大哥,https://bbs.pediy.com/thread-250189.htm,我试了又试,还是回到了这个傻逼的架构不匹配的问题上。

    由于32位和64位架构不匹配,当你64位的钩子注入到32位的程序里时,会使用sendmessage来传递消息,而这个进程,也就卡在了这里。。。

    0x2 文件读写问题

    本来想着在钩子里加个文件记录功能,能记录敲击的键盘按键,谁知virtual studio竟然不让我用fopen,气死我了。无奈转战createFile,谁知也是一地鸡毛,死活就是写不进去了。

    最后通过改宏定义用上了fopen,说来也怪,能用fopen之后,CreateFile也能行了,不知道是不是那个宏的原因。

    右键工程名–>属性–>C/C++–>预处理器–>预处理器定义,编辑右边输入框加入: 奈转战createFile,谁知也是一地鸡毛,死活就是写不进去了。

    参考文章:《逆向工程核心原理》 https://blog.csdn.net/weixin_39449570/article/details/78801573

    Processed: 0.014, SQL: 9