Blackbird: Defeating PatchGuard, One Layer Deeper.
How do you maintain observability without sacrificing system stability? Diving into the depths of virtualization and extended page tables.
Read articleHow do you maintain observability without sacrificing system stability? Diving into the depths of virtualization and extended page tables.
How do you maintain observability without sacrificing system stability? Diving into the depths of virtualization and extended page tables.
Read articleA long Windows Active Directory chain from LDAP injection and NTLM coercion through delegated ACL abuse, AD CS ESC3, protected-object inheritance repair, and S4U2Self U2U with RBCD.
Read articleWhat happens when an EDR trusts filenames, unauthenticated localhost traffic and world-writable kernel objects? Five vulnerabilities, two SYSTEM LPE's and an RCE, a driver load and a lot of assumptions that should never have crossed a security boundary.
Read articleA complete Windows attack chain through OAuth account-linking abuse, stored browser navigation, SQLite extension loading, DPAPI credential recovery, and a writable SYSTEM service binary.
Read articleSysWhispers, HellsGate, HeavensGate, SidewaysGate, SpoofGate, TFGate, DoomGate, whatever gate your tool is being detected before the initial handle fully opens. How do EDR's detect & deny direct and indirect syscalls?
Read articleHow does Blackbird make Windows lie to malware's faces? Most anti-analysis checks trust the kernel because they have no choice. Blackbird weaponizes this by modifying syscall, timing & registry return data, erasing VM-identifiers and much, much more.
Read articleThere's a reason security products avoid kernel hooking. They are fragile, build-sensitive, and BSOD prone. Advanced malware analysis demands the visibility they provide. This post delves into the hook engine behind Blackbird and the struggle developing it.
Read articleModern defensive tooling doesn’t need to see payloads to stop you, it only needs to see the call path. This post breaks down how Windows system calls are intercepted, how syscall stubs became signatures, and why ActiveBreach takes a fundamentally different approach.
Read articleOk I admit, the title is clickbait.
My earlier post, Blackbird: Doing What EDRs Won't, covered the first kernel hook engine. It used inline hooksA detour replaces the first instructions of a function with a jump to another handler. The displaced instructions are preserved so the original function can still run. inside ntoskrnl.exeThe Windows kernel executable. It contains core operating-system logic and the kernel implementations of native Nt and Zw routines..
In plain English, Blackbird replaced the function prologueThe prologue is the first group of machine instructions at the entry point of a function. A hook must copy complete instructions rather than cutting one in half. with a jump to its own handler. A trampolineA small executable bridge that replays the displaced original instructions, then jumps back into the untouched remainder of the function. replayed the instructions that had been moved and returned execution to Windows. Making that safe required correct instruction decoding, register preservation, cross-processor synchronization, and reliable rollback.
It technically worked, but proper analysis environments need stability. Kernel inline hooking changes protected bytes inside ntoskrnl.exe. Windows Kernel Patch Protection—PatchGuardAn x64 Windows integrity mechanism that checks protected kernel code and critical structures for unauthorized modification. periodically validates protected kernel code and structures, and when it finds the modification Windows blue-screens with CRITICAL_STRUCTURE_CORRUPTION (0x109). It soon became obvious this method was unsustainable, so I started researching ways around it.

Initially I researched bypassing PatchGuard, but I quickly realized I would be building against an intentionally undocumented moving target. Even if I got it working, a Windows update could turn the bypass into mass instability. Of course I had heard of hypervisors, but I imagined they were way outside my skillset and had no clue Second Level Address Translation (SLAT)A processor feature that adds a hypervisor-controlled memory translation after the page tables owned by the guest operating system. Intel calls its implementation EPT. hooks existed.
The new hook path keeps the same instrumentation model but moves the modified bytes somewhere else. Blackbird still intercepts selected Windows Nt* routines, enters the same kernel handlers, captures the same state, and calls the original implementation through a trampoline.
The difference is that the original ntoskrnl.exe page is never patched.
Instead Archangel, the Blackbird hypervisorA privileged layer below the guest operating system that controls virtual processors, memory translation, and selected hardware events.—uses Intel Extended Page Tables (EPT)The Intel implementation of Second Level Address Translation. EPT adds a hypervisor-controlled translation from guest physical memory to the actual machine page. to give selected processes an executable shadow view of that page.
Because the crash arrived late, I initially treated it as an ordinary hook bug. I checked the instruction decoder, register preservation, cross-processor patching, and teardown. Short runs became completely stable, but after extended periods KPP became a reoccuring problem.
An inline hook changes the physical page backing a kernel routine:
ntoskrnl physical page
┌──────────────────────────────────────────┐
│ original prologue → overwritten jump │
└──────────────────────────────────────────┘
Every process shares that page. PatchGuard, other drivers, debuggers, and Blackbird's target all resolve the address to the same modified bytes. Making the patch more reliable would never make it less visible.
I could start lying to PatchGuard, or I could stop changing the thing it protects.

