Chapter 45 — Final Chapter

Rootkit Techniques

DKOM, SSDT hooks, minifilter suppression, kernel inline hooks, bootkit/UEFI persistence, and hypervisor-based rootkits — and why VBS changes every assumption

Book 3 Complete
Scenario

A nation-state threat actor deploys a kernel rootkit on a high-value target. It hides its process from every Windows enumeration API, suppresses EDR callbacks, intercepts SSDT entries for security-relevant syscalls, and persists via a UEFI implant that survives OS reinstall. The rootkit has been active for 14 months before discovery via memory forensics, because everything it did was invisible to the OS's own APIs. This chapter ties together every concept in the book: the rootkit exploits your incomplete understanding of every layer below.

DKOM — Direct Kernel Object Manipulation

DKOM modifies kernel data structures directly in memory to hide artifacts from the OS's own APIs. The OS API reports only what it can find by traversing its own data structures — if those structures are modified, the API lies.

Process hiding via EPROCESS DKOM:

  PsActiveProcessLinks is a doubly-linked list of all EPROCESS structs.
  NtQuerySystemInformation(SystemProcessInformation) walks this list.
  Tools like Task Manager call NtQuerySystemInformation.

  Normal list:     System(4) <--> smss(280) <--> csrss(444) <--> malware(1234) <--> svchost(...)

  After DKOM unlink:
    System(4) <--> smss(280) <--> csrss(444) <--> svchost(...)
    malware(1234).Flink points nowhere useful; malware process still runs.

  Windows API tools see no malware process.
  Malware process still scheduled and executing.

  EPROCESS.ActiveProcessLinks offsets (x64 Win10/11):
    EPROCESS+0x448 = ActiveProcessLinks (LIST_ENTRY Flink/Blink)
    EPROCESS+0x450 = ActiveProcessLinks.Blink

  Unlink code:
    PLIST_ENTRY prev = malwareEproc->ActiveProcessLinks.Blink;
    PLIST_ENTRY next = malwareEproc->ActiveProcessLinks.Flink;
    prev->Flink = next;
    next->Blink = prev;
    malwareEproc->ActiveProcessLinks.Flink = &malwareEproc->ActiveProcessLinks;
    malwareEproc->ActiveProcessLinks.Blink = &malwareEproc->ActiveProcessLinks;
Cross-view detection

DKOM hiding only defeats API-based enumeration. Methods that bypass the PsActiveProcessLinks walk can still find the hidden process: (1) Walk all kernel memory looking for EPROCESS signatures (pool tag scan — "Proc" / 0x636F7250). (2) Enumerate scheduler data structures — every running thread has a KTHREAD that points back to its KPROCESS (EPROCESS), and the scheduler's KPRCB.ReadyListHead is not typically DKOM'd. (3) Inspect CPU CR3 values or the kernel's VAD tree — hidden processes still have a page directory. (4) Volatility's psxview plugin runs all enumeration methods and flags discrepancies. (5) Look for handles belonging to a process that doesn't appear in the process list.

SSDT Hooks

The System Service Descriptor Table (SSDT, KiServiceTable) maps syscall numbers to kernel function addresses. Modifying these pointers redirects syscalls through the rootkit's handler before the real kernel function executes. On 64-bit Windows, PatchGuard detects and blue-screens systems that modify the SSDT — but see the section on PatchGuard bypass below.

// SSDT hook concept (x64 Windows, simplified)
// KeServiceDescriptorTable is exported by ntoskrnl
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

// KSERVICE_TABLE_DESCRIPTOR layout:
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PULONG_PTR  Base;        // KiServiceTable pointer
    PULONG      Count;
    ULONG       Limit;       // number of entries
    PUCHAR      Number;      // argument table
} KSERVICE_TABLE_DESCRIPTOR;

// On Win10 x64, table entries are 32-bit offsets:
// actual_address = (ULONG64)KiServiceTable + (entry >> 4)

PVOID GetSSDTFunctionAddress(ULONG index) {
    PULONG table = (PULONG)KeServiceDescriptorTable->Base;
    return (PVOID)((ULONG64)table + (table[index] >> 4));
}

// Hook NtQuerySystemInformation (index varies per build)
// After hook, all process/thread/module queries filtered by rootkit
PatchGuard (KPP)

