【技术分享】用户模式下基于异常和内核控制的线程级钩子技术分析

http://p4.qhimg.com/t010d6febb7e8a70b7e.jpg

译者:WisFree

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


简介

在这篇文章中,我们将会跟大家介绍我们在研究过程中所发现的一种新型的钩子(Hook)技术。

Hook技术可以帮助我们了解并控制操作系统中每一部分软件组件的操作行为,使用了钩子技术的部分软件有:应用程序安全解决方案、系统应用工具、编程软件(例如用于拦截、调试和功能扩展的软件)、以及恶意软件(例如rootkit)等等。

钩子(Hook)是Windows消息处理机制的一个平台,应用程序可以在上面设置子进程/线程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,钩子可以在目标窗口处理函数之前对数据进行处理它,因为钩子机制允许应用程序截获处理window消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

需要注意的是,本文所要介绍的内容并不涉及提权以及漏洞利用技术。本文所介绍的技术主要用于后渗透场景,即攻击者已经成功获取到目标设备控制权的情况。由于恶意内核代码(rootkit)一般都会尝试在目标系统中实现持久化感染,因此隐藏技术也逐渐开始扮演一种十分重要的角色了。


技术描述

我们给这项技术取名为BoundHook,BoundHook技术可以在用户模式场景中的特殊位置引发异常,然后通过捕捉异常来接管线程的执行。我们可以使用BOUND指令(Intel MPX内存保护扩展的一部分)来实现上述操作,这种指令原本的作用是通过检测指针引用来提升软件的安全性。简而言之,BOUND指令可以检测数组索引的越界情况(可能触发内存崩溃漏洞),如果测试失败则会引起软件中断。(32-bit: nt!KiTrap05, 64-bit: nt!KiBoundFault

你可能会问,为什么不对这两种操作指令进行一下对比呢?因为英特尔的技术人员在设计这种新型指令时故意生成了一个异常,并让操作系统来检查边界测试失败的情况。

http://p8.qhimg.com/t01fd64c890da1e3dc8.png

这种指令的语句如下所示:

BOUND r16, m16&16 – 检测r16(数组索引)是否越界(m16&16指定)

BOUND r32, m32&32 – 检测r32(数组索引)是否越界(m32&32指定)

当边界检查出现错误时,陷阱处理程序(trap handler)将会调用nt!KiHandleBound并执行已注册的边界异常回调程序。

内核模式驱动程序或者运行在内核模式下的shellcode payload可以使用nt!KeRegisterBoundCallback来为边界检测异常注册一个回调程序。需要注意的是,这个函数并不是WDK header“提供”的,而且这里还需要动态加载一个指向该函数的指针。

这种回调程序没有任何的参数,并且会返回一个BOUND_CALLBACK_STATUS(枚举类型),具体如下所示:

http://p4.qhimg.com/t01b211178e5d61d146.jpg

完成了边界异常的注册之后,内核模式代码会得到一个指向用户模式DLL基地址的指针,并计算出需要设置钩子的函数地址。

获取函数地址其实是一件非常简单的事情,而且可以通过多种方式去实现,例如通过解析PE头就可以。需要注意的是,解析一个某个特定进程所加载的图片则需要在进程环境中进行,或者使用特定的API。

当我们的代码计算出了函数地址之后,我们如果可以直接开始向这个地址写入数据就非常好了。但是,由于这部分代码存在于只读/可执行内存之中,我们就没办法做到这一点了。

Windows内存保护的实现主要依赖一下几个因素:

http://p2.qhimg.com/t01f96240896bc6c94e.png

现在我们就有几种选择了。我们可以想办法向这个地址写入数据来触发COW(copy-on-write)保护,或者使用__readcr0()和__writecr0()来修改CR0寄存器。除此之外,我们还可以分配我们的内存描述符列表(MDL)来描述内存页面,并使用按位或(bitwise OR)来调整MDL和MDL_MAPPED_TO_SYSTEM_VA的权限。最后这种方法相对来说更加“隐蔽”一些,因为根据当前PatchGuard实现的设计来看,这种方法是完全不可见的。

首先,我们给大家介绍如何修改CR0寄存器。CR-寄存器的描述如下所示(来源于Intel 64和IA-32软件架构开发人员手册):

”WP写入保护(16位CR0)-当设置时,将阻止高等级进程向只读页面中写入数据;当清空时,将允许高等级进程向只读页面中写入数据。“

下面是CR0寄存器的一个简单的修改样例:

http://p8.qhimg.com/t01f636db54fe72615a.png

如果可以直接向DLL的COW页面写入数据的话,我们就能够对操作系统中每一个使用了这个DLL的进程设置钩子,因为我们已经可以影响cow-origin页面了。

触发边界异常也是比较简单的,比如说,下面的代码将触发一次错误异常:

http://p7.qhimg.com/t01b53d58d9c27e42f0.png

因此,我们负责执行钩子的内核模式代码将能够向目标位置写入一个类似的汇编代码,并成功接管目标线程的执行过程。

比如说,如果我们想要挂钩KERNELBASE!CreateFileW,我们就可以将下面给出的这行操作码注入到该函数的起始位置:

UCHAR opcodes[5]= {0x36, 0x66, 0x62, 0x0C, 0x24};

你可以直接理解为:BOUND CX, DWORD PTR SS : [ESP]。在这种特殊场景下,我们假设CX为0(在真实的使用场景中,我们需要对每一个函数进行测试来决定这个值),而栈顶的值肯定大于0(这只是一个PoC,而并非最终的Exploit)。

现在,当我们将操作码写入进了KERNELBASE!CreateFileW之后,如果用户模式下的线程调用这个函数时,我们内核模式下的回调函数就能够完全接管这个用户模式下的线程了。

如果可以实现的话,那我们的优势就非常大了,比如说:

1.  挂钩的页面仍然是COW,因此反恶意软件解决方案以及研究人员所进行的手动分析将无法发现页面遭到了篡改。

2.  绝大多数反病毒产品不会检测到我们的这种技术,而且这个问题似乎无法解决,因为页面仍然是COW。

3.  用户模式调试器将无法捕捉到这种钩子。普通的内联钩子方法会让已挂钩的程序跳转到其他的用户模式代码,但BoundHook技术可以通过内核边界异常处理器来修改这种执行流程。

4.  绝大多数PatchGuard(PG)保护机制都无法察觉到我们的这种钩子技术。根据目前PG的设计原理,用本文所介绍的MDL方法绕过COW机制是不会被检测到的。对于修改CR0寄存器的方法来说,虽然CR0寄存器是受PG保护的,但PG发现这种修改操作的可能性也非常小,因为修改操作可以在非常短的时间内完成。

PoC-已挂钩的线程调用栈:

http://p9.qhimg.com/t0198abe493b265d724.png

总结

我们知道对于微软而言,BoundHook技术所利用的技术因素并不会被他们认为是一种安全漏洞,因为设备管理员权限已经被攻击者拿到了。在此之前,微软曾从CyberArk(GhostHook技术)那里收到了类似问题的报告,而微软对此的回应如下:

”我们已经对上报的安全问题进行了详细的分析和调查,并且发现这并不是一个真正意义上的安全漏洞,因为这只是一种用于躲避安全检测的技术,但设备此时已经被攻击者入侵了。你们所提交的是一种后渗透技术,而且并不符合我们的漏洞规定,因此我们无法针对该问题发布更新补丁,但我们会在将来的Windows版本中考虑解决这个问题。“

但不管怎么样,我认为我们所设计的这种技术可以给软件安全厂商以及恶意软件开发者提供一种新的思路,也希望微软能够尽快解决这个问题(虽然他们不认为这是一个安全漏洞)。

(完)