我们知道Windows中的窗口程序是基于消息,由事件驱动的,在某些情况下可能需要捕获或者修改消息,从而完成一些特殊的功能(MFC框架就利用Windows钩子对消息进行引导)。对于捕获消息而言,无法使用IAT或Inline Hook之类的方式去进行捕获,这就要用到接下来要介绍的Windows提供的专门用于处理消息的钩子函数。
Windows下的应用程序大部分都是基于消息机制的,它们都会有一个消息过程函数,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制的作用就是用来截获和监视这些系统中的消息。Windows钩子琳琅满目,可以用来应对各种不同的消息。
按照钩子作用的范围不同,又可以分为局部钩子和全局钩子。其中,全局钩子具有相当大的功能,几乎可以实现对所有Windows消息的拦截、处理和监控。局部钩子是针对某个线程的;而全局钩子则是作用于整个系统中基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。系统钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会被操作系统自动或强行地加载到该进程中。因此,设置消息钩子,也可以达到DLL注入的目的。
般来说,HOOK API由两个组成部分,即实现HOOK API的DLL文件,和启动注入的主调程序。本文采用HOOK API 技术对剪切板相关的API 函数进行拦截,从而实现对剪切板内容的监控功能,同样使用该技术实现进程防终止功能。其中DLL文件支持HOOK API的实现,而主调客户端程序将在初始化时把带有HOOK API功能的DLL随着鼠标钩子的加载注入到目标进程中,这里的鼠标钩子属于系统钩子。
几点需要说明的地方:
(1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
(2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
(3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
1、按事件分类
有如下的几种常用类型
(1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。
(2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。
(3) 外壳钩子可以监视各种Shell事件消息。比如启动和关闭应用程序。
(4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。
(5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。
此外,还有一些特定事件的钩子提供给我们使用,不一一列举。
2、按使用范围分类
主要有线程钩子和系统钩子:
(1) 线程钩子监视指定线程的事件消息。
(2) 系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)中。这是系统钩子和线程钩子很大的不同之处。
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
该函数的返回值是一个钩子句柄。参数介绍如下:
lpfn:指定Hook函数的地址。如果dwThreadId参数被赋值为0,或者被设置为一个其他进程中的线程ID(远程钩子/全局钩子),那么lpfn属于DLL中的函数过程。如果dwThreadId为当前进程中的一个线程ID,那么lpfn可以使指向当前进程模块中的函数,当然,也可以使DLL模块中的函数(局部钩子/本地钩子)。
hMod:该参数指定钩子函数所在模块的模块句柄。即lpfn所在的模块句柄。如果dwThreadId为当前进程中的线程ID,若lpfn所指函数在当前进程中,则该参数被设置为NULL。
dwThreadId:指定需要被挂钩的线程ID号。如果设置为0,表示在所有基于所有的线程中挂钩;如果设置了具体的线程ID,表示在指定线程中挂钩。该参数影响上面两个参数的取值,同时也决定了该钩子是全局钩子还是局部钩子。注意是否全局钩子还是局部钩子与下面具体的钩子种类无关仅由本参数控制。
idHook:该参数表示钩子的类型。常用的几种如下:
按照该钩子的作用是监视被投递到消息队列中的消息。也就是当调用GetMessage或PeekMessage函数时,函数从程序的消息队列中获取一个消息后调用该钩子。
WH_GETMESSAGEG的钩子函数如下:
LRESULT CALLBACK GetMsgProc(
int code, //hook code
WPARAM wParam, //removal option
LPARAM lParam //message
);
该钩子用于监视鼠标消息。钩子函数如下:
LRESULT CALLBACK MouseProc(
int nCode, //hook code
WPARAM wParam, //message identifier
LPARAM lParam //mouse coordinates
);
该钩子用于监视键盘消息。钩子函数如下:
LRESULT CALLBACK KeyboardProc(
int code, //hook code
WPARAM wParam, //virtual-key code
LPARAM lParam //keystroke-message information
}
用于调试其它钩子。钩子函数如下:
LRESULT CALLBACK DebugProc(
int nCode, //hook code
WPARAM wParam, //hook type
LPARAM lParam //debugging information
);
对于以上钩子函数的详情还请各位看客老爷们自行挪步到MSDN了。
移除先前用SetWindowsHookEx安装的钩子:
BOOL UnhookWindowsHookEx(
HHOOK hhk
);
唯一的参数是待移除的钩子句柄。
在实际应用中,可以多次调用SetWindowsHookEx函数来安装钩子,而且可以安装多个同样类型的钩子。这样,钩子就会形成一条钩子链,最后安装的钩子会首先截获到消息。当该钩子对消息处理完毕后,可以选择返回或者把消息继续传递下去。如果是为了屏蔽某消息,可以在安装的钩子函数中直接返回非零值。如果希望我们的钩子函数处理完消息后可以继续传递给目标窗口,则必须选择将消息继续传递。继续传递消息的函数定义如下:
LRESULT CallNextHookEx(
HHOOK hhk, //handle to current hook
int nCode, //hook code passed to hook procedure
WPARAM wParam, //value passed to hook procedure
LPARAM lParam //value passed to hook procedure
);
第一个参数是钩子句柄,就是调用SetWindowsHookEx函数的返回值;后面3个参数是钩子的参数,直接一次copy即可。例如:
HHOOK g_Hook = SetWindowsHookEx(…);
LRESULT CALLBACK GetMsgProc(
int code, //hook code
WPARAM wParam, //removal option
LPARAM lParam //message
)
{
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
Windows钩子的使用场景比较广泛,我们就几种比较常见的情况做一个应用示例。
参看上一小结可知,编写钩子程序的三个步奏是:
定义钩子函数:
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam)
安装钩子:
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
卸载钩子:
BOOL UnhookWindowsHookEx( HHOOK hhk)
还需要注意一点:系统钩子必须放在独立的动态链接库中。由此,程序分为两个部分:一个是钩子程序动态链接库,实现了鼠标钩子程序;另一个是MFC操作窗体,对DLL进行加载和卸载,即对DLL进行测试。//我在win7下测试没有使用dll也成功了why?不想在DLL实现SetWindowsHookEx时第三个参数传入GetModuleHandle(NULL)即可?
先新建一个DLL程序(这个不会可以看我以前的博客,这里就不重复了),我们在头文件中增加两个导出函数和两个全局。
#define MY_API __declspec(dllexport)
extern "C" MY_API VOID SetHookOn();
extern "C" MY_API VOID SetHookOff();
HHOOK g_Hook = NULL; //钩子句柄
HINSTANCE g_Inst = NULL; //DLL模块句柄
在DllMain中保存该DLL模块的句柄,以方便安装全局钩子。
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//保存DLL模块句柄
g_Inst = (HINSTANCE)hModule;
return TRUE;
}
安装与卸载钩子的函数如下:
VOID SetHookOn()
{
//安装钩子
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0);
}
VOID SetHookOff()
{
//卸载钩子
UnhookWindowsHookEx(g_Hook);
}
钩子函数的实现如下:
//钩子函数
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
if(code < 0)
{
//如果code小于0,必须调用CallNextHookEx传递消息,不处理该消息,并返回CallNextHookEx的返回值。
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
if(code == HC_ACTION && lParam > 0)
{
//code等于HC_ACTION,表示消息中包含按键消息
//如果为WM_KEYDOWN,则显示按键对应的文本
char szBuf[MAXBYTE] = {0};
GetKeyNameText(lParam, szBuf, MAXBYTE);
MessageBox(NULL, szBuf, "提示", MB_OK);
}
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
编译链接后产生我们需要的.dll和.lib文件,然后新建一个项目来导入动态库内容调用相关函数。
新建项目如下:
首先导入库:
#pragma comment (lib, "全局钩子.lib")
声明将要调用的函数(不声明链接时将报错):
extern "C" VOID SetHookOn();
extern "C" VOID SetHookOff();
在按钮事件中调用导出函数:
void CHookDebugDlg::OnHookon()
{
SetHookOn();
}
void CHookDebugDlg::OnHookoff()
{
SetHookOff();
}
数据防泄漏软件通常会精致PrintScreen键,防止通过截屏将数据保存为图片而导致数据泄密。下面我们也可以模仿一下,简单的实现该功能。这里需要注意的是,普通的键盘钩子(WH_KEYBOARD)是无法过滤一些系统按键的,得通过安装低级键盘钩子(WH_KEYBOARD_LL)来达到目的。
在低级键盘钩子的回调函数中,判断是否为PrintScreen键,如果是,则直接返回TRUE;如果不是,则传递给下一个钩子处理。
实现过程与上面相同。
可能在编译时会报错,说WH_KEYBOARD_LL和KBDLLHOOKSTRUCT未定义,此时可以在文件开头加上如下代码:
#define WH_KEYBOARD_LL 13
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
DWORD dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
其实在winuser.h中已有定义,但是可能是兼容的缘故用不了。
利用WH_GETMESSAGE钩子,可以方便地将DLL文件注入到所有基于消息机制的程序中。因为有时候可能需要DLL文件完成一些工作,但是工作时需要DLL在目标进程的空间中。这个时候,就可以将DLL注入目标进程来完成相关的功能。
主要的代码如下:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//保存DLL模块句柄
g_Inst = (HINSTANCE)hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DoSomething();
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
VOID SetHookOn()
{
g_Hook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_Inst, 0);
}
VOID SetHookOff()
{
UnhookWindowsHookEx(g_Hook);
}
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
VOID DoSomething()
{
MessageBox(NULL, "Hello,我被执行了!", "提示", MB_OK);
}