Executive Summary
Modern Windows 11 stacks several kernel-protection layers on top of each other: Virtualization-Based Security (VBS), Hypervisor-protected Code Integrity (HVCI), and kernel-mode Control-flow Enforcement Technology (kCET). Together they make classic code-injection and control-flow-hijacking attacks against the kernel extremely difficult. This research from Exploit Pack demonstrates a data-only technique that sidesteps those mitigations entirely by treating the Interrupt Descriptor Table (IDT) as a redirection surface rather than injecting any new code.
The core idea is to never touch the live IDT. Instead, the original IDT page is copied into a Free Writable Area (FWA) physical page, a single gate is modified inside that clone, and the processor’s IDT mapping is briefly pointed at the clone for the duration of a trigger window. Execution is bridged through the legacy INT 0x2E path into existing kernel dispatch machinery, a token-swap payload runs once, and both the service-table entry and the original IDT mapping are restored. The result is kernel code execution — proven with a token swap — on a fully hardened Windows 11 (latest build) with VBS, HVCI and kCET enabled.
What the IDT Is
This work continues a line of research showing how the EP3 platform’s DOG (Data-Only Gadgets) capability can hijack kernel tables such as the SSDT and Shadow SSDT. The next target is the IDT. The Interrupt Descriptor Table is a processor data structure that tells the CPU which code to run when various interrupts occur — it is the central dispatch mechanism for all interrupt handling in x86/x64 systems and the bridge between hardware events and the software that handles them.
At its core the IDT is an array of descriptors, each 8 or 16 bytes in size (for 32-bit or 64-bit respectively). Each 64-bit entry has the following layout:
| Offset | Size | Field |
|---|---|---|
| 0x00 | 2 bytes | Offset bits 0–15 (low word of handler address) |
| 0x02 | 2 bytes | Segment Selector (code segment in GDT/LDT) |
| 0x04 | 1 byte | Interrupt Stack Table (3 bits used, 5 reserved) |
| 0x05 | 1 byte | Gate Type & Attributes (present, DPL, gate type) |
| 0x06 | 2 bytes | Offset bits 16–31 (middle word) |
| 0x08 | 4 bytes | Offset bits 32–63 (high dword) |
| 0x0C | 4 bytes | Reserved (must be zero) |
IDT entries are called gates and may be Interrupt Gates, Task Gates, or Trap Gates. Each entry defines the target handler for a specific vector along with the gate metadata required to transfer execution into kernel mode.
The IDT Is Per-CPU
Because the IDT is local to each processor, the first problem was to bind execution to a specific CPU and resolve that processor’s IDT mapping — the IDTR-derived base address, the physical page backing the table, and the page-table entry that controls the mapping. Rather than assuming a single global table, the technique treats the IDT as a per-CPU structure.
The current implementation pins the executing thread to a selected processor before resolving or modifying any IDT-related state. For the token-swap payload, the active process is bound to CPU 0: the thread is moved to CPU 0, the IDT mapping for CPU 0 is cloned and remapped, the int3-to-INT 0x2E redirection is triggered, and the token payload runs exactly once. Pinning to a single CPU avoids duplicate token swaps and duplicate shell launches while still respecting the processor-local nature of the IDT.
Why Hooking INT 0x2E Used to Be Easy to Spot
A limitation inherent to hooking an interrupt like 0x2E is that almost nobody uses it anymore — current hardware performs the system-call transition via SYSENTER/SYSCALL in conjunction with MSRs. In the old days, spotting an IDT hook was straightforward: the IDT descriptor for vector 0x2E points at KiSystemService() inside ntoskrnl.exe. If the handler offset for INT 0x2E resolved to an address outside the range of ntoskrnl.exe, it was obvious that something had been tampered with.
The Data-Only Twist: FWA-Backed IDT Cloning
So what is new here, beyond using data-only modification and remapping to alter the IDT? The technique clones the live IDT page into a page backed by a Free Writable Area (FWA). An FWA page is a physical page that lies outside the normal operating-system-managed RAM ranges, yet remains readable and writable through the physical-memory primitive. In effect, FWA pages are the gaps between allocations that the operating system leaves unused.
In this project these gaps serve as external backing storage for DOG operations. The implementation scans the gaps around managed physical memory, filters out unsafe regions such as MMIO, and then verifies candidate zero pages by writing a temporary test pattern, reading it back, and restoring the original contents. Once a page passes verification it can be used as a controlled physical workspace.
For the IDT technique, the original IDT page is copied into a verified FWA page, the chosen gate is modified inside that clone, and the processor’s IDT mapping is transiently redirected to the FWA-backed clone during the trigger window. From the CPU’s point of view the IDTR base stays at the same virtual address; only the physical page reached by the page-table translation changes for the duration of the window.
This gives the manipulation three useful properties:
- The live IDT page remains intact.
- The modified table lives in a physical page selected and controlled by the data-only primitive, rather than in ordinary process allocation.
- Restoration is simple: the IDT page-table entry is pointed back at the original physical page, and the FWA clone page can be cleared after use.
The Execution Flow
The resulting end-to-end flow is:
FWA page -> cloned IDT -> #BP -> INT2E dispatch bridge -> native service slot -> x64 ABI target -> token payload -> restore
The token payload calls the kernel routines PsGetCurrentProcess, PsReferencePrimaryToken, memcpy, ObDereferenceObject, and — as a visible proof of concept — DbgPrint and DbgPrintEx.
INT 0x2E as a Dispatch Bridge
INT 0x2E is the legacy way of performing a user-to-kernel mode transition, and it is still supported by every x86 CPU in existence. Issuing INT 0x2E invokes the interrupt service routine registered in the IDT for vector 0x2E — nt!KiSystemService — conceptually similar to how the SSDT is reached.
On x64 Windows there are two separate contracts involved, and they must not be mixed. The first is the interrupt/syscall dispatch contract, which identifies a native service by service number and transfers execution into the kernel service dispatcher. The second is the normal Windows x64 function ABI used once a kernel routine is actually invoked.
The Windows x64 ABI passes the first four integer or pointer arguments in RCX, RDX, R8, and R9. The caller also reserves 32 bytes of shadow space on the stack, and any additional arguments are passed on the stack. This is the ABI used by normal kernel routines.
Historically, 32-bit Windows used INT 0x2E as a native system-call entry mechanism, with EAX holding the service number and EDX pointing to the caller’s argument stack — that is the x86 contract. On x64 the normal native system-call path uses SYSCALL rather than INT 0x2E, but the IDT still provides an interrupt-dispatch surface. In this technique INT 0x2E is not used as a 32-bit ABI; it is used purely as an existing kernel dispatch bridge, reached through the cloned IDT path.
A preselected native service is then temporarily redirected so its service-table entry points at the intended kernel routine. The NT function used is NtSetQuotaInformationFile. The choice of service is unimportant — it simply provides a stable service-table slot that can be redirected during the trigger window. The actual target routine is invoked according to the Windows x64 calling convention, and the service-table entry is restored immediately after the call.
Proof of Concept
The token-swap payload uses this mechanism to call PsGetCurrentProcess, PsReferencePrimaryToken, memcpy, and ObDereferenceObject. As a visible proof of concept, DbgPrint and DbgPrintEx are also called — their output is visible in the kernel debugger while attached to the target kernel with the EP3 tool:

This shows that the IDT can be used as a data-only redirection surface: the IDT gate is modified in a cloned table, the live mapping is switched only for the trigger window, and the actual call completes through existing kernel dispatch machinery. After execution, both the native service entry and the original IDT mapping are restored.
And finally, the mandatory screenshot — the token swap as proof of kernel code execution under a VBS/HVCI/kCET-enabled Windows 11 (latest build):

Key Takeaways
- The technique achieves kernel code execution on Windows 11 with VBS, HVCI and kCET enabled — without injecting any new code.
- It is a data-only attack: the live IDT is never modified; a clone in an FWA-backed physical page is what gets changed.
- Free Writable Area (FWA) pages — physical gaps outside OS-managed RAM but reachable through a physical-memory primitive — provide attacker-controlled backing storage.
- The IDT is per-CPU, so execution is pinned to a single processor (CPU 0) before any IDT state is touched, preventing duplicate payload runs.
- Legacy
INT 0x2Eis reused as a dispatch bridge into existing kernel machinery, not as a 32-bit ABI; the x64 calling convention (RCX/RDX/R8/R9 + 32-byte shadow space) governs the final call. - A throwaway service slot (
NtSetQuotaInformationFile) is briefly redirected to the target routine, then restored. - Everything is restored after a single trigger window — IDT mapping, service-table entry, and FWA clone — leaving minimal residue.
Defensive Recommendations
- Don’t treat VBS/HVCI/kCET as complete. They raise the bar against code injection and control-flow hijacking but do not stop data-only manipulation of kernel structures; assume determined attackers will pivot to data-only techniques.
- Cut off the physical-memory primitive. This entire class depends on arbitrary physical read/write; prioritize eliminating physical-memory primitives (vulnerable signed drivers, exposed
\Device\PhysicalMemory, etc.) and enforce strict driver blocklists (HVCI/WDAC, Microsoft vulnerable-driver blocklist). - Monitor IDT integrity per-CPU. Periodically validate each processor’s IDTR base and the physical page it resolves to; a base that maps to a physical page outside known IDT pages — even transiently — is a strong signal.
- Watch the page-table entries behind the IDT. Transient remapping of the IDT’s PTE to an unusual physical page is the heart of this technique; PTE-level integrity checks on critical kernel structures can catch it.
- Flag legacy
INT 0x2Edispatch on x64. Genuine x64 system calls useSYSCALL; unexpectedINT 0x2E/KiSystemServiceactivity warrants investigation. - Audit service-table entries for transient redirection. Short-lived changes to slots such as
NtSetQuotaInformationFilethat revert immediately after a call are suspicious. - Account for FWA / off-map physical pages. Detection that assumes only OS-managed RAM matters will miss clones staged in unmanaged physical gaps; extend memory-integrity tooling to cover them.
Conclusion
This research shows that the IDT can serve as a data-only redirection surface even on a fully hardened Windows 11 host. By cloning the live IDT page into an FWA-backed physical page, modifying a single gate inside that clone, switching the live mapping only for a brief trigger window, and completing the call through existing kernel dispatch machinery, the technique sidesteps VBS, HVCI and kCET without injecting code — and then restores both the native service entry and the original IDT mapping. It is a reminder that mitigations focused on code integrity and control flow do not, on their own, protect the integrity of kernel data structures.
Original text: “IDT Table Hijacking under VBS/HVCI/kCET in Windows 11” at Exploit Pack.

