[学习笔记] Windows编程——窗口和消息 ——(八)Hook

前言:

学习笔记,随时更新。如有谬误,欢迎指正。


说明:

  1. 红色字体为较为重要部分。
  2. 绿色字体为个人理解部分。

原文链接:https://learn.microsoft.com/en-us/windows/win32/winmsg/hooks

1 Hook

  1. Hook 是系统消息处理机制中的一个点,在其之中应用程序可以安装子例程来监视系统中的消息流量,并在某些类型的消息到达目标窗口程序之前对其进行处理。

1.1 Hook 概述

  1. Hook 是应用程序截获消息、鼠标操作和击键等事件的机制。
  2. 截获特定类型的事件的函数称为 Hook 程序。 Hook 程序可以对其接收的每个事件执行操作,然后修改或放弃该事件。
  3. Hook 往往会降低系统速度,因为它们会增加系统必须对每条消息执行的处理量。应仅在必要时安装 Hook ,并尽快将其删除

1.1.1 Hook 链

  1. 系统支持多种不同类型的 Hook ,每种类型都提供对其消息处理机制的不同方面的访问。
  2. 系统为每种类型的 Hook 维护单独的 Hook 链。Hook 链是指向应用程序定义的特殊回调函数(称为 Hook 程序)的指针列表。当发生与特定类型的 Hook 关联的消息时,系统会将消息依次传递给 Hook 链中引用的每个 Hook 程序。
  3. Hook 程序可以执行的操作取决于所涉及的 Hook 类型。某些 Hook 类型的 Hook 程序只能监视消息,而其他 Hook 类型的 Hook 程序可以修改消息或通过链停止其调用传递,从而阻止它们到达下一个 Hook 程序或目标窗口。

1.1.2 Hook 程序

  1. 使用 SetWindowsHookEx 函数将 Hook 程序安装到与 Hook 关联的链中。
  2. SetWindowsHookEx 函数始终在 Hook 链的开头安装 Hook 程序。当发生由特定类型的 Hook 监视的事件时,系统会在与 Hook 关联的 Hook 链的开头调用 Hook 程序。
  3. 链中的每个 Hook 程序会确定是否将事件传递给下一个程序。Hook 程序通过调用 CallNextHookEx 函数将事件传递给下一个 Hook 程序。调用 CallNextHookEx 是可选的,但强烈建议使用,否则,安装了 Hook 的其他应用程序将不会收到 Hook 通知,因此行为可能不正确 除非绝对需要阻止其他应用程序看到通知,否则都应调用 CallNextHookEx
  4. 全局 Hook 监视与调用线程位于同一桌面中的所有线程的消息。特定于线程的 Hook 仅监视单个线程的消息。
  5. 全局 Hook 程序可以在调用线程所在的桌面中的任何应用程序的上下文中调用,因此该 Hook 程序必须位于单独的 DLL 模块中。
  6. 特定于线程的 Hook 程序仅在关联线程的上下文中调用。如果应用程序为其自己的线程之一安装 Hook 程序,则 Hook 程序可以位于与应用程序代码的其余部分相同的模块中,也可以位于 DLL 中。但如果应用程序为其他应用程序的线程安装 Hook 程序,则该 Hook 程序必须位于 DLL 中。
  7. 应仅将全局 Hook 用于调试目的,否则,应避免使用它们。全局 Hook 会损害系统性能,并导致与实现相同类型全局 Hook 的其他应用程序冲突。

1.1.3 Hook 类型

1.1.3.1 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET
  1. 使用 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 类型的 Hook 可以监视发送到窗口程序的消息。系统在将消息传递到接收窗口的窗口程序之前调用 WH_CALLWNDPROC 的 Hook 程序,并在窗口程序处理消息后调用 WH_CALLWNDPROCRET 的 Hook 程序。
  2. 有关详细信息,请参阅 CallWndProcCallWndRetProc 回调函数。
1.1.3.2 WH_CBT
  1. 在激活、创建、销毁、最小化、最大化、移动或调整窗口大小之前,在完成系统命令之前,在从系统消息队列中删除鼠标或键盘事件之前,在设置输入焦点前,或在与系统消息队列同步之前,系统会调用 WH_CBT 类型的 Hook 程序。Hook 程序的返回值会决定系统是允许还是阻止这些操作。WH_CBT 类型的 Hook 主要用于基于计算机训练 (CBT) 的应用程序。
  2. 有关详细信息,请参阅 CBTProc 回调函数。
