在Windows中,用户层和内核层通过DeviceIoControl来实现通信得,该函数定义如下:
DeviceIoControl函数最后会通过内核函数NtDeviceIoControlFile来实现通信,该函数定义如下:
由于这两个函数包含了用户层和内核层通信的所有需要的数据,因此可以通过对这两个函数进行操作来实现驱动程序的模糊测试。根据实现模糊测试器实现的方法不同,可以分为以下两类:
IoControl MITM(Man-in-the-Middle) Fuzz
IoControl Driver Fuzz
下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。
这样,当用户层与内核层发生通信的时候,我们可以监控到这个操作并且可以对其中的输入输出数据进行修改,在将修改以后的数据传递给原始的NtDeviceIoControlFile函数。
如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或蓝屏,往往预示着该驱动程序可能存在内核漏洞。
下图则是该方法的数据变异策略,主要是对输入地址,输入数据,输入长度,输出地址和输出长度进行变异。
上述方法无法主动对驱动进行测试,只能被动地等待相应地通信发生,然后在对数据进行变异。想要主动地对相应派遣函数进行调用,就需要首先通过逆向地手段获得驱动的设备名称以及派遣函数对应的IoControlCode,接着对数据进行变异以后通过主动调用DeviceIoControl函数来完成测试。
根据IoControlCode中Method值的不同,数据变异策略有以下两种情况:
Method != METHOD_NEITHER:由于输入输出都有系统保护,因此修改地址没有意义,需要变异的数据只有:输入数据,输入长度,输出长度
Method == NMETHOD_NEITHER:驱动中可能直接访问输入输出地址,而没有探测是否可写,因此需要变异的数据有:输入地址,输入数据,输出地址,输出长度
以下是数据的变异策略:
这个模糊测试器的源码在:https://github.com/Cr4sh/ioctlfuzzer/,该模糊测试器就是通过HOOK NtDeviceIoControlFile函数来构建的IoControl MITM Fuzzer,HOOK代码如下:
在new_NtDeviceIoControlFile中会首先判断先前模式是否是用户模式,如果是内核模式就直接调用原函数继续运行
如果先前模式是用户模式,调用ObReferenceObjectByHandle获取句柄的对象
接着验证一波设备对象和驱动对象的有效性
在输入满足条件的时候,会继续调用Fuzz_NtDeviceIoControlFile函数来进行测试
对于Fuzz_NtDeviceIoControlFile函数,该函数首先判断全局变量m_FuzzIotions是否有FUZZ_OPT_FUZZ_FAIR标志,如果没有就会调用FuzzContinue_NtDeviceIoControlFile函数完成测试
如果带有FUZZ_OPT_FUZZ_FAIR标记,那么程序附加到模糊测试器的进程中,并通过APC机制来完成模糊测试
而要执行的APC函数也是通过FuzzContinue_NtDeviceIoControlFile函数完成测试
真正完成测试的是在FuzzContinue_NtDeviceIoControlFile函数中完成的,变异策略步骤如下:
对输入数据按1字节或4字节为单位进行变异,其中按一字节进行变异的时候,也会变异输入数据的长度
对输出数据的地址进行变异
如果通信方式不是METHOD_BUFFERED,则会对输入输出的地址与长度进行变异
具体代码如下:
其中的一些关键的宏定义如下:
这个模糊测试器源码在:https://github.com/koutto/ioctlbf,该模糊测试器则是IoControl Driver Fuzzer。因此,该模糊测试器会首先获取驱动的合法IoControlCode,然后在对这些合法的IoControlCode进行测试。
保存合法IoControl的结构体定义如下:
在获取合法IoControlCode的代码中,beginIoControl和endIoCtl是由用户指定的,用来指定要测试的IoControlCode范围,然后从该范围中一一测试其对应的IoControlCode一一 测试。测试步骤如下:
指定输入输出地址为NULL,调用DeviceIoControl。如果函数返回值为0,且GetLastError()的结果为ERROR_ACCESS_DENIED或ERROR_NOT_SUPPORT中的一个,则该IoControlCode不合法,结束本次循环
判断是否指定了filteralwaysok标志,如果指定了,则测试输入输出的数据是否大于4,如果是则结束本次循环
将输入输出长度从0到MAX_BUFSIZE一一测试,来查找输入输出数据最小长度,成功查找,则将其作为一个合法的IoControlCode,加入listIoctls中
从输入输出数据的最小长度+1开始测试,获取输入输出数据的最大长度
具体代码实现如下:
其中的最大长度MAX_BUFSIZE定义如下:
有了合法的IoControlCode,就可以进行测试,测试步骤如下:
如果IoControlCode的method不为METHOD_BUFFERED,则将输入输出数据地址指定为整型数组invalidAddress中保存的不合法的地址完成测试
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-3-11 15:23
被1900编辑
,原因: