Hook技术被广泛应用于安全的多个领域,我们常说的Hook一般指微软提供的基于消息机制的Api, 除此之外Hook还有多种实现,hook的根本用途即信息的拦截,包括消息和API参数等的拦截。借鉴网上有个比较全面的分类如下:
Hook分为应用层(Ring3)Hook和内核层(Ring0)Hook,应用层Hook适用于x86和x64,而内核层Hook一般仅在x86平台适用,因为从Windows Vista的64版本开始引入的Patch Guard技术极大地限制了Windows x64内核挂钩的使用。
红圈部分基于消息机制的的由SetWindowsHook接口设置提供Hook功能的钩子只是其中一种,本文中我们暂称为“消息钩子”
消息钩子的原理:(关于windows消息机制可以参照前面一篇文章)
首先以键盘消息为例说明:
所以,我们只需在[OS message queue]和[application message queue]之间安装钩子即可窃取键盘消息,并实现恶意操作。
关于消息钩子的具体说明和使用可以在MSDN查阅SetWindowsHookEx:https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwindowshookexa
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
该函数的返回值是一个钩子句柄。参数介绍如下:
lpfn:指定Hook函数的地址。如果dwThreadId参数被赋值为0,或者被设置为一个其他进程中的线程ID(远程钩子/全局钩子),那么lpfn属于DLL中的函数过程。如果dwThreadId为当前进程中的一个线程ID,那么lpfn可以使指向当前进程模块中的函数,当然,也可以使DLL模块中的函数(局部钩子/本地钩子)。
hMod:
Type: HINSTANCE
A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
该参数指定钩子函数所在DLL的DLL模块句柄。即lpfn回调函数所在的模块句柄。如果dwThreadId为当前进程中的线程ID且lpfn所指函数在当前进程中,则该参数必须被设置为NULL。
dwThreadId:
Type: DWORD
The identifier of the thread with which the hook procedure is to be associated. For desktop apps, if this parameter is zero, the hook procedure is associated with all existing threads running in the same desktop as the calling thread. For Windows Store apps, see the Remarks section.
指定需要被挂钩的线程ID号。如果设置为0,表示在和被调用线程位于同一桌面环境中所有的线程中挂钩;如果设置了具体的线程ID,表示在指定线程中挂钩。该参数影响上面两个参数的取值,同时也决定了该钩子是全局钩子还是局部钩子。注意是否全局钩子还是局部钩子与下面具体的钩子种类无关仅由本参数控制。
idHook:该参数表示钩子的类型。
局部钩子:有的资料又叫线程钩子,用来监视指定线程的事件消息。
全局钩子:有的资料又叫系统钩子,监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)中。这是系统钩子和线程钩子很大的不同之处。
为什么全局钩子必须在Dll中实现:
因为它需要插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会被操作系统自动或强行地加载到该进程中。因此,设置全局消息钩子,也可以达到DLL注入的目的。
几点需要说明的地方:
(1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
(2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
(3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
移除先前用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
);
写一个消息钩子程序的步骤总结:
1、在MSDN中查阅SetWindowsHookExA function的说明页面
2、在idHook参数说明表格中找到自己想要设置的钩子类型及回调函数类型(表格中后面一列)
3、点击查看回调函数说明比如CBTProc,
LRESULT CALLBACK CBTProc( _In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM wParam);
4、在页面的Remarks部分查看nCode、wParam、wParam转换成对应的结构体,获取到要拦截的消息内容。
下面我实现一个键盘钩子及一个获取窗口改变大小时当前窗口尺寸的消息钩子:
从这个练习中发现有些全局钩子在传入模块句柄时可以传入GetMoudleHandle(Null)比如全局键盘钩子,有些必须要传一个非NULL的句柄比如CBT类型的,否则会产生错误捕获不到消息。 具体不清楚为啥,后面再研究看看。
具体链接如下:
https://download.csdn.net/download/roshy/10688762