1.1.3.3 WH_DEBUG
  1. 在调用与系统中任何其他 Hook 关联的 Hook 程序之前,系统将调用 WH_DEBUG 类型的 Hook 程序。可以使用此 Hook 来确定是否允许系统调用与其他类型的 Hook 关联的 Hook 程序。
  2. 有关详细信息,请参阅 DebugProc 回调函数。
1.1.3.4 WH_FOREGROUNDIDLE
  1. 当应用程序的前台线程即将变为空闲状态时,系统会调用 WH_FOREGROUNDIDLE 类型的 Hook 程序。借助 WH_FOREGROUNDIDLE 类型的 Hook ,可以在前台线程空闲时执行低优先级任务。
  2. 有关详细信息,请参阅 ForegroundIdleProc 回调函数。
1.1.3.5 WH_GETMESSAGE
  1. WH_GETMESSAGE 类型的 Hook 使应用程序能够监视 GetMessage 或 PeekMessage 函数即将返回的消息。可以使用 WH_GETMESSAGE 类型的 Hook 来监视鼠标和键盘输入、以及发布到消息队列的其他消息。
  2. 有关详细信息,请参阅 GetMsgProc 回调函数。
1.1.3.6 WH_JOURNALPLAYBACK
  1. WH_JOURNALPLAYBACK 类型的 Hook 使应用程序能够将消息插入系统消息队列。可以使用此类型的 Hook 回放之前使用 WH_JOURNALRECORD 录制的一系列鼠标和键盘事件。
  2. 只要安装了 WH_JOURNALPLAYBACK类型的 Hook ,常规鼠标和键盘输入就处于禁用状态。
  3. WH_JOURNALPLAYBACK 类型的 Hook 是全局 Hook ,不能用作特定于线程的 Hook 。
  4. WH_JOURNALPLAYBACK 型的 Hook 返回超时值。此值告知系统在处理来自回放 Hook 的当前消息之前要等待多少毫秒。这使 Hook 能够控制它回放的事件的计时。
  5. 从 Windows 11 开始,此类型的 Hook 将被废弃。因此强烈建议改为调用 SendInput
  6. 有关详细信息,请参阅 JournalPlaybackProc 回调函数。
1.1.3.7 WH_JOURNALRECORD
  1. 使用 WH_JOURNALRECORD 类型的Hook 可以监视和记录输入事件。通常,可以使用此类型的 Hook 记录一系列鼠标和键盘事件,以便稍后使用 WH_JOURNALPLAYBACK 播放。
  2. WH_JOURNALRECORD 类型的 Hook 是全局 Hook ,不能用作特定于线程的 Hook 。
  3. 从 Windows 11 开始,此类型的 Hook 将被废弃。因此强烈建议改为调用 SendInput
  4. 有关详细信息,请参阅 JournalRecordProc 回调函数。
1.1.3.8 WH_KEYBOARD_LL
  1. 使用 WH_KEYBOARD_LL 类型的 Hook 可以监控即将在线程输入队列中发布的键盘输入事件。
  2. 有关详细信息,请参阅 LowLevelKeyboardProc 回调函数。
1.1.3.9 WH_KEYBOARD
  1. WH_KEYBOARD 类型的 Hook 使应用程序能够监控即将由 GetMessage 或 PeekMessage 函数返回的 WM_KEYDOWN 和 WM_KEYUP 消息的消息流量。您可以使用 WH_KEYBOARD 类型的 Hook 来监控发送到消息队列的键盘输入。
  2. 有关详细信息,请参阅 KeyboardProc 回调函数。
1.1.3.10 WH_MOUSE_LL
  1. 使用 WH_MOUSE_LL 类型的 Hook 可以监控即将在线程输入队列中发布的鼠标输入事件。
  2. 有关详细信息,请参阅 LowLevelMouseProc 回调函数。
1.1.3.11 WH_MOUSE
  1. 使用 WH_MOUSE 类型的 Hook 可以监控 GetMessage 或 PeekMessage 函数即将返回的鼠标消息。 可以使用 WH_MOUSE 类型的 Hook 来监控发布到消息队列的鼠标输入。
  2. 有关详细信息,请参阅 MouseProc 回调函数。
