首页
论坛
课程
招聘
[原创]Windows内核模糊测试之IoControl Fuzz
2022-3-1 17:12 6591

[原创]Windows内核模糊测试之IoControl Fuzz

2022-3-1 17:12
6591

一.测试原理

在Windows中,用户层和内核层通过DeviceIoControl来实现通信得,该函数定义如下:

BOOL WINAPI DeviceIoControl(
  __in         HANDLE hDevice,
  __in         DWORD dwIoControlCode,
  __in_opt     LPVOID lpInBuffer,
  __in         DWORD nInBufferSize,
  __out_opt    LPVOID lpOutBuffer,
  __in         DWORD nOutBufferSize,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped);

DeviceIoControl函数最后会通过内核函数NtDeviceIoControlFile来实现通信,该函数定义如下:

NTSTATUS 
  NtDeviceIoControlFile(
    IN HANDLE  FileHandle,
    IN HANDLE  Event,
    IN PIO_APC_ROUTINE  ApcRoutine,
    IN PVOID  ApcContext,
    OUT PIO_STATUS_BLOCK  IoStatusBlock,
    IN ULONG  IoControlCode,
    IN PVOID  InputBuffer,
    IN ULONG  InputBufferLength,
    OUT PVOID  OutputBuffer,
    IN ULONG  OutputBufferLength
    );

由于这两个函数包含了用户层和内核层通信的所有需要的数据,因此可以通过对这两个函数进行操作来实现驱动程序的模糊测试。根据实现模糊测试器实现的方法不同,可以分为以下两类:

  • IoControl MITM(Man-in-the-Middle) Fuzz

  • IoControl Driver Fuzz

二.IoControl MITM(Man-in-the-Middle) Fuzz

下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。

这样,当用户层与内核层发生通信的时候,我们可以监控到这个操作并且可以对其中的输入输出数据进行修改,在将修改以后的数据传递给原始的NtDeviceIoControlFile函数。

如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或蓝屏,往往预示着该驱动程序可能存在内核漏洞。

下图则是该方法的数据变异策略,主要是对输入地址,输入数据,输入长度,输出地址和输出长度进行变异。

三.IoControl Driver Fuzz

上述方法无法主动对驱动进行测试,只能被动地等待相应地通信发生,然后在对数据进行变异。想要主动地对相应派遣函数进行调用,就需要首先通过逆向地手段获得驱动的设备名称以及派遣函数对应的IoControlCode,接着对数据进行变异以后通过主动调用DeviceIoControl函数来完成测试。

根据IoControlCode中Method值的不同,数据变异策略有以下两种情况:

  • Method != METHOD_NEITHER:由于输入输出都有系统保护,因此修改地址没有意义,需要变异的数据只有:输入数据,输入长度,输出长度

  • Method == NMETHOD_NEITHER:驱动中可能直接访问输入输出地址,而没有探测是否可写,因此需要变异的数据有:输入地址,输入数据,输出地址,输出长度

以下是数据的变异策略:

四.IoctlFuzzer部分源码介绍

这个模糊测试器的源码在:https://github.com/Cr4sh/ioctlfuzzer/,该模糊测试器就是通过HOOK NtDeviceIoControlFile函数来构建的IoControl MITM Fuzzer,HOOK代码如下:

old_NtDeviceIoControlFile = (NT_DEVICE_IO_CONTROL_FILE)InterlockedExchange(
            (PLONG)&SYSTEM_SERVICE(m_SDT_NtDeviceIoControlFile), 
            (LONG)new_NtDeviceIoControlFile
        );

在new_NtDeviceIoControlFile中会首先判断先前模式是否是用户模式,如果是内核模式就直接调用原函数继续运行

NTSTATUS NTAPI new_NtDeviceIoControlFile(
    HANDLE FileHandle,
    HANDLE Event,
    PIO_APC_ROUTINE ApcRoutine,
    PVOID ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    ULONG IoControlCode,
    PVOID InputBuffer,
    ULONG InputBufferLength,
    PVOID OutputBuffer,
    ULONG OutputBufferLength)
{    
    KPROCESSOR_MODE PrevMode = ExGetPreviousMode();
    BOOLEAN bLogOutputBuffer = FALSE; 

    // handle only user mode calls
    if (PrevMode != KernelMode)
    {
        // 省略部分代码
    }

    // 内核模式,则调用原函数
    NTSTATUS status = old_NtDeviceIoControlFile(
        FileHandle, 
        Event, 
        ApcRoutine, 
        ApcContext, 
        IoStatusBlock, 
        IoControlCode, 
        InputBuffer, 
        InputBufferLength, 
        OutputBuffer, 
        OutputBufferLength
    );    

    return status;
}

