开yun体育app官方下载入口 Windows Hook原理与实现

Windows Hook原理及实现

教程参考《逆向工程核心原理》

SSDT挂钩参考:

1. 概述

Hook技术广泛应用于安防多个领域。例如,杀毒软件的主动防御功能涉及到一些敏感API的监控,需要对这些API进行Hook;窃取密码的木马病毒需要接收键盘输入。挂钩键盘消息;甚至Windows系统和一些应用程序在打补丁时也需要使用Hook技术。接下来我们来学习一下Hook技术的原理。

下图简单易懂地解释了Hook机制。在notepad.exe和kernel32.dll之间挂上一个“钩子”,将它们想要使用的CreateFile()函数替换掉,替换成MyCreateFile()函数,就可以实现我们想要的自定义功能了。

1

2.挂钩分类

Hooks分为应用层(Ring3)Hooks和内核层(Ring0)Hooks。应用层Hooks适用于x86和x64,而内核层Hooks一般只适用于x86平台,因为从64位版本的Windows Vista开始引入了Patch Guard技术。极大地限制了Windows x64内核hook的使用。

2

3.消息挂钩

3.1 技术原理

首先,我们来了解一下常规的Windows消息流:

[1] 当键盘输入事件发生时,会向[OS消息队列]添加一条WM_KEYDOWN消息。

[2]操作系统判断事件发生在哪个应用程序中,然后从【操作系统消息队列】中取出消息,添加到对应应用程序的【应用程序消息队列】中。

[3]应用程序(如记事本)监视自身的[应用程序消息队列],发现新添加的WM_KEYDOWN消息后,调用相应的事件处理程序进行处理。

因此,我们只需要在【操作系统消息队列】和【应用程序消息队列】之间安装一个钩子,就可以窃取键盘消息,实现恶意操作。

3

那么我们如何安装这个消息钩子呢?这很简单。 Windows提供了一个官方函数SetWindowsHookEx()用于设置消息Hooks。编程时,只需调用该API即可实现Hooks。

消息挂钩经常被窃取秘密的​​木马用来监视用户的键盘输入。只需要在程序中编写如下代码即可挂钩键盘消息:

设置WindowsHookEx(

WH_KEYBOARD, //键盘消息

KeyboardProc, //Hook函数(处理键盘输入的函数)

hInstance, //钩子函数所在DLL的句柄

0 //该参数用于设置要Hooked的线程ID。当为0时,表示监控所有线程。

3.2 代码实现

过滤记事本输入的示例——核心代码:

4

虽然这个API简单高效,但它也有一个缺点,那就是它只能监听少量的消息,比如击键消息、鼠标移动消息和窗口消息。如果想要更全面地hook系统,就必须使用下面介绍的两种Hook方法。

4.调试钩子

4.1 技术原理

这种Hook方法的原理类似于调试器的工作机制。其核心思想是导致进程中出现异常,然后自己捕获异常,从而可以在调试状态的层面上进行恶意操作。

下图展示了常规流程的异常事件处理。当一个进程没有被其他进程调试时,它的默认异常事件处理程序是操作系统。一旦进程发生异常,操作系统就会捕获异常并进行相应的处理。

5

如果该进程被另一个进程(如OllyDbg)调试,异常事件的处理将交给调试器。例如,如果过程中发生被零除错误,OllyDbg将收到异常事件并进行相应处理。

PS:调试器不处理或不关心的调试事件最终由操作系统处理。

6

因此,调试Hook的核心思想就是修改API的第一个字节为0xCC(INT 3)。当调用 API 时,由于触发异常,控制权将转移到调试器。

4.2 代码实现

5.注入钩子

Hook的核心思想就是修改API代码。但是,例如,如果进程A想要hook进程B的CreateProcess函数,那么A就没有权限修改B内存中的代码。我应该怎么办?这时使用DLL注入技术就可以解决这个问题。我们将Hook代码写入DLL(或者直接是shellcode),并将这个DLL注入到B进程中。此时,因为DLL在B进程的内存中,所以我有权限直接修改B的内存中的代码。

5.1 IAT 钩子

顾名思义,IAT Hook就是通过修改IAT中的函数地址来hook API。

5.1.1 技术原理

如下图,左图红框是IAT修改前的状态开yun体育官网入口登录app,表示SetWindowTextW()的地址为0x77D0960E,所以当calc.exe执行call SetWindowTextW(dword ptr[01001110])时,实际上执行的是call 0x77D0960E 。

右图为挂钩后的状态。 IAT中SetWinowTextW()的地址已修改为0x10001000。当calc.exe执行call SetWindowTextW (dword ptr[01001110])时,它实际上执行的是call 0x10001000(这就是恶意代码的来源)。起始地址),然后我们就可以进行我们想做的操作了。

7

8

5.1.2 代码实现

下图是Hook IAT的代码实现。核心代码很少,大部分代码都在IAT的计算中。这里值得注意的是开yun体育官网入口登录体育,当我们用我们的恶意函数替换SetWindowTextW()后,我们必须在我们的恶意函数执行后回调SetWindowTextW()(我们在Hook之前保存了SetWindowTextW()的地址),以保证功能完整性。

9

5.2 内联钩子

与IAT Hook相比,inline Hook更加简单粗暴。它直接修改内存中任意函数的代码,并劫持到Hook API。同时它比IAT Hook的适用范围更广,因为只要内存中有函数就可以hook,而后者只能hook IAT表中存在的函数(有的程序会动态加载函数) )。

5.2.1 技术原理

Inline Hook的目标是系统函数,如下所示。左图是Hook之前的状态。当procexp.exe进程调用ZwQuerySystemInformation()函数时,ZwQuerySystemInformation()的代码是普通代码。右图是Hook后的状态。注意红框中的代码。 ZwQuerySystemInformation()函数的前5个字节被修改为jmp 0x10001120,这就是我们的恶意代码的地址。之后,我们就可以开始我们的定制操作了。 0x1000116A 我们首先执行脱钩操作(dehooking)以恢复 ZwQuerySystemInformation() 代码。您可能想知道为什么刚修改完就必须恢复它。原因很简单。 Hook的目的是当某个函数被调用时,我们可以劫持进程的执行流程。现在我们已经劫持了进程的执行流程云开·全站app登录网页入口,我们可以恢复ZwQuerySystemInformation()代码,这样我们的恶意代码就可以正常调用ZwQuerySystemInformation()了。执行恶意代码后,再次hook并监听该函数。

10

11

5.2.2 代码实现

首先获取原始API的地址并保存到pfnOrg中,然后修改内存段属性为RWX,备份原始代码(用于后续代码恢复),实时计算JMP的相对偏移量,最后修改API 的前 5 个字节的代码。恢复记忆属性。

12

5.3 修补程序挂钩

从上一节对Code Hook方法的讲解中,我们会发现Code Hook存在一个效率问题,因为每个Code Hook都要进行“hook + dehook”操作,这意味着API的前5个字节必须是修改了两次。很多时候,这样,当我们要进行全局Hook时,系统运行效率就会受到影响。而且,当一个线程尝试运行某段代码时,如果另一个线程正在“写入”该段代码,就会出现程序冲突,最终会出现一些错误。

有没有办法避免这个隐患呢?答案是肯定的,可以使用HotFix Hook(“热补丁”)方法。

5.3.1 技术原理

上面提取的API起始代码有以下两个明显的相似之处:

[1] API 代码以“MOV EDI,EDI”指令开头。

[2]API代码上方有5条NOP指令。

MOV EDI,EDI用于将EDI的值再次复制到EDI中,没有实际意义。也就是说,API起始码的MOV指令(2字节)和其上面的5条NOP指令(5字节)加起来总共7字节指令,没有任何意义。所以我们可以通过修改这7个字节来实现Hook操作。由于这种方法可以在进程运行时临时更改进程内存中的库文件,因此微软也经常使用这种方法来应用“热补丁”。

13

如下,将前7个字节修改为:

JMP 10001000(恶意代码地址)

JMP 短 0x7C802366

这样,调用API时,首先执行JMP SHORT 0x7C802366,然后跳转到JMP 10001000,最后跳转到恶意代码的起始点0x10001000。

14

在5字节代码修改技术中,“脱钩”就是“调用原来的函数”,而使用“热补丁”技术hook API时,即使API代码被修改(从[API起始地址+2]地址开始,原来的API仍然可以正常调用,并且执行的动作是一模一样的)。

5.3.2 代码实现

该技术的难点在于计算偏移地址。

由于HotFix Hook需要修改7字节代码,因此并非所有API都适合该方法。如果不是,请使用5字节代码修改技术。

6.SSDT挂钩

SSDT Hook属于内核层Hook,也是最底层的Hook。由于用户层API的最终本质是调用内核API(Kernel32->Ntdll->Ntoskrnl),所以这种Hook方法是最强大的。不过,值得注意的是

6.1SSDT原理

内核通过SSDT(系统服务描述符表)调用各种内核函数。 SSDT是一个函数表。只要得到一个索引值,就可以根据这个索引值在表中得到想要的函数地址。

下图是0x80563520处ntoskrnl对应的服务描述符表结构SSDT。那么前32位0x804e58a0就是SSDT Base,也就是SSDT的首地址。

通过对这些地址进行反汇编,可以获得相应的功能。下图中,0x80591bfb是SSDT表中第一个函数NtAcceptConnectPort的地址。

18

接下来我们尝试找到 NtQuerySystemInformation 的地址。首先,我们反汇编ZwQuerySystemInformation,得知它正在SSDT中寻找索引号为0xAD的地址。

从上面我们可以知道NtQuerySystemInformation的索引号是0xAD,那么我们就可以计算出NtQuerySystemInformation的地址:

0x80591bfb + 0xAD = 0x8056ff1

6.2SSDT钩子

其实内核层Hook并没有想象中的那么先进。 Hook的原理是一样的,只是Hook的对象不同。 Hook步骤还是一样的5步:

1.修改内存属性为RWX。

2、拼接汇编代码jmp[HookFunc]。

3. 保存原代码头的5个字节。

4. 将前 5 个字节替换为 2 的汇编代码。

5. 恢复前5个字节。

6.恢复记忆属性。

21

关键词:

客户评论

我要评论