references:
在代码中我们可以看到他是这样获取ioctl的起始和终止范围的:
beginIoctl = (DWORD)parseHex(singleIoctl) & 0xffffc000;
endIoctl = ((DWORD)parseHex(singleIoctl) & 0xffffc000) | 0x00003fff;
那他为什么这样写呢,这就和ioctl的编码方式有关了
Bits | Field | Meaning |
---|---|---|
31–16 | DeviceType | Type of device |
15–14 | Access | Required access (read, write, etc.) |
13–2 | Function | Function code (custom or system) |
1–0 | Method | Transfer method (buffered, direct, etc) |
可以看到从14bit开始就是不会变化的东西了,他们是devicetype和access,这个我们不用管,我们只用爆破低14bit即可,0xffffc000就是低14bit为0的一个值,和他进行与操作就可以把低14bit置0,就得到了起始地址,终止地址就是把低14bit置1,即0x3fff
其实这个ioctl fuzz的原理非常简单,就是确定一个起始和终止范围,然后循环测试每一个iocode,得到每一个iocode对应的input buffer的最小和最大长度,因为deviceiocontrol函数会在正常的时候返回非0值,我们只需要循环测试每一个输入buffer长度并检测返回值即可
注意
并不是所有的驱动都会在失败的时候返回0,存在一些驱动,不管在什么情况下返回的status都不为0
ioctlfuzzer会在filteralwaysok选项开启的时候过滤掉这些iocode,过滤原理就是下面这个循环
for (j = 0; j < 4 && status != 0 && cont; j++) {
status = DeviceIoControl(deviceHandle,
currentIoctl,
&bufInput,
j,
&bufOutput,
j,
&nbBytes,
NULL);
/*
if(status == 0)
printf("0x%08x (size %d) -> error code %03d \n", currentIoctl, j, GetLastError());
else
printf("0x%08x (size %d) -> status != 0 \n", currentIoctl, j);
*/
}
if (j == 4) {
//printf("Skip 0x%08x\n", currentIoctl);
continue;
}
他循环5次,这5次循环中,inputlength依次为0~5,如果5次全部都不为0,说明这个驱动在任何条件下都会返回status!=0
此时就跳过对该IOCODE的input length的测试,我在这个地方添加了一个pause指令,以便引起我的注意,我需要调试看一下,下一步会产生何种反应
另外一点就是,原版的fuzz代码会在fuzz开始前要求我们去指定一个iocode,但是我给他改成直接就从起始范围开始测,如果崩溃了,我们就去调试器分析即可