游戏外挂技术(一)-- HOOK基础

1 基本概念

Windows 操作系统是建立在事件驱动机制之上的,系统各部分之间的沟通也都是通过消息的相互传递而实现的。但在通常情况下,应用程序只能处理来自进程内部的消息或是从其他进程发过来的消息,如果需要对在进程外传递的消息进行拦截处理就必须采取一种被称为HOOK 的技术。钩子是 Windows 操作系统中非常重要的一种系统接口,用它可以轻松截获并处理在其他应用程序之间传递的消息,并由此可以完成一些普通应用程序难以实现的特殊功能。HookWindows 消息处理机制的一个平台, 应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理 window 消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

2 运行机制
1
、钩子链表和钩子过程:
   
每一个Hook 都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook 子程调用的回调函数,也就是该钩子的各个处理子过程。当与指定的Hook 类型关联的消息发生时,系统就把这个消息传递到Hook 子过程。一些Hook 子过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook 子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。

Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook 链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。

    钩子是一个应用程序定义的回调函数 (CALLBACK Function), 不能定义成某个类的成员函数,只能定义为普通的C 函数。在使用钩子时可以根据其监视范围的不同将其分为全局钩子和线程钩子两大类,其中 线程钩子只能监视某个线程,而全局钩子则可对在当前系统下运行的所有线程进行监视。显然,线程钩子可以看作是全局钩子的一个子集,全局钩子虽然功能强大但同时实现起来也比较烦琐:其钩子函数的实现必须封装在动态链接库中才可以使用。

钩子必须按照以下的语法:

LRESULT CALLBACK HookProc        //HookProc 是应用程序定义的名字。
(
      int nCode,
      WPARAM wParam,
    LPARAM lParam
);
nCode
参数是 Hook 代码, Hook 子程使用这个参数来确定任务。这个参数的值依赖于 Hook 类型,每一种 Hook 都有自己的 Hook 代码特征字符集。
wParam
lParam 参数的值依赖于 Hook 代码,但是它们的典型值是包含了关于发送或者接收消息的信息。

2 、钩子的安装与释放:

由于全局钩子具有相当的广泛性而且在功能上完全覆盖了线程钩子,因此下面就主要对应用较多的全局钩子的安装与使用进行讨论。前面已经提过,操作系统是通过调用钩子链表开始处的第一个钩子处理函数而进行消息拦截处理的。因此,为了设置钩子,只需将回调函数放置于链首即可,操作系统会使其首先被调用。在具体实现时由函数SetWindowsHookEx() 负责将回调函数放置于钩子链表的开始位置。SetWindowsHookEx() 函数原型声明如下:

HHOOK SetWindowsHookEx(

int idHook;

HOOKPROC lpfn;

HINSTANCE hMod;

DWORD dwThreadId);

其中:参数idHook 指定了钩子的类型,总共有如下13 种:

  WH_CALLWNDPROC 系统将消息发送到指定窗口之前的" 钩子"

  WH_CALLWNDPROCRET 消息已经在窗口中处理的" 钩子"

  WH_CBT 基于计算机培训的" 钩子"

  WH_DEBUG 差错" 钩子"

  WH_FOREGROUNDIDLE 前台空闲窗口" 钩子"

  WH_GETMESSAGE 接收消息投递的" 钩子"

  WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD" 钩子" 记录的输入消息

  WH_JOURNALRECORD 输入消息记录" 钩子"

  WH_KEYBOARD 键盘消息" 钩子"

  WH_MOUSE 鼠标消息" 钩子"

  WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息" 钩子"

  WH_SHELL 外壳" 钩子"

  WH_SYSMSGFILTER 系统消息" 钩子"

参数lpfn 为指向钩子处理函数的指针,即回调函数的首地址;

参数hMod 则标识了钩子处理函数所处模块的句柄 HINSTANCE

参数dwThreadId 指定被监视的线程,如果明确指定了某个线程的ID 就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为0 ,则表示此钩子为监视系统所有线程的全局钩子。此函数在执行完后将返回一个钩子句柄 HHOOK , 失败返回 NULL

虽然对于线程钩子并不要求其像全局钩子一样必须放置于动态链接库中,但是推荐其也在动态链接库中实现。因为这样的处理不仅可使钩子可为系统内的多个进程访问,也可以在系统中被直接调用,而且对于一个只供单进程访问的钩子,还可以将其钩子处理过程放在安装钩子的同一个线程内,此时SetWindowsHookEx() 函数的第三个参数也就是该线程的实例句柄。