如果先前模式是用户模式,调用ObReferenceObjectByHandle获取句柄的对象

        POBJECT_NAME_INFORMATION DeviceObjectName = NULL, DriverObjectName = NULL;    
        PFILE_OBJECT pFileObject = NULL;

        // get device object by handle
        NTSTATUS ns = ObReferenceObjectByHandle(
            FileHandle, 
            0, 0, 
            KernelMode, 
            (PVOID *)&pFileObject, 
            NULL
        );

接着验证一波设备对象和驱动对象的有效性

            PVOID pDeviceObject = NULL;

            // validate pointer to device object
            if (MmIsAddressValid(pFileObject->DeviceObject))
            {
                pDeviceObject = pFileObject->DeviceObject;
            }
            else
            {
                goto end;
            }

            if (pDeviceObject == m_DeviceObject)
            {
                // don't handle requests to our driver
                goto end;
            }

            // validate pointer to driver object
            if (!MmIsAddressValid(pFileObject->DeviceObject->DriverObject))
            {
                goto end;
            }

            // get loader information entry for the driver module
            PLDR_DATA_TABLE_ENTRY pModuleEntry = (PLDR_DATA_TABLE_ENTRY)
                pFileObject->DeviceObject->DriverObject->DriverSection;

            if (pModuleEntry == NULL)
            {
                goto end;
            }

            // validate pointer to loader's table and data from it
            if (!MmIsAddressValid(pModuleEntry) ||
                !ValidateUnicodeString(&pModuleEntry->FullDllName))
            {
                goto end;
            }

在输入满足条件的时候,会继续调用Fuzz_NtDeviceIoControlFile函数来进行测试

                    if (InputBuffer != NULL && InputBufferLength > 0 &&
                        (m_FuzzOptions & FUZZ_OPT_FUZZ) && bProcessEvent)
                    {   
                        // fuzz this request
                        Fuzz_NtDeviceIoControlFile(
                            PrevMode,
                            &DeviceObjectName->Name,
                            FileHandle,    
                            IoStatusBlock,
                            IoControlCode,
                            InputBuffer,
                            InputBufferLength,
                            OutputBuffer,
                            OutputBufferLength
                        );
                    }

对于Fuzz_NtDeviceIoControlFile函数,该函数首先判断全局变量m_FuzzIotions是否有FUZZ_OPT_FUZZ_FAIR标志,如果没有就会调用FuzzContinue_NtDeviceIoControlFile函数完成测试

void Fuzz_NtDeviceIoControlFile(
    KPROCESSOR_MODE PrevMode,
    PUNICODE_STRING usDeviceName,
    HANDLE FileHandle,
    PIO_STATUS_BLOCK IoStatusBlock,
    ULONG IoControlCode,
    PVOID InputBuffer,
    ULONG InputBufferLength,
    PVOID OutputBuffer,
    ULONG OutputBufferLength)
{    
    MAPPED_MDL InBuffMapped, OutBuffMapped;
    KAPC_STATE ApcState;
    BOOLEAN bInBuffMapped = FALSE, bOutBuffMapped = FALSE, bNeedToDetach = FALSE;
    PVOID TmpInputBuffer = NULL, TmpOutputBuffer = NULL;

    // save original parameters from the nt!NtDeviceIoControlFile()
    FUZZ_THREAD_PARAMS ThreadParams;
    ThreadParams.PrevMode = PrevMode;
    ThreadParams.hFuzzHandle = FileHandle;
    ThreadParams.cIoStatusBlock = IoStatusBlock;
    ThreadParams.IoControlCode = IoControlCode;
    ThreadParams.cInputBuffer = InputBuffer;
    ThreadParams.cOutputBuffer = OutputBuffer;
    ThreadParams.cInputBufferLength = InputBufferLength;
    ThreadParams.cOutputBufferLength = OutputBufferLength;    

    if (m_FuzzOptions & FUZZ_OPT_FUZZ_FAIR)
    {
        // 省略部分代码
    }
    else
    {
        /**
         * Sending IOCTL's from context of the original process.
         */
        FuzzContinue_NtDeviceIoControlFile(
            PrevMode,
            ThreadParams.hFuzzHandle,
            NULL, NULL, NULL,
            ThreadParams.cIoStatusBlock,
            IoControlCode,
            ThreadParams.cInputBuffer,
            ThreadParams.cInputBufferLength,
            ThreadParams.cOutputBuffer,
            ThreadParams.cOutputBufferLength
        );
    }    

    // 省略部分代码
}

如果带有FUZZ_OPT_FUZZ_FAIR标记,那么程序附加到模糊测试器的进程中,并通过APC机制来完成模糊测试

        if (m_FuzzProcess && m_FuzzThreadId && m_UserModeData)
        {            
            if (InputBuffer != NULL && InputBufferLength > 0)
            {
                // 保存输入数据
                if (TmpInputBuffer = M_ALLOC(InputBufferLength))
                {
                    memcpy(TmpInputBuffer, InputBuffer, InputBufferLength);
                }
            }

            if (OutputBuffer != NULL && OutputBufferLength > 0)
            {
                // 保存输出数据
                if (TmpOutputBuffer = M_ALLOC(OutputBufferLength))
                {
                    memcpy(TmpOutputBuffer, OutputBuffer, OutputBufferLength);
                }
            }

            // 附加到模糊测试器进程
            KeStackAttachProcess(m_FuzzProcess, &ApcState);

            if (InputBuffer != NULL && InputBufferLength > 0)
            {
                // 在传递的参数中保存输入数据
                if (bInBuffMapped = AllocateUserMemory(InputBufferLength, &InBuffMapped))
                {
                    ThreadParams.cInputBuffer = InBuffMapped.MappedBuffer;
                    memcpy(ThreadParams.cInputBuffer, TmpInputBuffer, InputBufferLength);
                }
            }

            if (OutputBuffer != NULL && OutputBufferLength > 0)
            {
    		// 在传递的参数中保存输出数据
                if (bOutBuffMapped = AllocateUserMemory(OutputBufferLength, &OutBuffMapped))
                {
                    ThreadParams.cOutputBuffer = OutBuffMapped.MappedBuffer;
                    memcpy(ThreadParams.cOutputBuffer, TmpOutputBuffer, OutputBufferLength);
                }
            }
            
            PETHREAD Thread = NULL;
            KAPC Apc;

            // 获取模糊测试器线程对象
            ns = PsLookupThreadByThreadId(m_FuzzThreadId, &Thread);

            if (NT_SUCCESS(ns))
            {
                // 初始化事件对象
                KeInitializeEvent(
                    &ThreadParams.OperationComplete,
                    NotificationEvent,
                    FALSE
                );

                // 初始化APC对象  
                KeInitializeApc(
                    &Apc, 
                    (PKTHREAD)Thread, 
                    OriginalApcEnvironment, 
                    ApcKernelRoutine, 
                    NULL, 
                    ApcNormalRoutine, 
                    KernelMode, 
                    &ThreadParams
                );

                // 插入APC队列
                if (KeInsertQueueApc(&Apc, NULL, NULL, 0))
                {
                    // waiting for APC execution
                    KeWaitForSingleObject(
                        &ThreadParams.OperationComplete,
                        Executive,
                        KernelMode,
                        FALSE, NULL
                    );
                }
            }  
        }

而要执行的APC函数也是通过FuzzContinue_NtDeviceIoControlFile函数完成测试

VOID ApcNormalRoutine(
    PVOID NormalContext,
    PVOID SystemArgument1,
    PVOID SystemArgument2)
{
    PFUZZ_THREAD_PARAMS ThreadParams = (PFUZZ_THREAD_PARAMS)NormalContext;

    // continue fuzzing
    FuzzContinue_NtDeviceIoControlFile(
        ThreadParams->PrevMode,
        ThreadParams->hFuzzHandle,
        NULL, NULL, NULL,
        ThreadParams->cIoStatusBlock,
        ThreadParams->IoControlCode,
        ThreadParams->cInputBuffer,
        ThreadParams->cInputBufferLength,
        ThreadParams->cOutputBuffer,
        ThreadParams->cOutputBufferLength
    );
    
    KeSetEvent(&ThreadParams->OperationComplete, 0, FALSE); 
}

