Hook(钩子技术)基本知识讲解,原理

一、什么是HOOK(钩子)  API

Windows消息传递机制,当在应用程序进行相关操作,例如点击鼠标、按下键盘,操作窗口等,操作系统能够感知这一事件,接着把此消息放到系统消息队列,然后到应用程序的消息序列中,应用程序通过Getmessage函数取出消息,然后调用DispatchMessage函数将这条消息调度给操作系统,操作系统会调用在设计窗口类时指定的应用程序窗口对这一消息进行处理,处理过程如图所示:

在《VC深入详解》一书将钩子过程比喻为警察为了抓逃犯而设置的检查站,基本原理也确实与此类似。就是在应用程序将信息传递给操作系统时(图中③ 的),对消息进行捕获和过滤,从而阻止消息发送到指定的窗口过程,最终完成对某些消息的屏蔽功能。

HOOK(钩子,挂钩)是一种实现Windows平台下类似于中断的机制。HOOK机制允许应用程序拦截并处理Windows消息或指定事件,当指定的消息发出后,HOOK程序就可以在消息到达目标窗口之前将其捕获,从而得到对消息的控制权,进而可以对该消息进行处理或修改,加入我们所需的功能。钩子按使用范围分,可分为线程钩子和系统钩子,其中,系统钩子具有相当大的功能,几乎可以实现对所有Windows消息的拦截、处理和监控。这项技术涉及到两个重要的API,一个是SetWindowsHookEx,安装钩子;另一个是UnHookWindowsHookEx,卸载钩子。

对于Windows系统,它是建立在事件驱动机制上的,说白了就是整个系统都是通过消息传递实现的。hook(钩子)是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。所以说,我们可以在系统中自定义钩子,用来监视系统中特定事件的发生,完成特定功能,如屏幕取词,监视日志,截获键盘、鼠标输入等等。

程序员在讨论时也常说“可以先钩住再处理”,即执行某操作之前,优先处理一下,再决定后面的执行走向
        钩子的种类很多,每种钩子可以截获相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 中。
 
   
 所以说,hook(钩子)就是一个Windows消息的拦截机制,可以拦截单个进程的消息(线程钩子),也可以拦截所有进程的消息(系统钩子),也可以对拦截的消息进行自定义的处理。Windows消息带了一些程序有用的信息,比如Mouse类信息,就带有鼠标所在窗体句柄、鼠标位置等信息,拦截了这些消息,就可以做出例如金山词霸一类的屏幕取词功能。

二、Hook 分类

(1) 线程钩子监视指定线程的事件消息。
(2) 系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)中。这是系统钩子和线程钩子很大的不同之处。

三、HOOK(钩子)的工作原理

在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个线程钩子,您进程中的钩子函数将被调用。如果是一个系统钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。

当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。
       几点需要说明的地方: 
  (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。 
  (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 
  (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。

Hook(钩子技术)基本知识讲解,原理的相关教程结束。

相关推荐:

为什么Python中NumPy的运算速度比原生List快_详解向量化计算与C底层原理

NumPy运算更快的关键在于绕过Python解释器开销、使用连续内存与向量化指令,而非单纯“用了C”;小数组易受缓存未命中和初始化开销影响,需保持数据连续、类型单一、操作可并行。 NumPy运算比Python原生list快,不是因为“它用了C”,而是因为它绕过了Python解释器的运行时开销,并把计...

为什么Python 3.13将引入实验性JIT_探索Copy-and-Patch技术

Python3.13的JIT是仅限源码编译、需显式启用--with-jit且依赖LLVM的实验性特性,不包含在官方二进制包中,无运行时开关或API,仅对极少数纯Python字节码模式做模板化加速,不具备通用优化能力。 copy-and-patch不是用户可启用的开关,也不是运行时能调用的API。Py...

为什么Python字典在3.7版本后更省内存_解析紧凑型字典的实现原理

Python3.7+字典更省内存是因采用紧凑字典结构,平均节省20%~25%:旧版用稀疏哈希表致近1/3内存为占位符,新版拆分为小而稀疏的indices数组和大而紧凑的entries数组,消除指针、填充及哈希缓存三类冗余。 Python3.7+的字典确实更省内存,这不是错觉,而是底层结构从“稀疏哈希...

如何在Python Flask中实现全局拦截器_使用before_request钩子

before_request在每次请求进入Flask应用、到达视图函数前必定执行,不区分HTTP方法和路由,但默认不处理静态文件;它仅在WSGI层生效,不能修改response,只能读取request、设置g变量、抛异常或返回响应。 before_request什么时候执行? before_requ...

Python集合运算为什么比循环快_哈希表底层原理与位运算优化

集合交集比for循环快几十倍,本质是哈希表O(1)查找与列表O(n)线性扫描的差异;set底层用位运算哈希索引、开放寻址解决冲突,负载因子超0.75触发扩容;非查重场景或需保序时不宜盲目替换list。 集合交集&为什么比forxina:ifxinb快几十倍 根本不是语法糖,是底层哈希表查表和...