内核 FUZZ 思路
内核 API 函数:是提供给 Ring3 调用,在 Ring0 完成最终功能的函数。这些函数接收 Ring3 传入的参数,如果处理参数的过程存在问题的话,很有可能成为一个内核漏洞。这样的内核 API 函数有很多,例如 SSDT、Shadow SSDT 等。
Hook API的代码:很多安全软件为了防御病毒木马,对很多内核 API 进行了 Hook/Inline Hook。然而这些 Hook 内核 API 的代码也存在安全问题。如果对参数的处理不当或内部逻辑的错误,也很有可能出现内核漏洞。
网络协议处理的内核代码:有一些协议的处理是在 SYSTEM 进程中的,也就是在内核模块中处理的,可以通过网络协议 Fuzz,构造畸形协议数据包,来挖掘这方面的漏洞。
IoControl:从已公布的漏洞来看,IoControl 类型的漏洞是最多的,可以作为内核漏洞挖掘的重点方向。IoControl 作为 Ring0/Ring3 缓冲区交互的重要方式,很有可能会出现参数处理不当的问题,或者内部逻辑甚至设计缺陷引发的问题。
既然 IoControl 类型的内核漏洞如此多,那么如何挖掘这种类型的漏洞呢?首先再来回顾下 Ring3和Ring0 通讯的重要函数 DeviceIoControl():
1 BOOL DeviceIoControl( 2 HANDLE hDevice, //设备句柄 3 DWORD dwIoControlCode, //Io 控制号 4 LPVOID lpInBuffer, //输入缓冲区指针 5 DWORD nInBufferSize, //输入缓冲区字节数 6 LPVOID lpOutBuffer, //输出缓冲区指针 7 DWORD nOutBufferSize, //输出缓冲区字节数 8 LPDWORD lpBytesReturned, //返回输出字节数 9 LPOVERLAPPED lpOverlapped //异步调用时指向的 OVERLAPPED 指针 10 );
正常情况下,DeviceIoControl() 的参数都是自己程序指定的,不会是畸形的参数,驱动中也主要考虑功能的实现,对于畸形参数和处理逻辑缺陷方面的安全问题不够重视。因此可以通过构造畸形参数来 Fuzz 驱动程序的处理是否存在漏洞。
这种 Fuzz 称为“IoControl Fuzz”。关于 IoControl Fuzz,有2种方法:
IoControl MITM(Man-In-The-Middle)Fuzz
这种方法类似于“中间人攻击”,是在内核 Hook NtDeviceIoControlFile(),当识别到 IoControl 的对象为要 Fuzz 的对象对,获取该函数的参数,并按照预先的 Fuzz 策略对参数或参数指向的缓冲区数据进行篡改,然后将篡改后的参数传递给原始的 NtDeviceIoControlFile():
IoControl Driver Fuzz
MITM 方式虽然巧妙,但多数情况下,难以覆盖某驱动程序所有的 IoControlCode,就算覆盖所有的 IoControlCode,这种 Fuzz 也只是“一次性”的,不够全面。
为了能够对驱动程序中的某个 IoCodeCode 做全面的 Fuzz,需要对 DeviceIoControl() 的每个参数都进行畸形化,每个参数值可以畸形出很多不同的值,然后将各个参数值进行组合。这种方法称为 IoControl Driver Fuzz。
内核 FUZZ 工具介绍
[一] 莫斯科一家公司的安全研究实验室 eSage Lab(http://www.esagelab.com)有一个开源工具 IOCTL Fuzzer,是 CLI 工具,功能如下
* 根据进程名称、驱动名称、设备名称、IoControlCode 进行 IRP 过滤 * IRP Fuzz * 监视模式 * 通过控制台或文件输出日志
IOCTL Fuzzer 是在处理 IRP 的时候提取出满足配置文件中指定条件的 IRP,然后将该 IRP 的输入参数进行随机修改来进行 Fuzz,也即是 MITM Fuzz 方法。该工具使用 XML 文件来配置 Fuzz 对象和 Fuzz 策略。
其短板是,通过 XML 配置固然简单,但不够灵活,每次改变 Fuzz 对象和 Fuzz 策略都要重启 Fuzz 程序,使用不便。且 Fuzz 策略不够细致,可能会遗漏。
[二] 书中(工具及源码见随书光盘)给出一个 GUI 的 IoControl Fuzz 工具,能进行 MITM Fuzz 和 IoControl Driver Fuzz。其关键概念和概要设计如下:
Fuzz 对象:被 Fuzz 的对象或触发 Fuzz 的条件
Fuzz 策略:Fuzz 过程中对参数和数据畸形化的方案
Fuzz 项:Fuzz 对象和 Fuzz 策略合起来,称为一个 Fuzz 项。对于 Fuzz 程序来说,所有的 Fuzz 项就是其输入。Fuzz 程序支持多个 Fuzz 项同时工作,且能灵活地增删改,不发重启 Fuzz 引擎。
漏洞挖掘实战
书中展示了几个已经被修补了的漏洞的挖掘,利用的是上面提到的 IoControl Fuzzer 和 WinDbg 内核调试跟踪。
[一] 超级巡警 ASTDriver.sys 本地提权,影响 v4 Build0316 及以前的版本
超级巡警中有个恢复 Hook 的内核函数功能,在实现时没有对输入缓冲区进行检查,可导致向任意地址写 0。Fuzz 蓝屏后,可以在 WinDbg 中发现以下信息,进一步使用 !analyze -v 和反汇编追踪,可以发现漏洞代码位置,具体过程见书中内容。
[二] 东方微点 mp110013.sys 本地提权,影响东方微点防御软件 1.2.10581.0278 及以前的版本
mp110013.sys 对 0x8000012C 这个 IoControlCode 的处理存在问题,它会调用输入缓冲区的第一个 DWORD,如果用户在第一个 DWORD 填入错误的值,那么派遣例程会将这个值修改为 0。所以只要在 0 这个地址放入 shellcode,并且在输入缓冲区的第一个 DWORD 填入错误值,就能执行 Ring0 Shellcode。
[三] 瑞星 HookCont.sys 驱动本地拒绝服务漏洞,影响 23.0.0.5 及之前版本
HookCont.sys 驱动派遣例程中,对 IoControlCode 为 0x83003C07 的处理中,对 UserBuffer 检查使用 ProbeForWrite() 不当和 try 的位置使用不当,造成本地拒绝服务漏洞。
其对 UserBuffer 检查使用 ProbeForWrite() 的方法不正确,ProbeForWrite() 的第二个参数为 OutputBufferLength,驱动中没有任何检查就直接信任用户输入的 OutputBufferLength,只要 OutputBufferLength 为 0,这个 ProbeForWrite() 检查就毫无意义,也就是说 UserBuffer 可以是一个非法内存地址。
另外,在做 ProbeForRead() 和 ProbeForWrite() 检查时,使用了 try,但是在最后给 UserBuffer 写数据时没有使用 try。所以只要躲过前面的检查,后面由于内存访问错误,就会造成蓝屏崩溃。