VOID ApcKernelRoutine(
    struct _KAPC *Apc,
    PKNORMAL_ROUTINE *NormalRoutine,
    PVOID *NormalContext,
    PVOID *SystemArgument1,
    PVOID *SystemArgument2) 
{
    /**
     * This code exeuting in context of the fuzzer's process at APC_LEVEL.
     * Nothing to do here...
     */
}

真正完成测试的是在FuzzContinue_NtDeviceIoControlFile函数中完成的,变异策略步骤如下:

  1. 对输入数据按1字节或4字节为单位进行变异,其中按一字节进行变异的时候,也会变异输入数据的长度

  2. 对输出数据的地址进行变异

  3. 如果通信方式不是METHOD_BUFFERED,则会对输入输出的地址与长度进行变异

具体代码如下:

void FuzzContinue_NtDeviceIoControlFile(
    KPROCESSOR_MODE PrevMode,
    HANDLE FileHandle,
    HANDLE Event,
    PIO_APC_ROUTINE ApcRoutine,
    PVOID ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    ULONG IoControlCode,
    PVOID InputBuffer,
    ULONG InputBufferLength,
    PVOID OutputBuffer,
    ULONG OutputBufferLength)
{                       
    // 分配输入数据长度的内存空间
    PUCHAR NewBuff = (PUCHAR)M_ALLOC(InputBufferLength);
    
    // 分配成功,则先对输入数据进行测试
    if (NewBuff)
    {
	// 保存输入数据
        RtlCopyMemory(NewBuff, InputBuffer, InputBufferLength);
	
	// 对输入数据每个字节进行随机化
        if (m_FuzzingType == FuzzingType_Random)
        {
            
            for (int i = 0; i < RANDOM_FUZZING_ITERATIONS; i++)
            {
                ULONG TmpInputLength = InputBufferLength;
		
		// 对输入数据长度进行变异
                if (m_FuzzOptions & FUZZ_OPT_FUZZ_SIZE)
                {
                    TmpInputLength = getrand(1, TmpInputLength * 4);
                }
              
                // 对输入数据的每个字节进行变异
                for (ULONG s = 0; s < InputBufferLength; s++)
                {
                    *((PUCHAR)InputBuffer + s) = (UCHAR)getrand(1, 0xff);
                }
              
                // 调用原函数
                NTSTATUS status = old_NtDeviceIoControlFile(
                    FileHandle, 
                    Event, ApcRoutine, 
                    ApcContext, 
                    IoStatusBlock, 
                    IoControlCode, 
                    InputBuffer, 
                    TmpInputLength, 
                    OutputBuffer, 
                    OutputBufferLength
                );
            }
        }
        else if (m_FuzzingType == FuzzingType_Dword)
        {             
            // 以4字节为单位变异输入数据

            // 对其输入数据长度 
            ULONG FuzzingLength = XALIGN_DOWN(InputBufferLength, sizeof(ULONG));

            if (FuzzingLength <= DWORD_FUZZING_MAX_LENGTH && FuzzingLength >= sizeof(ULONG))
            {
                // fuzz each dword value in input buffer
                for (ULONG i = 0; i < FuzzingLength; i += DWORD_FUZZING_DELTA)
                {
                    for (ULONG i_v = 0; i_v < sizeof(m_DwordFuzzingConstants) / sizeof(ULONG); i_v++)
                    {                        
                        // 变异输入数据
                        ULONG OldBuffVal = *(PULONG)((PUCHAR)InputBuffer + i);
                        *(PULONG)((PUCHAR)InputBuffer + i) = m_DwordFuzzingConstants[i_v];  

                        // 调用原函数
                        NTSTATUS status = old_NtDeviceIoControlFile(
                            FileHandle, 
                            Event, ApcRoutine, 
                            ApcContext, 
                            IoStatusBlock, 
                            IoControlCode, 
                            InputBuffer, 
                            InputBufferLength, 
                            OutputBuffer, 
                            OutputBufferLength
                        );

                        // 恢复原来的数据
                        *(PULONG)((PUCHAR)InputBuffer + i) = OldBuffVal;                        
                    }
                }
            }
        }

        
        // 恢复输入数据
        RtlCopyMemory(InputBuffer, NewBuff, InputBufferLength);        
        ExFreePool(NewBuff);
    }

    // 对输出数据进行变异
    if (OutputBufferLength > 0)
    {        
        // 将输出数据地址改成用户空间的一个地址
        PVOID TmpOutputBuffer = USER_BUFFER_ADDRESS;

        // 调用原函数
        NTSTATUS status = old_NtDeviceIoControlFile(
            FileHandle, 
            Event, ApcRoutine, 
            ApcContext, 
            IoStatusBlock, 
            IoControlCode, 
            InputBuffer, 
            InputBufferLength, 
            TmpOutputBuffer, 0
        );        

        // 将输出数据地址改成内核空间的一个地址
        TmpOutputBuffer = KERNEL_BUFFER_ADDRESS;


        // 调用原函数
        status = old_NtDeviceIoControlFile(
            FileHandle, 
            Event, ApcRoutine, 
            ApcContext, 
            IoStatusBlock, 
            IoControlCode, 
            InputBuffer, 
            InputBufferLength, 
            TmpOutputBuffer, 0
        );
    }
    
    // 判断通信方式
    ULONG Method = IoControlCode & 3;
    if (Method != METHOD_BUFFERED)
    {
        // try to fuzz buffer addresses, if method is not buffered
        for (int i = 0; i < BUFFERED_FUZZING_ITERATIONS; i++)
        {
            // 变异输入,输出地址与长度,其中地址变异为用户空间的地址
            PVOID TmpInputBuffer  = USER_BUFFER_ADDRESS;
            PVOID TmpOutputBuffer = USER_BUFFER_ADDRESS;
            ULONG TmpInputBufferLength  = getrand(0, 0x100);
            ULONG TmpOutputBufferLength = getrand(0, 0x100);

           

            // 调用原函数
            NTSTATUS status = old_NtDeviceIoControlFile(
                FileHandle, 
                Event, ApcRoutine, 
                ApcContext, 
                IoStatusBlock, 
                IoControlCode, 
                TmpInputBuffer, 
                TmpInputBufferLength, 
                TmpOutputBuffer, 
                TmpOutputBufferLength
            );
        }
	
	
        for (int i = 0; i < BUFFERED_FUZZING_ITERATIONS; i++)
        {
            // 变异输入,输出地址与长度,其中地址变异为内核空间的地址
            PVOID TmpInputBuffer  = KERNEL_BUFFER_ADDRESS;
            PVOID TmpOutputBuffer = KERNEL_BUFFER_ADDRESS;
            ULONG TmpInputBufferLength  = getrand(0, 0x100);
            ULONG TmpOutputBufferLength = getrand(0, 0x100);

            // 调用原函数
            NTSTATUS status = old_NtDeviceIoControlFile(
                FileHandle, 
                Event, 
                ApcRoutine, 
                ApcContext, 
                IoStatusBlock, 
                IoControlCode, 
                TmpInputBuffer, 
                TmpInputBufferLength, 
                TmpOutputBuffer, 
                TmpOutputBufferLength
            );
        }
    }
}