The requirement was strange but precise: keep the same kernel address, execute different bytes for selected processes, and leave Windows' copy untouched.
EPTIntel's implementation of SLAT. It lets a hypervisor control the final mapping and permissions of guest physical memory. provided exactly that gap. Windows still translates a process virtual addressThe address a process sees. Windows page tables translate it before memory is accessed. through its page tablesCPU-readable structures that map virtual addresses to physical pages.. The active CR3The register containing the root of the current page-table hierarchy. In practice it identifies the address space currently running. selects which address space is in use. Archangel then controls one final translation below Windows:
guest virtual address
│
▼
Windows page tables
│
▼
guest physical address
│
▼
EPT hierarchy
│
▼
actual machine page
Windows decides which guest page it wants. Archangel decides which real page backs it. That let me build two views:
Identity EPT Hook EPT
──────────── ────────
guest physical ─► original page guest physical ─► shadow page
clean bytes patched copy
The SSDTWindows uses this table to route a system-call number to the corresponding Nt routine inside the kernel. still sends execution to the real ntoskrnl address. Blackbird changes only the final backing page, and only for a monitored process.
The nice surprise was that the old hook engine was not wasted. It already knew how to decode complete instructions, build trampolines, and enter Blackbird's kernel handlers. I only needed to move where the jump was written.
For each page containing a target routine, the driver creates a private 4 KB shadow copy. Stripped down to the important part, the staging path looks like this:
rawAllocation = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
BK_NTAPI_SLAT_SHADOW_ALLOC_SIZE,
BK_NTAPI_SLAT_SHADOW_POOL_TAG
);
if (rawAllocation == NULL)
return NULL;
shadowPage = BkntkiSlatAlignShadowAllocation(rawAllocation);
RtlCopyMemory(shadowPage, (PVOID)kernelPageVa, PAGE_SIZE);
livePa = MmGetPhysicalAddress((PVOID)kernelPageVa);
shadowPa = MmGetPhysicalAddress(shadowPage);
The jump goes into the copy at the same offset as the original function:
original page shadow page
───────────── ───────────
NtAllocateVirtualMemory: NtAllocateVirtualMemory:
original prologue jump BlackbirdHandler
original body original body
Targets on the same page share one copy. Once the redirects are staged, the driver gives Archangel the page through a hypercallA controlled call from the guest into the hypervisor, similar in spirit to a system call into the operating system..
The shadow redirect uses a 13-byte absolute jump through R11, a general-purpose CPU register used here to hold the handler address:
mov r11, <Blackbird hook handler>
jmp r11
Preparation rejects any target without enough complete instructions or whose patch would cross a page boundary. Once execution enters the handler, the old path takes over: recursion guard, target check, argument capture, trampoline, then back to Windows.
So yes, these are still kernel hooks. The hypervisor chooses the page; the driver still performs the instrumentation.

