WINDOWS 的消息机制
Windows 系统是以消息处理为其控制机制,系统通过消息为窗口过程( windows procedure)传递输入。系统和应用两者都可以产生消息。
Windows 的消息处理机制为了能在应用程序中监控系统的各种事件消息,提供了挂接各种回调函数(HOOK)的功能。可以挂接多个反调函数构成一个挂接函数链。系统产生的各种消息首先被送到各种挂接函数,挂接函数根据各自的功能对消息进行监视、修改和控制等,然后交还控制权或将消息传递给下一个挂接函数以致最终达到窗口函数。 WINDOW 系统的这种反调函数挂接方法虽然会略加影响到系统的运行效率,但在很多场合下是非常有用的,通过合理有效地利用键盘事件的挂钩函数监控机制可以达到预想不到的良好效果。
hook 介绍
Hook(钩子)是 Windows 提供的一种消息处理机制平台,是指在程序正常运行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息,(钩子)实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
注意:安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能越大责任越大。
HOOK 链
WINDOWS 提供了 14 种不同类型的 HOOKS;不同的 HOOK 可以处理不同的消息。
WINDOWS 为不同的HOOKS 维护着各自的 HOOK 链表。 HOOK 链表是一串由应用程序定义的回调函数(CALLBACK Function)队列,当某种类型的消息发生时, WINDOWS 向此种类型的 HOOK 链的第一个函数( HOOK 链的顶部)发送该消息,在第一函数处理完该消息后由该函数向链表中的下一个函数传递消息,依次向下。如果链中某个函数没有向下传送该消息,那么链表中后面的函数将得不到此消息。(对于某些类型的 HOOK,不管 HOOK 链中的函数是否向下传递消息,与此类型 HOOK 联系的所有 HOOK 函数都会收到系统发送的消息)一些 Hook 子过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个 Hook 子过程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
钩子的作用范围
一共有两种范围(类型)的钩子:局部的和远程的。
一、局部钩子仅钩挂您自己进程的事件。
二、远程的钩子还可以将钩挂其它进程发生的事件。远程的钩子又有两种:
1、基于线程的 它将捕获其它进程中某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。
2、系统范围的 将捕捉系统中所有进程将发生的事件消息。
可以通过SetWindowsHookEx的MSDN解析看出
HOOK 类型
1、 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC Hook 子过程,并且在窗口过程处理完消息之后调用 WH_CALLWNDPROCRET Hook 子过程。WH_CALLWNDPROCRET Hook 传递指针到 CWPRETSTRUCT 结构,再传递到 Hook 子过程。
CWPRETSTRUCT 结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
LRESULT CALLBACK CallWndProc( int nCode,
WPARAM wParam,
LPARAM lParam
);
2、 WH_CBT Hook
在以下事件之前,系统都会调用 WH_CBT Hook 子过程,这些事件包括:
1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
2. 完成系统指令;
3. 来自系统消息队列中的移动鼠标,键盘事件;
4. 设置输入焦点事件;
5. 同步系统消息队列事件。
3、 WH_DEBUG Hook
在系统调用系统中与其它 Hook 关联的 Hook 子过程之前,系统会调用 WH_DEBUG Hook 子过程。你可以使用这个 Hook 来决定是否允许系统调用与其它 Hook 关联的 Hook 子过程。
4、 WH_FOREGROUNDIDLE Hook
当应用程序的前台线程处于空闲状态时,可以使用 WH_FOREGROUNDIDLE Hook 执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用 WH_FOREGROUNDIDLE Hook 子过程。
5、 WH_GETMESSAGE Hook
应用程序使用 WH_GETMESSAGE Hook 来监视从 GetMessage or PeekMessage 函数返回的消息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及其它发送到消息队列中的消息。
6、 WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使用这个 Hook 回放通过使用 WH_JOURNALRECORD Hook 记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook 已经安装,正常的鼠标和键盘事件就是无效的。
WH_JOURNALPLAYBACK Hook 是全局 Hook,它不能象线程特定 Hook 一样使用。
WH_JOURNALPLAYBACK Hook 返回超时值,这个值告诉系统在处理来自回放 Hook 当前消息之前需要等待多长时间(毫秒)。这就使 Hook 可以控制实时事件的回放。 WH_JOURNALPLAYBACK 是system-wide local hooks,它们不会被注射到任何进程地址空间。
7、 WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个 Hook 记录连续的鼠标和键盘事件,然后通过使用 WH_JOURNALPLAYBACK Hook 来回放。WH_JOURNALRECORD Hook 是全局 Hook,它不能象线程特定 Hook 一样使用。
WH_JOURNALRECORD 是 system-wide local hooks,它们不会被注射到任何进程地址空间。
8、 WH_KEYBOARD Hook
在应用程序中, WH_KEYBOARD Hook 用来监视 WM_KEYDOWN and WM_KEYUP 消息,这些消息通过 GetMessage or PeekMessage function 返回。可以使用这个 Hook 来监视输入到消息队列中的键盘消息。
9、 WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook 监视输入到线程消息队列中的键盘消息。更底层的一个键盘钩子,更为常用,因为它的参数就是VK码
wParam
[in] Specifies the identifier of the keyboard message. This parameter can be one of the following messages: WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP.
lParam
[in] Pointer to a KBDLLHOOKSTRUCT structure.
typedef struct {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
10、 WH_MOUSE Hook
WH_MOUSE Hook 监视从 GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个 Hook 监视输入到消息队列中的鼠标消息。11、 WH_MOUSE_LL Hook
WH_MOUSE_LL Hook 监视输入到线程消息队列中的底层鼠标消息。
12、 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用 ALT+TAB or ALT+ESC 组合键切换窗口。 WH_MSGFILTER Hook 只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了 Hook 子过程的应用程序建立的对话框的消息。
WH_SYSMSGFILTER Hook 监视所有应用程序消息。
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
通过调用 CallMsgFilter function 可以直接的调用 WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
BOOL CallMsgFilter( LPMSG lpMsg,
int nCode
);
13、 WH_SHELL Hook
外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用 WH_SHELL Hook 子过程。WH_SHELL 共有5钟情况:
1. 只要有个 top-level、 unowned 窗口被产生、起作用、或是被摧毁;
2. 当 Taskbar 需要重画某个按钮;
3. 当系统需要显示关于 Taskbar 的一个程序的最小化形式;
4. 当目前的键盘布局状态改变;
5. 当使用者按 Ctrl+Esc 去执行 Task Manager(或相同级别的程序)。
按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL 消息之前,应用程序必须调用 SystemParametersInfo function 注册它自己。
安装钩子
使用 SetWindowsHookEx 函数( API 函数),指定一个 HOOK 类型、自己的 HOOK 过程是全局还是局部 HOOK,同时给出 HOOK 过程的进入点,就可以轻松的安装你自己的 HOOK 过程。
HHOOK SetWindowsHookEx( int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
SetWindowHookEx 函数参数说明:
idHook:代表是何种 Hook(也就是上面讲的 14 种 Hook)
lpfn:代表处理 Hook 的回调函数的 Address,这是一个 CallBack Fucnction(也就是上面讲的回调函数),
hMod:代表.DLL 的 hInstance,如果是 Local Hook,该值可以是 Null,而如果是Remote Hook,则可以使用 GetModuleHandle(".dll 名称")来传入。
dwThreadId:代表执行这个 Hook 的 ThreadId(线程 ID),如果不设定是那个 Thread(线程),则传 Null,该参数可以决定该钩子是局部的还是系统范围的。如果该值为 NULL,那么该钩子将被解释成系统范围内的,那它就可以监控所有的进程及它们的线程。如果您指定了您自己进程中的某个线程ID 号,那该钩子是一个局部的钩子。如果该线程 ID 是另一个进程中某个线程的 ID,那该钩子是一个全局的远程钩子。
1.WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK 总是代表局部的系统范围的钩子,之所以说是局部,是因为它们没有必要放到一个 DLL 中。
2.WH_SYSMSGFILTER 总是一个系统范围内的远程钩子。其实它和 WH_MSGFILTER 钩子类似,如果把参数 ThreadID 设成 0 的话,它们就完全一样了。
SetWindowsHookEx 总是将你的 HOOK 函数放置在 HOOK 链的顶端。你可以使用 CallNextHookEx 函数将系统消息传递给 HOOK 链中的下一个函数。
[注意]对于某些类型的 HOOK,系统将向该类的所有 HOOK 函数发送消息,这时, HOOK 函数中的CallNextHookEx 语句将被忽略。
1.必须在应用程序外建立一个 DLL
2.将该 HOOK 函数封装到DLL中
应用程序在安装全局 HOOK 过程时必须先得到该 DLL 模块的句柄。将 DLL 名传递给 LoadLibrary 函数,就会得到该 DLL 模块的句柄
得到该句柄 后,使用 GetProcAddress 函数可以得到 HOOK 过程的地址。
最后,使用SetWindowsHookEx 将 HOOK 过程的首址嵌入相应的 HOOK 链中, SetWindowsHookEx 传递一个模块句柄,它为 HOOK 过程的进入点,线程标识符置为 0,指出:该 HOOK 过程同系统中的所有线程关联。
如果是安装局部 HOOK 此时该 HOOK 函数可以放置在 DLL 中,也可以放置在应用程序的模块段。
这里有两个特殊情况:
全局(远程钩子) HOOK 函数可以拦截系统中所有线程的某个特定的消息,为了安装一个全局 HOOK 过程,
g_hHook = SetWindowsHookEx( WH_CALLWNDPROC,(HOOKPROC)HookProc,
hDll, GetWindowThreadProcessId(hWnd,NULL) );