基本概念
Windows系统是建立在事件驱动机制上的,整个系统都是通过消息传递实现的。Hook,中文里常常被译作“钩子”或者“挂钩”,是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。即,Hook可以改变程序执行流程,将程序原有的执行流程拦截,更改程序流向,并可以执行自己的自定义代码。Hook可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。
Hook技术被广泛应用于安全的多个领域,例如杀毒软件的主动防御功能,涉及到对一些敏感API的监控,就需要对这些API进行Hook;窃取密码的木马病毒,为了接收键盘的输入,需要Hook键盘消息;甚至是Windows系统及一些应用程序,在打补丁时也需要用到Hook技术。
基本原理
在真正执行原始API之前,对程序流进行拦截,使其先执行自定义的代码后,再执行原始API调用流程。简而言之就是篡改程序的运行路径,实现执行自定义程序的目的。
例如,在calc.exe和kernel32.dll之间挂上一个钩子,将它们之间要使用的CreateFile函数替换成自定义的EvilFunc函数,即可实现想要实现的自定义功能。
分类
Hook大体分为应用层Hook和内核层Hook。
- 应用层Hook:在Ring3层的Hook机制,主要涉及到用户态的程序和接口。这包括了对API函数的拦截和处理,例如通过IAT钩子(Import Address Table Hook)来拦截DLL的导入函数调用,或者通过Inline Hook直接在内存中修改函数的指令来实现拦截。
- 消息Hook
- 注入Hook
- IAT Hook(导入地址表钩子):通过修改PE文件结构中的导入表来实现对特定函数调用的拦截。这种方法通常用于DLL注入,通过修改目标进程的导入表来劫持函数调用。
- Inline Hook(内联钩子):直接在内存中修改函数的指令来实现拦截。这种方法通过在目标函数的指令流中插入跳转指令(如,jmp),使得函数调用被重定向到自定义的代码中。
- HotFix Hook(热修复钩子):通过在函数的头部分寻找可替换的“无用”指令(例如 mov edi, edi),并将这些指令替换为跳转指令(如,EB F9)来实现拦截。这种方法不需要频繁地挂载和卸载Hook,从而避免了资源浪费,提高了效率。当需要调用原始函数时,只需跳过这个短跳转指令即可,而无需还原被替换的指令,使得Hook过程更加高效和稳定。
- VEH Hook(向量化异常处理钩子):通过注册一个异常处理函数到操作系统的异常处理链表中,来实现对特定函数的拦截。当目标函数被调用时,会触发一个软件断点(如将指令修改为int 3),从而引发一个异常。VEH钩子作为异常处理程序,可以捕获这个异常,并在异常处理函数中执行自定义的代码。在处理完异常后,可以恢复原始函数的执行,或者修改寄存器和栈来改变程序的执行流程。VEH钩子的优点是它不需要修改原始函数的代码,因此可以实现无痕Hook,难以被检测工具发现。VEH钩子通常用于高级恶意软件技术,以绕过操作系统和安全软件的检测。
- ……
- 调试Hook
- 内核层Hook:在Ring0层的Hook机制,通常涉及到更底层的系统调用和内核API的拦截。例如SSDT钩子(System Service Descriptor Table Hook),它通过修改系统服务描述符表来拦截内核API调用。
- SSDT Hook(系统服务描述符表钩子):这是一种内核层的Hook技术,通过修改SSDT表中的函数地址来拦截系统服务调用。这种方法可以用于监控和过滤系统级别的活动,如文件操作、网络通信等。
- ……
常见Hook技术
IAT Hook
基本概念
可移植可执行文件( PE):可移植可执行文件格式是一种文件格式,用于32位和64位版本的Windows操作系统中的可执行文件、目标代码、DLL、FON字体文件等。PE格式是一种数据结构,它封装了Windows操作系统加载器管理封装可执行代码所需的信息。
导入地址表(**Import Address Table,IAT**):地址表在应用程序调用不同模块中的函数时用作查找表,它可以采用按序号导入和按名称导入两种形式。由于编译后的程序无法知道其所依赖的库的内存位置,因此每次调用API时都需要间接跳转。动态链接器在加载模块并将它们连接在一起时,会将实际地址写入IAT插槽,使它们指向相应库函数的内存位置。每个进程都有IAT表,当PE加载到内存中,系统会将被导入函数的地址写到对应的函数指针位置,通过IAT表,可以直接调用导入函数。
导入名称表(Import Name Table,INT):用于储存被导入函数的名称。加载PE时,系统会根据INT表的函数名,查找填充IAT的函数地址。
实现方式
IAT HOOK可以解释为操纵导入地址表,将API函数重定向到所需的内存地址,该地址可以是另一个API函数、恶意shellcode或程序代码的另一部分。要覆盖IAT中的地址,第一步是找到进程内存中IAT表的地址。查找PE文件中的任何表都需要大量的结构解析,但是查找IAT地址比大多数情况都要容易,因为它可以在PE文件可选头文件中的数据目录中找到。
但是,仅仅找到导入地址表还不足以HOOK API函数。该表只包含API地址,为了替换API函数地址,还需要知道哪个条目属于将要HOOK的API函数。深入研究PE格式后可以发现,导入地址表中的地址顺序与导入名称表相同。因此,可以通过解析导入名称表来找到所需的API函数的条目编号。
在导入名称表中查找函数名需要解析PE文件的导入表中的_IMAGE_IMPORT_DESCRIPTOR结构,在解析了必要的结构并在IAT中找到API函数索引之后,在覆盖函数地址之前还需要执行另一个步骤。通常导入地址表位于内存中,只有读权限,为了覆盖表内的条目,需要将内存保护属性修改为PAGE_READWRITE。借助VirtualProtect函数,可以更改IAT的内存保护属性(或者只是需要覆盖的条目)。
1 | typedef struct _IMAGE_IMPORT_DESCRIPTOR { |
示例
1 |
|
Inline Hook
基本概念
Inline Hook又被称为内联Hook,主要思想是直接修改目标API函数的代码
实现方式
- 使用E9(JMP)进行InlineHook
E9是JMP指令的操作码,用于进行近跳转。它后面跟随一个相对偏移,表示从当前指令后的下一个指令开始计算的跳转目标。
当使用E9进行InlineHook时,直接在目标函数的入口插入一个跳转指令,使其跳转到Hook函数。例如,E9 [offset],其中[offset]是从JMP指令后的下一个指令到Hook函数的相对偏移(相对偏移 = 目的地址 - 源地址 - 5)。其中,5是jmp指令的字节数。
- 使用B8(MOV)和FF E0(JMP EAX)进行InlineHook
B8是MOV指令的操作码,用于将一个立即数值移动到EAX寄存器。FF E0是JMP EAX指令的操作码,表示跳转到EAX寄存器中的地址。
当使用这种方法进行InlineHook时,首先将Hook函数的地址移动到EAX寄存器,然后使用JMP EAX跳转到该地址。例如,B8 [hook function address] FF E0,其中[hook function address]是Hook函数的绝对地址(即目标地址)。
示例
1 |
|