Disclaimer. This research is published for educational and defensive purposes only. I do not endorse the use of this technique for unauthorized access to any computer system. Always obtain explicit written authorization before testing. If you use this on systems you don't own, that's on you, and it's illegal.

What is this?

A Rust Proof of Concept that produces a fully legitimate call stack at the exact moment an indirect syscall executes, while remaining compliant with Intel CET (Control-flow Enforcement Technology) hardware enforcement.

Every frame in the resulting call stack belongs to a signed Windows module. No synthetic frames, no fabricated unwind data. The enum function is genuinely on the stack because we genuinely called it.

The technique in one paragraph

A thread pool worker callback manually unwinds its own frame and redirects execution to EnumSystemLocalesEx. The enum function calls our callback, which unwinds again and redirects to a syscall; ret trampoline inside ntdll.dll. CET compliance is achieved through a jmp-based context switch (bypassing the shadow stack entirely) combined with RDSSPQ/INCSSPQ reconciliation to consume stale shadow stack entries left by the call into user_mode_continue.

Execution flow

Three phases, same thread:

  1. Thread pool worker captures its context, unwinds one frame to find the TP internals' return address, then redirects to EnumSystemLocalesEx. The EmbeddedContext pointer is passed through TEB.ArbitraryUserPointer.

  2. Enum callback (first invocation) retrieves the context from TEB, unwinds its own frame, sets up the syscall registers (RAX = SSN, RIP = trampoline, args in registers/stack), and jumps to the syscall; ret gadget in ntdll.dll.

  3. Enum callback (second invocation, if needed) restores overwritten stack slots, restores TEB, returns 0 to stop the enumeration.

The call stack at syscall time

ntdll!NtProtectVirtualMemory+12              <- `syscall; ret` trampoline
kernelbase!Internal_EnumSystemLocales+348    <- real frame
kernelbase!EnumSystemLocalesEx+1F            <- real frame
ntdll!TppWorkpExecuteCallback+4D0            <- thread pool internals
ntdll!TppWorkerThread+801                    <- thread pool internals
kernel32!BaseThreadInitThunk+17
ntdll!RtlUserThreadStart+2C

Every frame: ntdll.dll, kernelbase.dll, or kernel32.dll. No unbacked memory.

CET compliance

Intel CET introduces a hardware-enforced shadow stack: an independent copy of return addresses that the CPU compares against on every ret. Mismatch = #CP fault = process dead. This kills traditional stack spoofing.

This PoC takes a different route:

  • jmp instead of ret for context switches. jmp does not interact with the shadow stack at all: no pop, no comparison, no fault.
  • RDSSPQ/INCSSPQ reconciliation to consume stale entries. Because user_mode_continue is a real function (#[inline(never)]), the call that enters it pushes a return address onto the shadow stack. Since we jmp away instead of ret-ing, that entry becomes stale. INCSSPQ advances the SSP past it, realigning the shadow stack with the normal stack before the next ret.
  • Same binary works on CET and non-CET systems. If CET is off, RDSSPQ returns 0 and the reconciliation is skipped entirely.

Build

Requires Rust stable (or nightly if you want -Z ehcont-guard). The project targets x86_64-pc-windows-msvc.

cargo build --release

CET and CFG flags are already configured in .cargo/config.toml:

[target.x86_64-pc-windows-msvc]
rustflags = [
    "-C", "control-flow-guard=yes",
    "-C", "link-arg=/CETCOMPAT",
    "-C", "link-arg=/force:guardehcont",
    "-C", "link-arg=/guard:ehcont",
    # "-Z", "ehcont-guard",  # requires nightly; uncomment if using rustc nightly
    "-C", "link-arg=/DYNAMICBASE",
    "-C", "link-arg=/NXCOMPAT",
    "-C", "link-arg=/HIGHENTROPYVA",
]

What the PoC does

The demo uses ZwProtectVirtualMemory (via the spoofed chain) to:

  1. Change the protection of kernelbase!GetEraNameCountedString to PAGE_EXECUTE_WRITECOPY
  2. Write 64 NOP bytes (0x90) over the function
  3. Restore the original protection

The victim function is arbitrary. The point is that the syscall executes through a fully spoofed, CET-compliant call chain.

A note on return values

The syscall's NTSTATUS (in RAX) cannot be recovered after the trampoline returns to the enum function. The PoC uses sentinel values (0xFFFFFFFF) to detect success: if the kernel overwrites the sentinel with the real old_protect value, the syscall succeeded.

Intentional simplifications

This PoC is not a weapon. It would not survive basic static analysis by any halfway decent EDR.

Aspect PoC approach Production approach
Module resolution GetModuleHandleW + GetProcAddress PEB walking, hash-based lookup
SSN resolution Hardcoded per-OS table Dynamic via stub parsing (Hell's Gate, etc.)
Enum function Single (EnumSystemLocalesEx) Random selection from 39-function basket
Trampoline Scans target Zw function only Any ntdll function
Strings Plaintext Encrypted / obfuscated

The value is in the architecture, not in the operational stealth.

Prior art and acknowledgments

This work builds on research by people far more talented than me:

Research by Elastic Security Labs, Bill Demirkapi, taintedbits.com, and Synacktiv on CET internals was also instrumental.

Full article

For the detailed technical writeup covering the full technique, debugging war stories, and detection countermeasures, see this blog post.