CET shadow stack

CET shadow stack

TL;DR: Intel CET adds a CPU-maintained shadow stack of return addresses; every ret cross-checks the regular stack against the shadow and #CPs on mismatch — classic ROP that overwrites saved-RIP dies on the first chained gadget.

What it is

Control-flow Enforcement Technology (CET) is an Intel ISA extension (Tiger Lake+, AMD Zen 3+) with two parts: Shadow Stack (SS) for backward-edge protection and Indirect Branch Tracking (IBT) for forward edges. Windows exposes shadow stack as “Hardware-enforced Stack Protection” (HSP) since Windows 10 20H1 / 21H1. When a thread is CET-enabled, every call pushes the return address onto a separate, supervisor-readable-only shadow page; every ret pops both stacks and faults (#CP) if they disagree.

Preconditions / where it applies

  • CET-capable CPU (Intel 11th gen+, AMD Zen 3+) and Windows 10 20H2+/Server 2022 with HSP enabled
  • Per-process opt-in: the binary’s loader config / /CETCOMPAT link flag plus the process mitigation policy ProcessUserShadowStackPolicy
  • User mode only (kernel CET shipped with Windows 11 22H2 — see cfg-cet-kernel)

Technique

Attacker workarounds when CET is on:

  • Pivot to JOP / COP — jump-oriented and call-oriented programming use indirect jmp/call, which the shadow stack does not check. IBT (forward edge) blocks them unless an endbr64 precedes the target, so chains have to land on legitimate IBT-tagged targets.
  • Data-only attacks — corrupt heap metadata, vtable slots, or app-level state to reach RCE without ever returning into a gadget.
  • Disable shadow stack — overwrite the ssp (Shadow Stack Pointer) via wrss/rstorssp if the process exposes a primitive; or call SetThreadInformation(ThreadDynamicCodePolicy)-style APIs from within the process to relax CET (usually requires existing arbitrary call).
  • Find a non-CET module — a single DLL loaded into the process without /CETCOMPAT disables the policy for the whole process unless STRICT_MODE is set.
  • Race the SSP — research has demonstrated TOCTOU on rstorssp/saveprevssp sequences during signal-like transitions.
1
2
3
4
5
// Querying CET state at runtime
PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY p = {0};
GetProcessMitigationPolicy(GetCurrentProcess(),
    ProcessUserShadowStackPolicy, &p, sizeof(p));
// p.EnableUserShadowStack, p.EnableUserShadowStackStrictMode

Detection and defence

  • Enable HSP in audit mode first (Set-ProcessMitigation -Name foo.exe -Enable UserShadowStack,UserShadowStackStrictMode); WER reports #CP faults as STATUS_STACK_BUFFER_OVERRUN with shadow-stack tag
  • Combine CET with CFG (forward edge) and ACG (no dynamic code) — see control-flow-guard
  • Ensure every loaded module is /CETCOMPAT; deny-list non-compat DLLs via Image Load exploit-guard rules
  • Kernel: HVCI raises the bar further by preventing the attacker from forging shadow-stack pages — see hvci-vbs

References

Related: control-flow-guard, xfg, rop-chains, cfg-cet-kernel