1.1.3.12 WH_MSGFILTER 和 WH_SYSMSGFILTER
  1. WH_MSGFILTER 和 WH_SYSMSGFILTER 类型的 Hook 可以监控菜单、滚动条、消息框或对话框即将处理的消息,并检测用户按 Alt+TAB 或 Alt+ESC 组合键后即将激活其他窗口的时机。
  2. WH_MSGFILTER 类型的 Hook 只能监控传递给菜单、滚动条、消息框或由安装 Hook 程序的应用程序创建的对话框的消息。WH_SYSMSGFILTER 类型的 Hook 能监控所有应用程序的此类消息。
  3. WH_MSGFILTER 和 WH_SYSMSGFILTER 类型的 Hook 使您能够在模态循环期间执行消息过滤,这相当于在主消息循环中完成的过滤。例如,应用程序经常在主循环中检查从队列中检索消息到分派消息之间的新消息,并根据需要执行特殊处理。然而,在模态循环期间,系统检索和调度消息,而不允许应用程序在其主消息循环中过滤消息。如果应用程序安装了 WH_MSGFILTER 或 WH_SYSMSGFILTER 类型的 Hook 程序,系统将在模态循环期间调用该程序。
  4. 应用程序可以通过调用 CallMsgFilter 函数直接调用 WH_MSGFILTER 类型的 Hook 。通过使用这个函数,应用程序可以在模态循环期间使用与主消息循环相同的代码来过滤消息。为此,需要将过滤操作封装在 WH_MSGFILTER 类型的 Hook 的 Hook 程序中,并在调用 GetMessage 和 DispatchMessage 函数之间调用 CallMsgFilter 。
  5. CallMsgFilter 的最后一个参数被简单地传递给 Hook 程序,您可以输入任何值。通过定义诸如 MSGF_MAINLOOP 之类的常量,Hook 程序可以使用该值来确定此次 Hook 程序是从何处调用的。
  6. 有关详细信息,请参阅 MessageProcSysMsgProc 回调函数。
1.1.3.13 WH_SHELL
  1. Shell 应用程序可以使用 WH_SHELL 类型的 Hook 来接收重要的通知。当 Shell 应用程序即将被激活、以及创建或销毁顶层窗口时,系统会调用 WH_SHELL 类型的 Hook 程序。
  2. 注意,自定义的 Shell 应用程序不接收 WH_SHELL 消息。因此,任何将自己注册为默认 Shell 的应用程序都必须在它(或任何其他应用程序)接收 WH_SHELL 消息之前调用 SystemParametersInfo 函数,函数中必传入 SPI_SETMINIMIZEDMETRICS 和 MINIMIZEDMETRICS 参数,并且 MINIMIZEDMETRICS 结构体中的 iArrange 成员变量要被设置为 ARW_HIDE 。
  3. 有关详细信息,请参阅 ShellProc 回调函数。

1.2 使用 Hook

  1. 示例:https://learn.microsoft.com/en-us/windows/win32/winmsg/using-hooks
  2. 调用 UnhookWindowsHookEx 函数并指定要释放的 Hook 程序的句柄,可以释放线程特定的 Hook 程序(从 Hook 链中删除它的地址)。一旦应用程序不再需要 Hook 程序,就立即释放它。
  3. 调用 UnhookWindowsHookEx 可以释放一个全局 Hook 程序,但是这个函数不会释放包含 Hook 程序的 DLL 。这是因为全局 Hook 程序是在桌面中每个应用程序的进程上下文中调用的,这会导致对所有这些进程的 LoadLibrary 函数的隐式调用。因为不能对另一个进程调用 FreeLibrary 函数,所以没有办法释放 DLL 。当所有显式链接到 DLL 的进程终止或调用 FreeLibrary ,并且所有调用 Hook 程序的进程在 DLL 外部恢复处理后,系统最终才会释放 DLL 。
  4. 安装全局 Hook 程序的另一种方法是在 DLL 中提供一个安装函数以及 Hook 程序。使用此方法,安装 Hook 程序时应用程序不需要 DLL 模块的句柄。通过链接 DLL ,应用程序获得了对安装功能的访问权。安装函数可以在调用 SetWindowsHookEx 时提供 DLL 模块句柄和其他细节。DLL 也可以包含一个释放全局 Hook 程序的函数,应用程序可以在终止时调用这个 Hook 程序释放函数。

1.3 Hook 参考目录

1.3.1 Hook 程序

  1. Hook 程序

1.3.2 Hook 通知

  1. Hook 通知

1.3.3 Hook 结构体

  1. Hook 结构体