SetWindowsHookEx() 函数完成对钩子的安装后,如果被监视的事件发生,系统马上会调用位于相应钩子链表开始处的钩子处理函数进行处理,每一个钩子处理函数在进行相应的处理时都要考虑是否需要把事件传递给下一个钩子处理函数。如果要传递,就通过函数 CallNestHookEx() 来解决。尽管如此,在实际使用时还是强烈推荐无论是否需要事件传递而都在过程的最后调用一次 CallNextHookEx( ) 函数,否则将会引起一些无法预知的系统行为或是系统锁定。该函数将返回位于钩子链表中的下一个钩子处理过程的地址,至于具体的返回值类型则要视所设置的钩子类型而定。该函数的原型声明如下:

LRESULT CallNextHookEx(HHOOK hhk; int nCode; WPARAM wParam; LPARAM lParam);   其中,参数hhk 为由SetWindowsHookEx() 函数返回的当前钩子句柄;参数nCode 为传给钩子过程的事件代码;参数 wParamlParam 则为传给钩子处理函数的参数值,其具体含义同设置的钩子类型有关。

钩子函数也可以通过直接返回TRUE 来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。

最后,由于安装钩子对系统的性能有一定的影响,所以在钩子使用完毕后应及时将其卸载以释放其所占资源。释放钩子的函数为 UnhookWindowsHookEx() ,该函数比较简单只有一个参数用于指定此前由SetWindowsHookEx() 函数所返回的钩子句柄,原型声明如下:

  BOOL UnhookWindowsHookEx(HHOOK hhk);

函数成功返回TRUE ,否则返回FALSE


3
系统钩子与线程钩子

SetWindowsHookEx() 函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。线程 HOOK 用于监视指定线程的事件消息。线程 HOOK 一般在当前线程或者当前线程派生的线程内。系统 HOOK 监视系统中的所有线程的事件消息。因为系统 HOOK 会影响系统中所有的应用程序,所以 HOOK 函数必须放在独立的动态链接库 (DLL) 中。系统自动将包含 " 钩子回调函数 "DLL 映射到受钩子函数影响的所有进程的地址空间中,即将这个 DLL 注入了那些进程。
几点说明:
1 ) 如果对于同一事件(如鼠标消息)既安装了线程 HOOK 又安装了系统 HOOK ,那么系统会自动先调用线程 HOOK ,然后调用系统HOOK
2 )对同一事件消息可安装多个 HOOK 处理过程,这些 HOOK 处理过程形成了 HOOK 链。当前 HOOK 处理结束后应把 HOOK 信息传递给下一个 HOOK 函数。
3HOOK 特别是系统 HOOK 会消耗消息处理时间,降低系统性能。只有在必要的时候才安装 HOOK ,在使用完毕后要及时卸载。

 

4 一些运行机制

Win16 环境中, DLL 的全局数据对每个载入它的进程来说都是相同的;而在 Win32 环境中,情况却发生了变化, DLL 函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入 DLL 时,操作系统自动把 DLL 地址映射到该进程的私有空 间,也就是进程的虚拟地址空间,而且也复制该 DLL 的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的 DLL 的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。因此,在 Win32 环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个 Dll 的各进程之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
#pragma data_seg
预处理指令用于设置共享数据段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()
#pragma data_seg("SharedDataName")#pragma data_seg() 之间的所有变量将被访问该 Dll 的所有进程看到和共享。再加上一条指令 #pragma comment(linker,"/section:.SharedDataName,rws"), 那么这个数据节中的数据可以在所有 DLL 的实例之间共享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里。这使得 DLL 成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。

 

5 钩子类型

1 WH_CALLWNDPROCWH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC
WH_CALLWNDPROCRET Hooks 使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC Hook 子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook 子程。
WH_CALLWNDPROCRET Hook
传递指针到CWPRETSTRUCT 结构,再传递到Hook 子程。
CWPRETSTRUCT
结构包含了来自处理消息的窗口过程的返回 值,同样也包括了与这个消息关联的消息参数。
2
WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook 子程,这些事件包括:
1.
激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
2.
完成系统指令;
3.
来自系统消息队列中的移动鼠标,键盘事件;
4.
设置输入焦点事件;
5.
同步系统消息队列事件。
Hook
子程的返回值确定系统是否允许或者防止这些操作中的一个。
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
监视输入到线程消息队列中的键盘消息。
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 。通过使用这个函数,应用程序能够在模式循环期 间使用相同的代码去过滤消息,如同在主消息循环里一样。
13
WH_SHELL Hook
外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook 子程。
WH_SHELL
共有5钟情況:
1.
只要有个 top-levelunowned 窗口被产生、起作用、或是被摧毁;
2.
Taskbar 需要重画某个按钮;
3.
当系统需要显示关于 Taskbar 的一个程序的最小化形式;
4.
当目前的键盘布局状态改变;
5.
当使用者按 Ctrl+Esc 去执行 Task Manager (或相同级别的程序)。
按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL 消息之前,应用程序必须调用SystemParametersInfo function 注册它自己。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值