Kernel Patch Protection (PatchGuard, KPP) was introduced with Windows x64 to prevent SSDT hooks, IDT hooks, and kernel code patching. It periodically hashes critical kernel structures (SSDT, IDT, GDT, MSRs, ntoskrnl code sections) and compares them to expected values — a mismatch triggers BSOD (bugcheck 0x109, CRITICAL_STRUCTURE_CORRUPTION). PatchGuard runs at random intervals from a timer DPC, making it impossible to reliably disable from Ring 0 without also disabling its timer. Known PatchGuard bypasses: (1) Corrupting the integrity check context data in a way that makes it skip the check. (2) Hooking ExAllocatePoolWithTag to intercept the PatchGuard context allocation and disable its initialization. (3) Exploiting a race condition in the PatchGuard initialization path. These techniques are research-grade and kernel-build-specific. On HVCI-enabled systems, PatchGuard becomes much harder to bypass because code modifications require VTL1 cooperation.

Minifilter Bypass at Kernel Level

Minifilter suppression from kernel mode is more direct than the user-mode bypass techniques in chapter 44. A kernel rootkit can:

  1. Find the target minifilter's PFLT_FILTER in kernel memory (walk FLT_GLOBALS structures or scan pool with tag "FltF")
  2. Locate the filter's FLT_INSTANCE list on specific volumes
  3. Unlink the instance from the Filter Manager's volume instance list — the Filter Manager will no longer route IRPs through it
  4. Or: directly modify the filter's operation registration table to point its pre-operation and post-operation pointers to no-op stubs
fltmc enumerate

The user-mode tool fltmc filters queries the Filter Manager via FSCTL_GET_NEXT_FILTER_INFORMATION. If the rootkit's kernel DKOM removed the minifilter instance from the Filter Manager's list, fltmc won't show the gap — it can only report what Filter Manager tells it. Cross-view detection requires reading the kernel FLT_GLOBALS structure directly via a memory forensics tool (Volatility's filescan and modules plugins), comparing what fltmc reports against what is actually registered in kernel memory, or using hypervisor-level introspection to observe the filter instance list from a position the rootkit cannot tamper with.

Kernel Inline Hooks

Same technique as user-mode inline hooks (chapter 39) but applied to kernel functions. The rootkit writes a JMP instruction into the target ntoskrnl function to redirect execution to its own handler. Common targets:

Target Kernel FunctionRootkit Goal
NtQuerySystemInformationFilter process/module/thread lists to hide rootkit artifacts
NtQueryDirectoryFileFilter file listings to hide rootkit files on disk
NtEnumerateValueKey / NtQueryKeyFilter registry listings to hide rootkit registry keys
NtOpenProcessBlock other processes from opening handles to protected processes
ExAllocatePoolWithTagIntercept memory allocations to find and corrupt PatchGuard contexts
// Kernel inline hook installation (conceptual, PatchGuard context)
// Must temporarily disable write protection on kernel code pages
VOID DisableWriteProtect() {
    __writecr0(__readcr0() & ~0x10000); // clear WP bit in CR0
}
VOID EnableWriteProtect() {
    __writecr0(__readcr0() | 0x10000);  // set WP bit in CR0
}

VOID InstallKernelHook(PVOID target, PVOID replacement) {
    UCHAR jmp[14] = {
        0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,  // JMP QWORD PTR [rip+0]
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00  // 64-bit absolute address
    };
    *(PVOID*)(jmp + 6) = replacement;

    // On HVCI: VTL1 will block this write — BSOD or no-op depending on SLAT config
    DisableWriteProtect();
    __try {
        RtlCopyMemory(target, jmp, sizeof(jmp));
    } __except(EXCEPTION_EXECUTE_HANDLER) {}
    EnableWriteProtect();
}

Bootkit and UEFI Persistence

Boot Chain (UEFI Secure Boot off):

  Power on
   |
   v
  UEFI firmware (reads from SPI flash / NVRAM)
   |
   v
  Bootloader (bootmgfw.efi in EFI System Partition)
   |
   v
  Windows Boot Manager (winload.efi)
   |
   v
  Windows kernel (ntoskrnl.exe) loaded
   |
   v
  DKOM/callback rootkit activates from a driver loaded at boot (Start=0)