二、API Hook的原理 这里的API既包括传统的Win32 APIs,也包括任何Module输出的函数调用。熟悉PE文件格 式的朋友都知道,PE文件将对外部Module输出函数的调用信息保存在输入表中,即.idata段。 下面首先介绍本段的结构。 输入表首先以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式链接 进来的DLL都有一个IID.在这个数组中的最后一个单元是NULL,可以由此计算出该数组的项数。 例如,某个PE文件从两个DLL中引入函数,就存在两个IID结构来描述这些DLL文件,并在两个 IID结构的最后由一个内容全为0的IID结构作为结束。几个结构定义如下: IMAGE_IMPORT_DESCRIPTOR struct union{ DWORD Characteristics; ;00h DWORD OriginalFirstThunk; }; TimeDateStamp DWORD ;04h ForwarderChain DWORD ;08h Name DWORD ;0Ch FirstThunk DWORD ;10h IMAGE_IMPROT_DESCRIPTOR ends typedef struct _IMAGE_THUNK_DATA{ union{ PBYTE ForwarderString; PDWORD Functions; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; }u1; } IMAGE_IMPORT_BY_NAME结构保存一个输入函数的相关信息: IMAGE_IMPORT_BY_NAME struct Hint WORD ? ;本函数在其所驻留DLL的输出表中的序号 Name BYTE ? ;输入函数的函数名,以NULL结尾的ASCII字符串 IMAGE_IMPORT_BY_NAME ends OriginalFirstThunk(Characteristics):这是一个IMAGE_THUNK_DATA数组的RVA(相对于PE文件 起始处)。其中每个指针都指向IMAGE_IMPORT_BY_NAME结构。 TimeDateStamp:一个32位的时间标志,可以忽略。 ForwarderChain:正向链接索引,一般为0。当程序引用一个DLL中的API,而这个API又引用别的 DLL的API时使用。 NameLL名字的指针。是个以00结尾的ASCII字符的RVA地址,如"KERNEL32.DLL"。 FirstThunk:通常也是一个IMAGE_THUNK_DATA数组的RVA。如果不是一个指针,它就是该功能在 DLL中的序号。 OriginalFirstThunk与FirstThunk指向两个本质相同的数组IMAGE_THUNK_DATA,但名称不同, 分别是输入名称表(Import Name Table,INT)输入地址表(Import Address Table,IAT)。 IMAGE_THUNK_DATA结构是个双字,在不同时刻有不同的含义,当双字最高位为1时,表示函数以 序号输入,低位就是函数序号。当双字最高位为0时,表示函数以字符串类型的函数名 方式输入,这时它是指向IMAGE_IMPORT_BY_NAME结构的RVA。 三个结构关系如下图: IMAGE_IMPORT_DESCRIPTOR INT IMAGE_IMPORT_BY_NAME IAT -------------------- /-->---------------- ---------- ---------------- |01| 函数1 ||02| 函数2 || n| ... |"USER32.dll" | |--------------------| | | FirstThunk |---------------------------------------------------------------/ -------------------- 在PE文件中对DLL输出函数的调用,主要以这种形式出现: call dword ptr[xxxxxxxx] 或 jmp [xxxxxxxx] 其中地址xxxxxxxx就是IAT中一个IMAGE_THUNK_DATA结构的地址,[xxxxxxxx]取值为IMAGE_THUNK_DATA 的值,即IMAGE_IMPORT_BY_NAME的地址。在操作系统加载PE文件的过程中,通过IID中的Name加载相应 的DLL,然后根据INT或IAT所指向的IMAGE_IMPORT_BY_NAME中的输入函数信息,在DLL中确定函数地址, 然后将函数地址写到IAT中,此时IAT将不再指向IMAGE_IMPORT_BY_NAME数组。这样[xxxxxxxx]取到的 就是真正的API地址。 从以上分析可以看出,要拦截API的调用,可以通过改写IAT来实现,将自己函数的地址写到IAT中, 达到拦截目的。 另外一种方法的原理更简单,也更直接。我们不是要拦截吗,先在内存中定位要拦截的API的地址, 然后改写代码的前几个字节为 jmp xxxxxxxx,其中xxxxxxxx为我们的API的地址。这样对欲拦截API的 调用实际上就跳转到了咱们的API调用去了,完成了拦截。不拦截时,再改写回来就是了。 这都是自己从网上辛辛苦苦找来的,真的很好啊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值