其中的一些关键的宏定义如下:

#define RANDOM_FUZZING_ITERATIONS   10
#define BUFFERED_FUZZING_ITERATIONS 5
#define DWORD_FUZZING_MAX_LENGTH    0x200
#define DWORD_FUZZING_DELTA         4

#ifdef _X86_

#define KERNEL_BUFFER_ADDRESS (PVOID)(0xFFFF0000)
#define USER_BUFFER_ADDRESS   (PVOID)(0x00001000)

#elif _AMD64_

#define KERNEL_BUFFER_ADDRESS (PVOID)(0xFFFFFFFFFFFF0000)
#define USER_BUFFER_ADDRESS   (PVOID)(0x0000000000001000)

#endif

// constants for dword fuzzing
ULONG m_DwordFuzzingConstants[] =
{
    0x00000000,
    0x00001000,
    0xFFFF0000,
    0xFFFFFFFF
};

五.IoctlBf部分源码

这个模糊测试器源码在:https://github.com/koutto/ioctlbf,该模糊测试器则是IoControl Driver Fuzzer。因此,该模糊测试器会首先获取驱动的合法IoControlCode,然后在对这些合法的IoControlCode进行测试。

保存合法IoControl的结构体定义如下:

typedef struct IOCTLlist_ {
	DWORD IOCTL;                    // 保存的IoControlCode
	DWORD errorCode;
	size_t minBufferLength;         // 输入输出数据最小长度 
	size_t maxBufferLength;         // 输入输出数据最大长度
	struct IOCTLlist_ *previous;    // 指向上一个IOCTLlist_结构,用来连接所有的合法IoControlCode
} IOCTLlist, *pIOCTLlist;

