前言:
学习笔记,随时更新。如有谬误,欢迎指正。
说明:
- 红色字体为较为重要部分。
- 绿色字体为个人理解部分。
原文链接:https://learn.microsoft.com/en-us/windows/win32/winmsg/hooks
1 Hook
- Hook 是系统消息处理机制中的一个点,在其之中应用程序可以安装子例程来监视系统中的消息流量,并在某些类型的消息到达目标窗口程序之前对其进行处理。
1.1 Hook 概述
- Hook 是应用程序截获消息、鼠标操作和击键等事件的机制。
- 截获特定类型的事件的函数称为 Hook 程序。 Hook 程序可以对其接收的每个事件执行操作,然后修改或放弃该事件。
- Hook 往往会降低系统速度,因为它们会增加系统必须对每条消息执行的处理量。应仅在必要时安装 Hook ,并尽快将其删除。
1.1.1 Hook 链
- 系统支持多种不同类型的 Hook ,每种类型都提供对其消息处理机制的不同方面的访问。
- 系统为每种类型的 Hook 维护单独的 Hook 链。Hook 链是指向应用程序定义的特殊回调函数(称为 Hook 程序)的指针列表。当发生与特定类型的 Hook 关联的消息时,系统会将消息依次传递给 Hook 链中引用的每个 Hook 程序。
- Hook 程序可以执行的操作取决于所涉及的 Hook 类型。某些 Hook 类型的 Hook 程序只能监视消息,而其他 Hook 类型的 Hook 程序可以修改消息或通过链停止其调用传递,从而阻止它们到达下一个 Hook 程序或目标窗口。
1.1.2 Hook 程序
- 使用 SetWindowsHookEx 函数将 Hook 程序安装到与 Hook 关联的链中。
- SetWindowsHookEx 函数始终在 Hook 链的开头安装 Hook 程序。当发生由特定类型的 Hook 监视的事件时,系统会在与 Hook 关联的 Hook 链的开头调用 Hook 程序。
- 链中的每个 Hook 程序会确定是否将事件传递给下一个程序。Hook 程序通过调用 CallNextHookEx 函数将事件传递给下一个 Hook 程序。调用 CallNextHookEx 是可选的,但强烈建议使用,否则,安装了 Hook 的其他应用程序将不会收到 Hook 通知,因此行为可能不正确 除非绝对需要阻止其他应用程序看到通知,否则都应调用 CallNextHookEx 。
- 全局 Hook 监视与调用线程位于同一桌面中的所有线程的消息。特定于线程的 Hook 仅监视单个线程的消息。
- 全局 Hook 程序可以在调用线程所在的桌面中的任何应用程序的上下文中调用,因此该 Hook 程序必须位于单独的 DLL 模块中。
- 特定于线程的 Hook 程序仅在关联线程的上下文中调用。如果应用程序为其自己的线程之一安装 Hook 程序,则 Hook 程序可以位于与应用程序代码的其余部分相同的模块中,也可以位于 DLL 中。但如果应用程序为其他应用程序的线程安装 Hook 程序,则该 Hook 程序必须位于 DLL 中。
- 应仅将全局 Hook 用于调试目的,否则,应避免使用它们。全局 Hook 会损害系统性能,并导致与实现相同类型全局 Hook 的其他应用程序冲突。
1.1.3 Hook 类型
1.1.3.1 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET
- 使用 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 类型的 Hook 可以监视发送到窗口程序的消息。系统在将消息传递到接收窗口的窗口程序之前调用 WH_CALLWNDPROC 的 Hook 程序,并在窗口程序处理消息后调用 WH_CALLWNDPROCRET 的 Hook 程序。
- 有关详细信息,请参阅 CallWndProc 和 CallWndRetProc 回调函数。
1.1.3.2 WH_CBT
- 在激活、创建、销毁、最小化、最大化、移动或调整窗口大小之前,在完成系统命令之前,在从系统消息队列中删除鼠标或键盘事件之前,在设置输入焦点前,或在与系统消息队列同步之前,系统会调用 WH_CBT 类型的 Hook 程序。Hook 程序的返回值会决定系统是允许还是阻止这些操作。WH_CBT 类型的 Hook 主要用于基于计算机训练 (CBT) 的应用程序。
- 有关详细信息,请参阅 CBTProc 回调函数。
1.1.3.3 WH_DEBUG
- 在调用与系统中任何其他 Hook 关联的 Hook 程序之前,系统将调用 WH_DEBUG 类型的 Hook 程序。可以使用此 Hook 来确定是否允许系统调用与其他类型的 Hook 关联的 Hook 程序。
- 有关详细信息,请参阅 DebugProc 回调函数。
1.1.3.4 WH_FOREGROUNDIDLE
- 当应用程序的前台线程即将变为空闲状态时,系统会调用 WH_FOREGROUNDIDLE 类型的 Hook 程序。借助 WH_FOREGROUNDIDLE 类型的 Hook ,可以在前台线程空闲时执行低优先级任务。
- 有关详细信息,请参阅 ForegroundIdleProc 回调函数。
1.1.3.5 WH_GETMESSAGE
- WH_GETMESSAGE 类型的 Hook 使应用程序能够监视 GetMessage 或 PeekMessage 函数即将返回的消息。可以使用 WH_GETMESSAGE 类型的 Hook 来监视鼠标和键盘输入、以及发布到消息队列的其他消息。
- 有关详细信息,请参阅 GetMsgProc 回调函数。
1.1.3.6 WH_JOURNALPLAYBACK
- WH_JOURNALPLAYBACK 类型的 Hook 使应用程序能够将消息插入系统消息队列。可以使用此类型的 Hook 回放之前使用 WH_JOURNALRECORD 录制的一系列鼠标和键盘事件。
- 只要安装了 WH_JOURNALPLAYBACK类型的 Hook ,常规鼠标和键盘输入就处于禁用状态。
- WH_JOURNALPLAYBACK 类型的 Hook 是全局 Hook ,不能用作特定于线程的 Hook 。
- WH_JOURNALPLAYBACK 型的 Hook 返回超时值。此值告知系统在处理来自回放 Hook 的当前消息之前要等待多少毫秒。这使 Hook 能够控制它回放的事件的计时。
- 从 Windows 11 开始,此类型的 Hook 将被废弃。因此强烈建议改为调用 SendInput 。
- 有关详细信息,请参阅 JournalPlaybackProc 回调函数。
1.1.3.7 WH_JOURNALRECORD
- 使用 WH_JOURNALRECORD 类型的Hook 可以监视和记录输入事件。通常,可以使用此类型的 Hook 记录一系列鼠标和键盘事件,以便稍后使用 WH_JOURNALPLAYBACK 播放。
- WH_JOURNALRECORD 类型的 Hook 是全局 Hook ,不能用作特定于线程的 Hook 。
- 从 Windows 11 开始,此类型的 Hook 将被废弃。因此强烈建议改为调用 SendInput 。
- 有关详细信息,请参阅 JournalRecordProc 回调函数。
1.1.3.8 WH_KEYBOARD_LL
- 使用 WH_KEYBOARD_LL 类型的 Hook 可以监控即将在线程输入队列中发布的键盘输入事件。
- 有关详细信息,请参阅 LowLevelKeyboardProc 回调函数。
1.1.3.9 WH_KEYBOARD
- WH_KEYBOARD 类型的 Hook 使应用程序能够监控即将由 GetMessage 或 PeekMessage 函数返回的 WM_KEYDOWN 和 WM_KEYUP 消息的消息流量。您可以使用 WH_KEYBOARD 类型的 Hook 来监控发送到消息队列的键盘输入。
- 有关详细信息,请参阅 KeyboardProc 回调函数。
1.1.3.10 WH_MOUSE_LL
- 使用 WH_MOUSE_LL 类型的 Hook 可以监控即将在线程输入队列中发布的鼠标输入事件。
- 有关详细信息,请参阅 LowLevelMouseProc 回调函数。
1.1.3.11 WH_MOUSE
- 使用 WH_MOUSE 类型的 Hook 可以监控 GetMessage 或 PeekMessage 函数即将返回的鼠标消息。 可以使用 WH_MOUSE 类型的 Hook 来监控发布到消息队列的鼠标输入。
- 有关详细信息,请参阅 MouseProc 回调函数。
1.1.3.12 WH_MSGFILTER 和 WH_SYSMSGFILTER
- WH_MSGFILTER 和 WH_SYSMSGFILTER 类型的 Hook 可以监控菜单、滚动条、消息框或对话框即将处理的消息,并检测用户按 Alt+TAB 或 Alt+ESC 组合键后即将激活其他窗口的时机。
- WH_MSGFILTER 类型的 Hook 只能监控传递给菜单、滚动条、消息框或由安装 Hook 程序的应用程序创建的对话框的消息。WH_SYSMSGFILTER 类型的 Hook 能监控所有应用程序的此类消息。
- WH_MSGFILTER 和 WH_SYSMSGFILTER 类型的 Hook 使您能够在模态循环期间执行消息过滤,这相当于在主消息循环中完成的过滤。例如,应用程序经常在主循环中检查从队列中检索消息到分派消息之间的新消息,并根据需要执行特殊处理。然而,在模态循环期间,系统检索和调度消息,而不允许应用程序在其主消息循环中过滤消息。如果应用程序安装了 WH_MSGFILTER 或 WH_SYSMSGFILTER 类型的 Hook 程序,系统将在模态循环期间调用该程序。
- 应用程序可以通过调用 CallMsgFilter 函数直接调用 WH_MSGFILTER 类型的 Hook 。通过使用这个函数,应用程序可以在模态循环期间使用与主消息循环相同的代码来过滤消息。为此,需要将过滤操作封装在 WH_MSGFILTER 类型的 Hook 的 Hook 程序中,并在调用 GetMessage 和 DispatchMessage 函数之间调用 CallMsgFilter 。
- CallMsgFilter 的最后一个参数被简单地传递给 Hook 程序,您可以输入任何值。通过定义诸如 MSGF_MAINLOOP 之类的常量,Hook 程序可以使用该值来确定此次 Hook 程序是从何处调用的。
- 有关详细信息,请参阅 MessageProc 和 SysMsgProc 回调函数。
1.1.3.13 WH_SHELL
- Shell 应用程序可以使用 WH_SHELL 类型的 Hook 来接收重要的通知。当 Shell 应用程序即将被激活、以及创建或销毁顶层窗口时,系统会调用 WH_SHELL 类型的 Hook 程序。
- 注意,自定义的 Shell 应用程序不接收 WH_SHELL 消息。因此,任何将自己注册为默认 Shell 的应用程序都必须在它(或任何其他应用程序)接收 WH_SHELL 消息之前调用 SystemParametersInfo 函数,函数中必传入 SPI_SETMINIMIZEDMETRICS 和 MINIMIZEDMETRICS 参数,并且 MINIMIZEDMETRICS 结构体中的 iArrange 成员变量要被设置为 ARW_HIDE 。
- 有关详细信息,请参阅 ShellProc 回调函数。
1.2 使用 Hook
- 示例:https://learn.microsoft.com/en-us/windows/win32/winmsg/using-hooks。
- 调用 UnhookWindowsHookEx 函数并指定要释放的 Hook 程序的句柄,可以释放线程特定的 Hook 程序(从 Hook 链中删除它的地址)。一旦应用程序不再需要 Hook 程序,就立即释放它。
- 调用 UnhookWindowsHookEx 可以释放一个全局 Hook 程序,但是这个函数不会释放包含 Hook 程序的 DLL 。这是因为全局 Hook 程序是在桌面中每个应用程序的进程上下文中调用的,这会导致对所有这些进程的 LoadLibrary 函数的隐式调用。因为不能对另一个进程调用 FreeLibrary 函数,所以没有办法释放 DLL 。当所有显式链接到 DLL 的进程终止或调用 FreeLibrary ,并且所有调用 Hook 程序的进程在 DLL 外部恢复处理后,系统最终才会释放 DLL 。
- 安装全局 Hook 程序的另一种方法是在 DLL 中提供一个安装函数以及 Hook 程序。使用此方法,安装 Hook 程序时应用程序不需要 DLL 模块的句柄。通过链接 DLL ,应用程序获得了对安装功能的访问权。安装函数可以在调用 SetWindowsHookEx 时提供 DLL 模块句柄和其他细节。DLL 也可以包含一个释放全局 Hook 程序的函数,应用程序可以在终止时调用这个 Hook 程序释放函数。