Bootkit attack surfaces:

  1. MBR/VBR bootkit (legacy BIOS systems):
     Overwrites Master Boot Record or Volume Boot Record
     Runs before bootloader, loads rootkit kernel module
     Bypassed by Secure Boot (signed bootloader required)

  2. UEFI bootkit (EFI System Partition):
     Replaces or patches bootmgfw.efi / winload.efi in ESP
     Secure Boot bypass: stolen/leaked signing certificate (BootHole 2020),
       or exploit in UEFI Secure Boot verification path
     Persists across OS reinstall (ESP not wiped by reinstall)
     Examples: ESPecter, BlackLotus (CVE-2023-24932)

  3. UEFI implant (firmware-level):
     Implant written into SPI flash (UEFI firmware itself)
     Persists across disk wipe, OS reinstall, even disk replacement
     Survives until firmware reflash
     Requires physical access or exploitation of UEFI update mechanism
     Examples: MosaicRegressor, CosmicStrand

  Detection:
     Verify ESP contents against known-good bootloader hash (Microsoft hash catalog)
     Check SPI flash content via chipsec (https://github.com/chipsec/chipsec)
     Enable Secure Boot and verify no revoked certificates in Secure Boot DB
     Monitor System Integrity (UEFI) event logs

Hypervisor-Based Rootkits

A hypervisor rootkit (also called a blue pill) installs itself as a Type-1 hypervisor beneath the running OS. Once installed, the OS runs as a guest VM with no awareness of the hypervisor. The hypervisor can intercept everything the guest does: memory reads/writes (via EPT), CPUID execution (for fingerprint hiding), MSR reads/writes (RDMSR/WRMSR), and I/O port accesses. The rootkit is completely invisible to any tool running within the OS:

Hypervisor HookIntel VT-x MechanismRootkit Use
Memory page shadowingEPT (Extended Page Tables) — two physical memory mapsShow clean code to AV scanner while executing malicious code; different physical backing page seen by code fetch vs data read
CPUID interceptVM exit on CPUID instructionModify CPUID results to hide hypervisor presence (hypervisor bit, vendor string)
MSR interceptMSR bitmap in VMCSMonitor LSTAR writes (syscall handler changes), intercept ETW provider enable MSRs
VMCALLCustom hypercall interfaceCommunication channel from guest to hypervisor rootkit
I/O port interceptI/O bitmap in VMCSIntercept disk I/O to hide files at block level
VBS vs hypervisor rootkit

Microsoft's VBS (Virtualization-Based Security) itself runs as a Type-1 hypervisor below the Windows kernel. A hypervisor rootkit that installs after VBS is already running cannot become more privileged than the already-installed hypervisor — it would run at the same level as the normal Windows kernel (as another guest), not beneath VBS. To install a hypervisor rootkit beneath VBS, the attacker would need to modify firmware/UEFI before VBS loads (bootkit-level access). This is why VBS + Secure Boot + UEFI Secure Boot in combination creates a layered defense where the trust chain must be broken at the firmware level — which requires either physical access, a firmware vulnerability, or a Secure Boot bypass.

VBS Limits and What Survives

Rootkit TechniqueWithout VBSWith VBS + HVCI
DKOM (data structure manipulation)Works; hides from all API toolsWorks for most kernel data structures; kernel data pages are still writable in VTL0
SSDT hooksWorks (PatchGuard detects eventually)Code pages are read-only via SLAT; hook write fails; BSOD if attempted via CR0.WP clear
Kernel inline hooksWorks (PatchGuard detects eventually)Code pages read-only in VTL0; writes trigger page fault; cannot hook kernel code pages
Unsigned kernel driverTest signing mode requiredHVCI enforces code integrity; unsigned driver code pages denied execute permission
Callback array DKOMWorks completelyCallback arrays are kernel DATA (not code) — still writable; DKOM suppression still works against VTL0
BYOVD (signed vuln driver)Full ring-0 primitivesKernel read/write primitives still work; cannot write to code pages; limited to DKOM and data-only attacks
UEFI implantSurvives disk wipeSurvives disk wipe; VBS does not protect firmware; only Secure Boot + TPM attestation + UEFI update restrictions limit this

Detection Strategy — Full Stack

import subprocess, json, hashlib, os, struct

def cross_view_process_check():
    """
    Compare process list from multiple sources:
    1. NtQuerySystemInformation via tasklist
    2. WMI Win32_Process
    3. Kernel memory direct (needs a driver or Volatility/WinPmem)
    Discrepancies = DKOM hiding.
    """
    # Source 1: NtQuerySystemInformation path
    r1 = subprocess.run(['tasklist', '/fo', 'csv'],
                         capture_output=True, text=True)
    pids_tasklist = set()
    for line in r1.stdout.splitlines()[1:]:  # skip header
        parts = line.strip('"').split('",')
        if len(parts) > 1:
            try: pids_tasklist.add(int(parts[1].strip('"')))
            except: pass

    # Source 2: WMI (different code path)
    r2 = subprocess.run(
        ['powershell', '-Command',
         'Get-WmiObject Win32_Process | Select-Object ProcessId | ConvertTo-Json'],
        capture_output=True, text=True)
    try:
        wmi_procs = json.loads(r2.stdout)
        pids_wmi = set(p['ProcessId'] for p in (wmi_procs if isinstance(wmi_procs, list) else [wmi_procs]))
    except:
        pids_wmi = set()

    only_in_tasklist = pids_tasklist - pids_wmi
    only_in_wmi      = pids_wmi - pids_tasklist

    if only_in_tasklist:
        print(f"[!] PIDs in tasklist but not WMI: {only_in_tasklist}")
    if only_in_wmi:
        print(f"[!] PIDs in WMI but not tasklist: {only_in_wmi}")
    if not only_in_tasklist and not only_in_wmi:
        print("[OK] Process lists consistent across sources")

cross_view_process_check()

Q & A

Why can a kernel rootkit still use DKOM to suppress callbacks even on an HVCI-enabled system?

HVCI (Hypervisor-Protected Code Integrity) protects kernel code pages: it uses SLAT (Second Level Address Translation / EPT) to mark all kernel code sections as read-only at the hardware level, preventing writes even by Ring 0 code. However, callback arrays like PspCreateProcessNotifyRoutine are kernel DATA structures stored in kernel data pages, not kernel code pages. HVCI does not mark kernel data pages as read-only — only code pages. Therefore, a rootkit that achieves arbitrary kernel memory write (via a BYOVD primitive or a kernel vulnerability exploit) can still zero out entries in PspCreateProcessNotifyRoutine, effectively silencing EDR process creation callbacks, without triggering HVCI's code-page protection. The HVCI boundary prevents the rootkit from executing unsigned code or patching kernel function bodies (code), but callback array entries are just data pointers in writable kernel memory. This is the current frontier: defenders building on VBS + HVCI have eliminated the SSDT hook and kernel inline hook attack classes while the DKOM-based callback suppression class remains viable. Microsoft's mitigation for this layer is ETW-TI (Event Tracing for Windows — Threat Intelligence), which is implemented in the kernel and partially in the Secure Kernel (VTL1), meaning a DKOM attack in VTL0 Ring 0 cannot suppress ETW-TI events — they originate at a privilege level the rootkit cannot reach. This is why ETW-TI was specifically designed as a PPL-only provider: even a Ring 0 rootkit cannot unsubscribe or suppress it without VTL1 cooperation.

What is the difference between a Blue Pill hypervisor rootkit and a traditional kernel rootkit in terms of detection difficulty?

A traditional kernel rootkit operates within the OS (Ring 0 of the guest OS). Tools running at the same level (memory forensics, kernel driver enumeration, callback enumeration) can detect it by looking for discrepancies in kernel data structures — because both the rootkit and the detector share the same physical memory view. A blue pill hypervisor rootkit (Type-1 hypervisor beneath the OS) is fundamentally different: all OS-level tools, including memory forensics tools and kernel drivers, run as guests inside the hypervisor's VM. The hypervisor mediates every memory read from the guest's perspective. The rootkit can present different physical memory content to different guest operations — show the original kernel code to a memory scanner while the actual executing code is the rootkit's patched version. Standard detection techniques that work for kernel rootkits fail entirely because they use the hypervisor's compromised interface to read memory. Detection requires: (1) Timing-based detection: hypervisor exits (VMEXITs) introduce measurable latency for sensitive operations like RDTSC, CPUID, and VM-sensitive instructions; a hypervisor rootkit that must field these exits will be measurably slower than a bare-metal system. (2) Hardware-level introspection from outside the system (an external debugger or another trusted hypervisor at a higher privilege level — this is the VBS counter-approach: VBS installs its own trusted hypervisor first, preventing a malicious hypervisor from installing beneath it). (3) CPUID hypervisor presence bit: although a rootkit can intercept and spoof CPUID, naive hypervisor rootkits sometimes forget to. (4) Secure Boot + TPM attestation: the TPM extends PCRs with every boot component hash; a hypervisor rootkit that modified the boot chain will produce different PCR values, which remote attestation can detect. In practice, hypervisor rootkits are extremely rare in the wild because they require either physical access or a severe firmware vulnerability to install beneath VBS, and their deployment is limited to nation-state actors with the most sophisticated capabilities.

Book 3 Complete

You have reached the end of Book 3: Windows OS Internals. Chapters 1–45 have covered the full Windows security model from user-mode API layering through kernel rootkit techniques and VBS architecture. The progression from Win32 API internals (DLL loading, IAT, tokens, ACLs) through process injection techniques to kernel architecture and rootkit suppression methods represents the complete technical foundation for detection engineering work on Windows systems.