在获取合法IoControlCode的代码中,beginIoControl和endIoCtl是由用户指定的,用来指定要测试的IoControlCode范围,然后从该范围中一一测试其对应的IoControlCode一一 测试。测试步骤如下:

  1. 指定输入输出地址为NULL,调用DeviceIoControl。如果函数返回值为0,且GetLastError()的结果为ERROR_ACCESS_DENIED或ERROR_NOT_SUPPORT中的一个,则该IoControlCode不合法,结束本次循环

  2. 判断是否指定了filteralwaysok标志,如果指定了,则测试输入输出的数据是否大于4,如果是则结束本次循环

  3. 将输入输出长度从0到MAX_BUFSIZE一一测试,来查找输入输出数据最小长度,成功查找,则将其作为一个合法的IoControlCode,加入listIoctls中

  4. 从输入输出数据的最小长度+1开始测试,获取输入输出数据的最大长度

具体代码实现如下:

	for(currentIoctl = beginIoctl; currentIoctl<=endIoctl; currentIoctl++) {
		
		// 发送IoControl
		status = DeviceIoControl(deviceHandle, 
								 currentIoctl, 
								 NULL,
								 0,
								 NULL, 
								 0, 
								 &nbBytes, 
								 NULL);
	
		
		// 如果函数调用失败
		if(status == 0) {
			errorCode = GetLastError();
			
			// 如果GetLastError的值为ERROR_ACCESS_DENIED或ERROR_NOT_SUPPORTED
			// 则没有此IoControlCode,结束本次循环
			if(errorCode == ERROR_ACCESS_DENIED    || 
			   errorCode == ERROR_NOT_SUPPORTED)
			{
				continue;
			}	
		}
		
		// 是否指定了filteralwaysok标志
		if(filteralwaysok) {
			// 指定输入输出缓冲区为最大长度,发送IoControlCode
			status = DeviceIoControl(deviceHandle, 
									currentIoctl, 
									&bufInput, 
									MAX_BUFSIZE, 
									&bufOutput, 
									MAX_BUFSIZE, 
									&nbBytes, 
									NULL);
			if(status != 0) {
				cont   = TRUE;
				status = 1; 
				for(j = 0; j < 4 && status != 0 && cont; j++) 
				{
					status = DeviceIoControl(deviceHandle, 
										 	currentIoctl, 
										 	&bufInput, 
										 	j, 
										 	&bufOutput, 
										 	j,
										 	&nbBytes, 
										 	NULL);	
					
				}

				if(j == 4) {
					// 输入输出长度均>4 则结束本次循环
					continue;
				}

			}
		}
									
		// 查找最小输入输出长度
		cont = TRUE;
		for(j = 0; j < MAX_BUFSIZE && cont; j++) {
			status = DeviceIoControl(deviceHandle, 
									 currentIoctl, 
									 &bufInput, 
									 j, 
									 &bufOutput, 
									 j,
									 &nbBytes, 
									 NULL);

			if(status != 0) {
				// 如果发送成功,则将其加入到listIoctls链表
				listIoctls = addIoctlList(listIoctls, 
										  currentIoctl, 
										  0, 
										  j, 
										  MAX_BUFSIZE);
				cont = FALSE;
				i++;
			}
			
		}

		// 找到了最小数据长度,接下来查找最长数据产犊
		if(!cont) {
			cont = TRUE;

			// 发送限制的最长的数据长度
			status = DeviceIoControl(deviceHandle, 
									 currentIoctl, 
									 &bufInput, 
									 MAX_BUFSIZE, 
									 &bufOutput, 
									 MAX_BUFSIZE, 
									 &nbBytes, 
									 NULL);
			if(status != 0) 
			{
				// 如果发送成功,则指定最长数据长度
				listIoctls->maxBufferLength = MAX_BUFSIZE;
				cont = FALSE;
			}
			
			// 查找最长数据长度
			for(j = listIoctls->minBufferLength + 1; j < MAX_BUFSIZE && cont; j++) 
			{
				// 发送IoControlCode
				status = DeviceIoControl(deviceHandle, 
									     currentIoctl, 
										 &bufInput, 
										 j, 
										 &bufOutput, 
										 j, 
										 &nbBytes, 
										 NULL);
				// 发送成功,则更新最长数据长度
				if(status == 0) {
					listIoctls->maxBufferLength = j - 1;
					cont = FALSE;
				}
			}

			if(cont) 
			{
				listIoctls->maxBufferLength = MAX_BUFSIZE;
			}
		}
	}