The obvious first mapping was a readable and executable shadow page. It ran perfectly, but anything reading kernel code as data could see the jump. I had moved the mutation without solving visibility.
The policy became:
Execution stays on the shadow page without exiting. A data read lacks permission, causing an EPT violationA hardware event raised when guest memory access conflicts with the permissions in the active EPT entry. and a VM exitA controlled transition from guest execution into the hypervisor. Useful, but much more expensive than ordinary execution.. Archangel handles that read in four steps:
The useful part of the violation handler is small. It swaps in the clean page, records what must be restored, enables the one-instruction trap, and invalidates the cached translation:
if (ReadAccess != FALSE && hookRoot != nullptr) {
const UINT64 clean = EPT_ENTRY_READ | EPT_ENTRY_EXECUTE;
if (EptSetExisting4KbLeafMappingForRoot(
hookRoot, gpa, originalPa, clean) != FALSE &&
EptQueuePendingSlatRestore(gpa, shadowPa) != FALSE &&
VmxSetMonitorTrapFlag(TRUE) != FALSE) {
EptInvalidateLocalContext();
return TRUE;
}
}
After that one instruction, the VM-exit dispatcher restores the execute-only shadow mapping:
case VMX_EXIT_REASON_MONITOR_TRAP_FLAG:
EptRestorePendingSlatHooks();
VmxSetMonitorTrapFlag(FALSE);
EptInvalidateLocalContext();
return FALSE;
Then came the same-page case... An instruction can execute from a page while reading data from that same page. Switching the mapping underneath it can invalidate the code currently running, so the handler checks RIPThe x86-64 instruction pointer: the address of the instruction currently being executed. and keeps the shadow view for that instruction when required.
Writes are rejected rather than silently modifying either copy. A write to a protected hook page becomes a guest page fault and increments a diagnostic counter.
The next version worked, and then immediately started firing Blackbird's hook handlers across every process on the system. Performance did not like that.
The old inline hooks could do a quick PID lookup inside the handler, but doing that for thousands of intercepted calls every second was already wasteful. Doing more work in the hypervisor would turn Blackbird into a very advanced way of making Windows feel like it was running on a calculator.
Windows identifies a process by PID. Archangel has something better: CR3, the page-table root already describing the address space running on that processor. One complication: KPTIA mitigation that separates user and kernel page-table views, so one process can use different CR3 values during a system call. means the same process can have two:
Whenever the guest changes CR3, Archangel asks one question: does this address space belong to a monitored process? If yes, that processor receives the hook EPT view. If not, it stays on the clean identity view and never touches Blackbird's hooks.
So, start clean, scan for a matching target, and write the hook EPT pointer only when both the user or kernel CR3 and the target configuration match:
GuestCr3 &= AAHV_CR3_ADDRESS_MASK;
selectedEptp = EptGetIdentityMapPointer();
for (LONG i = 0; i < targetCount; ++i) {
if (EptTargetRequiresHookRoot(&g_ArchangelTargets[i], GuestCr3)) {
selectedEptp = EptGetHookMapPointerForCurrentProcessor();
break;
}
}
if (currentEptp != selectedEptp)
__vmx_vmwrite(EPT_VMCS_EPT_POINTER, selectedEptp);
My first attempt only tracked one CR3, so the hooks disappeared halfway through a system-call transition. Tracking both fixed that and, moreover, kept PatchGuard and ordinary system activity on the identity view.

That solved most of the performance problem. Normal execution from the shadow page does not cause a VM exitA transition from guest execution into the hypervisor. VM exits enable interception but cost far more than an ordinary instruction.; the patched prologue jumps straight into Blackbird's kernel handler. Exits are reserved for things Archangel actually needs to handle, such as clean reads, rejected writes, and EPT view changes.
The rest came down to not being stupid with the expensive parts:
At this point the obvious question was: if this works, why do EDRs not all do it?
They can. The difficult part is not the proof of concept; it is deploying a competing hypervisor across millions of machines the vendor does not control.
VBS and HVCI use the Windows hypervisor as part of the platform's security boundary. Microsoft also documents the practical ownership problem plainly: when Hyper-V, Memory Integrity, or Credential Guard is active, another virtualization stack cannot simply take over the same hardware virtualization extensions. Windows applications that need to act as virtualization hosts are expected to use the Windows Hypervisor Platform so they remain compatible with VBS.
An EDR has to survive consumer laptops, servers, VDI, strange firmware, old drivers, new drivers, other security software, and whatever the OEM preloaded that week. It cannot reasonably tell every customer to disable a platform security boundary so its sensor can own VMX and EPT. The support matrix would be brutal even if the engineering were perfect.
Blackbird gets to make a different trade because it is built for controlled analysis environments. The analyst owns the VM template. Nested virtualization can be exposed deliberately. The guest build, driver set, CPU features, snapshot state, and boot configuration can be pinned and tested together. When Archangel needs direct ownership of VMX and EPT, the analysis profile can run with VBS and HVCI disabled inside that disposable guest instead of negotiating with an unknown endpoint configuration.
Special thanks to: