-
-
[原创]Windows内核模糊测试之Random-based fuzzing
-
2022-2-28 18:22 9031
-
一.前言
最初的模糊测试器是基于随机数据来实现的,本文通过https://github.com/Rootkitsmm-zz/Win32k-Fuzzer这个模糊测试器的分析,来展现通过随机数据来实现模糊测试器的一种思路。
二.实现思路
链接中的模糊测试器的目标是发现Win32k.sys中的UAF漏洞,该驱动中的漏洞产生的原因往往是因为对申请的对象进行了错误的操作而产生的。而win32对象的申请是通过该驱动的HMAllocObject函数来实现的,该函数申请对象的伪代码如下:
对于DesktopAlloc和SharedAlloc这两个函数,它们是通过RtlAllocateHeap来申请堆内存来保存对象的。
而对于Win32AllocPoolWithTagZInit和Win32AllocPoolWithQuotaTagZInit函数,则是通过ExAllocatePoolWithTag申请池内存来保存对象。
对于使用堆来保存对象的内存,最终是通过RtlFreeHeap来实现堆内存的释放的,该函数定义如下:
BOOLEAN RtlFreeHeap( IN PVOID HeapHandle, IN ULONG Flags, IN PVOID HeapBase );
其中第三个参数指定了要释放的堆内存的地址,因此可以通过HOOK该函数,在该函数调用前,将要释放的堆内存中的数据改为像0x0C0C0C0C这种非法数据。当这块内存之后再次被申请使用的时候,如果程序没有对这块内存进行合理的处理而产生漏洞的话,就可以被用户捕捉到了。
但是这里有一个问题是,通过该函数并不知道堆内存的大小,因此需要通过未导出函数RtlSizeHeap来获取堆内存的大小,该函数的定义如下:
SIZE_T NTAPI RtlSizeHeap( HANDLE HeapPtr, ULONG Flags, PVOID Ptr )
三.具体实现
该模糊测试器的具体实现步骤如下:
通过ZwQuerySystemInformation获取内核模块基址
通过特征码查找RtlSizeHeap函数地址
检查RtlFreeHeap函数起始的字节是否符合要求
对RtlFreeHeap函数进行Inline HOOK
对应代码如下:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) { NTSTATUS nts; PVOID pBaseAddress=NULL; int Sizeofimage=0; DbgPrint("My Driver Loaded!"); DbgBreakPoint(); // 获取内核模块基址 nts = RtlGetModuleBase("ntkrnlpa.exe", &pBaseAddress, &Sizeofimage); if (!NT_SUCCESS(nts)) return nts; // 获取RtlSizeHeap函数地址 RtlSizeHeap = ScanForRtlSizeHeap((ULONG_PTR)pBaseAddress, Sizeofimage); if(!RtlSizeHeap) return STATUS_UNSUCCESSFUL; DbgPrintEx(DPFLTR_IHVVIDEO_ID, DPFLTR_ERROR_LEVEL, "pBaseAddress %x , Sizeofimage %x, RtlSizeHeap %x \r\n", pBaseAddress, Sizeofimage, RtlSizeHeap); // 检查RtlFreeHeap函数的特征码是否符合要求 if(STATUS_SUCCESS != CheckFunctionBytesRtlFreeHeap()) { DbgPrint("Match Failure on RtlFreeHeap!"); return STATUS_UNSUCCESSFUL; } // 对RtlFreeHeap进行HOOK操作 DetourFunctionRtlFreeHeap(); return STATUS_SUCCESS; }
对于RtlSizeHeap函数,是通过该函数的特征码从内核模块中查找的,代码如下:
static UCHAR RtlSizeHeapSign[]= { 0x8b, 0xff, 0x55, 0x8b, 0xec, 0x8b, 0x4d, 0x10, 0x53, 0x56, 0x8b, 0x75, 0x08, 0x33, 0xdb, 0xf6, 0x46, 0x48, 0x01, 0x75, 0x34, 0xf6, 0xc1, 0x07, 0x75, 0x20, 0x8d, 0x41, 0xf8, 0x80, 0x78, 0x07, 0x05, 0x75, 0x09, 0x0f, 0xb6, 0x48, 0x06, 0xc1, 0xe1, 0x03, 0x2b, 0xc1, 0xf6, 0x40, 0x07, 0x3f, 0x75, 0x1e, 0x53, 0x53, 0x6a, 0x08, 0x8b, 0xc8, }; PVOID ScanForRtlSizeHeap(ULONG_PTR StartAddress, int ScanLength ) { PUCHAR bytes; PCHAR endAddress; PCHAR address; endAddress =(PCHAR)(StartAddress + ScanLength); for (address =(PCHAR) StartAddress; address < endAddress; address++) { if (RtlCompareMemory((PVOID)address, RtlSizeHeapSign, sizeof(RtlSizeHeapSign)) == sizeof(RtlSizeHeapSign)) { DbgBreakPoint(); return (PVOID)(address); } } return NULL; }
在HOOK以后跳转的目标函数中,该函数就是通过调用RtlSizeHeap获取堆内存的大小,然后将这块堆内存赋值为1,在跳转回RtlFreeHeap函数去执行,具体代码如下所示:
__declspec(naked) my_function_detour_RtlFreeHeap() { __asm { // 执行RtlFreeHeap函数起始处的指令 mov edi, edi push ebp mov ebp, esp push ebx mov ebx, dword ptr [ebp+10h] // 获取RtlFreeHeap函数的第三个参数,也就是要释放的堆内存的地址 mov ebx,dword ptr [ebp+10h] // 保存寄存器状态 PUSHAD // 将RtlSizeHeap函数需要的参数入栈,然后调用该函数 // 函数的返回值就是堆内存的大小 PUSH dword ptr [ebp + 10h] PUSH dword ptr [ebp + 0Ch] PUSH dword ptr [ebp + 08h] call RtlSizeHeap; // 获取到堆块地址后将堆内存修改为1 sub ecx, ecx; mov ecx, eax; mov eax, 0x1 mov edi, ebx; // address of heap chunk rep stos byte ptr es:[edi] // 恢复寄存器状态 POPAD // 指令jmp FAR 0x08:0xAAAAAAAA // 在HOOK函数中会对0xAAAAAAAA进行修改,让它可以跳转到原函数执行 _emit 0xEA _emit 0xAA _emit 0xAA _emit 0xAA _emit 0xAA _emit 0x08 _emit 0x00 } }
如此一来,释放的堆内存中的数据就会被修改为1,如果程序后续对这块内存的操作出现错误导致漏洞就会更容易被捕获到。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课