其中的最大长度MAX_BUFSIZE定义如下:

#define MAX_BUFSIZE 4096		// Max length for input buffer

有了合法的IoControlCode,就可以进行测试,测试步骤如下:

  1. 如果IoControlCode的method不为METHOD_BUFFERED,则将输入输出数据地址指定为整型数组invalidAddress中保存的不合法的地址完成测试

  2. 将输入数据初始化为0x41,并指定不同的数据长度测试是否存在缓冲区溢出漏洞

  3. 以4字节为单位,变异输入数据挖出测试

  4. 随机化输入数据,完成测试

具体的代码实现如下:

	while(1) {
	
		// 从链表中获取IoControl,choice由用户输入
		posListIoctls = getIoctlListElement(listIoctls, choice);
		
		
		// 如果method != METHOD_BUFFERED,则将输入输出数据地址赋值为非法的地址进行测试
		if((posListIoctls->IOCTL & 0x00000003) != 0) 
		{
			cont = TRUE;
			for(i = 0; cont && i < INVALID_BUF_ADDR_ATTEMPTS; i++) 
			{
				for(j = 0; cont && j < (sizeof(invalidAddresses) / 4); j++) 
				{
					// 随机化输入输出长度
					randomLength = getrand(posListIoctls->minBufferLength, 
										   posListIoctls->maxBufferLength);
					
					// 将输入输出地址指定为不合法的地址
					status = DeviceIoControl(deviceHandle, 
											 posListIoctls->IOCTL, 
											 (LPVOID)invalidAddresses[j], 
											 randomLength,
											 (LPVOID)invalidAddresses[j], 
											 randomLength, 
											 &nbBytes, 
											 NULL);
				}
			}
		}
		
		
		// 测试是否存在缓冲区溢出漏洞
		cont = TRUE;
		// 初始化输入数据
		memset(bufInput, 0x41, 0x10000);
		// 指定不同输入数据长度,调用函数完成测试
		for(i = 0x100; i <= 0x10000; i += 0x100) 
		{
			status = DeviceIoControl(deviceHandle, 
									 posListIoctls->IOCTL, 
									 &bufInput, 
									 i, 
			                         &bufOutput, 
									 i, 
									 &nbBytes, 
									 NULL);
		}
		
		
		// 以4字节为单位,变异输入数据
		cont = TRUE;
		if(SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE)) 
		{
			// 初始化输入数据
			memset(bufInput, 0x00, MAX_BUFSIZE);

			// 以4字节为单位,将输入数据修改为FuzzConstants整型数组中的某一元素
			for(i = 0; cont && i < posListIoctls->maxBufferLength; i = i + 4)
			{
				// 随机化输入数据
				for(j = 0; cont && j < posListIoctls->maxBufferLength; j++) 
				{
					bufInput[j] = (BYTE)getrand(0x00, 0xff);
				}
				
				for(j = 0; cont && j < (sizeof(FuzzConstants) / 4); j++) 
				{
					// 指定将输入数据变异为FuzzConstants中的某一元素
					fuzzData = FuzzConstants[j];
					
					bufInput[i]   = fuzzData & 0x000000ff;
					bufInput[i + 1] = (fuzzData & 0x0000ff00) >> 8;
					bufInput[i + 2] = (fuzzData & 0x00ff0000) >> 16;
					bufInput[i + 3] = (fuzzData & 0xff000000) >> 24;
					
					// 调用函数,开始测试
					status = DeviceIoControl(deviceHandle, 
											 posListIoctls->IOCTL, 
											 &bufInput, 
											 posListIoctls->maxBufferLength,
											 &bufOutput, 
											 posListIoctls->maxBufferLength, 
											 &nbBytes, 
											 NULL);
			}
			
			// 将输入数据随机赋值为FuzzConstants整型数组中的元素
			while(cont) 
			{
				// 随机选取输入数据长度
				randomLength = getrand(posListIoctls->minBufferLength, 
				                       posListIoctls->maxBufferLength);
				
				// 初始化输入数据
				memset(bufInput, 0x00, MAX_BUFSIZE);
				
				// 为输入数据赋值
				for(i = 0; i < randomLength; i = i + 4)
				{
					// 从FuzzConstants随机选择元素赋值输入数据
					fuzzData = FuzzConstants[getrand(0, (sizeof(FuzzConstants) / 4) - 1)];
														
					bufInput[i]   = fuzzData & 0x000000ff;
					bufInput[i + 1] = (fuzzData & 0x0000ff00) >> 8;
					bufInput[i + 2] = (fuzzData & 0x00ff0000) >> 16;
					bufInput[i + 3] = (fuzzData & 0xff000000) >> 24;
				}		

				// 调用函数完成测试
				status = DeviceIoControl(deviceHandle, 
										 posListIoctls->IOCTL, 
										 &bufInput, 
										 randomLength,
										 &bufOutput, 
										 randomLength, 
										 &nbBytes, 
										 NULL);
			}
		}
		
		
		// 将输入数据随机初始完成测试
		cont = TRUE;
		if(SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE)) 
		{
			while(cont) 
			{
				// 随机选取输入输出数据长度
				randomLength = getrand(posListIoctls->minBufferLength, 
									   posListIoctls->maxBufferLength);
				
				// 随机化输入数据
				memset(bufInput, 0x00, MAX_BUFSIZE);
				for(i = 0; i < randomLength; i++) 
				{
					bufInput[i] = (BYTE)getrand(0x00, 0xff);
				}
						
				// 调用函数完成测试
				status = DeviceIoControl(deviceHandle, 
										 posListIoctls->IOCTL, 
										 &bufInput, 
										 randomLength,
				                         &bufOutput, 
										 randomLength, 
										 &nbBytes, 
										 NULL);

			}
		}
	}

其中的几个全局变量定义如下:

// Junk data used for fuzzing -------------------------------------------------
CHAR asciiString10[0x10];
CHAR asciiString100[0x100];
CHAR asciiString1000[0x1000];

WCHAR unicodeString10[0x10];
WCHAR unicodeString100[0x100];
WCHAR unicodeString1000[0x1000];
	
DWORD tableDwords[0x100];
	
DWORD FuzzConstants[] = {	0x00000000, 0x00000001, 0x00000004, 0xFFFFFFFF,
							0x00001000, 0xFFFF0000, 0xFFFFFFFE, 0xFFFFFFF0, 
							0xFFFFFFFC, 0x70000000, 0x7FFEFFFF, 0x7FFFFFFF, 
							0x80000000, 
							(DWORD)asciiString10, 
							(DWORD)asciiString100, 
							(DWORD)asciiString1000,
							(DWORD)unicodeString10, 
							(DWORD)unicodeString100, 
							(DWORD)unicodeString1000,
							(DWORD)tableDwords }; 
							
DWORD invalidAddresses[] = { 0xFFFF0000, 0x00001000 };

看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)

最后于 2022-3-11 15:23 被1900编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (1)
雪    币: 9793
活跃值: 活跃值 (12081)
能力值: ( LV12,RANK:385 )
在线值:
发帖
回帖
粉丝
SSH山水画 活跃值 3 2022-3-1 17:22
2
0

:older_man:

游客
登录 | 注册 方可回帖
返回