Security News

Cybersecurity news aggregator

🔓
MEDIUM Vulnerabilities Reddit r/netsec

SMAP is Pre-Disarmed: How a Stack Pivot That Shouldn't Work Revealed a Kernel-Wide Design Compromise

  • What: Researchers discover a kernel-wide design compromise through a stack pivot
  • Impact: System-level vulnerabilities may allow privilege escalation on affected systems
Read Full Article →

While developing a kernel exploit chain against the HackSys Extreme Vulnerable Driver (HEVD), I encountered a weird contradiction. (article here) I was abusing an arbitrary write primitive to overwriteHalDispatchTable+0x8and redirect execution through a stack pivot. The pivot gadget was a 32-bit partial register move: This instruction zeroes the upper 32 bits of RSP and lands execution in a fake stack I had constructed in low user-mode memory (aVirtualAllocbelow the 4GB boundary). From there, a ROP chain disabled SMEP viamov cr4, rcx, jumped to a token-theft payload, and finally recovered the original kernel stack. The exploit worked. I got SYSTEM. The VM stayed stable long enough to pop a shell. But something was wrong here. SMAP (Supervisor Mode Access Prevention) was supposed to crash my exploit chain! I ran the exe fully expecting a BSOD. CR4 bit 21 was set. The fake stack was inuser-modememory. The ROP chain was executing in Ring 0, reading gadget addresses from a user-mode page. Why didn’t SMAP kill the pivot the moment the kernel tried to read the first ROP gadget from my user-mode fake stack? I had three hypotheses: I designed three experiments to find out. Note: This experiment was conducted on a Windows 11 VM (build 26200.8328). SMAP enforcement is conditional. The CPU checks both CR4[21] (SMAP enable) and RFLAGS[18] (AC bit). If AC=1, SMAP is suppressed. TheSYSCALLinstruction masks RFLAGS usingIA32_FMASKon entry to the kernel. My first question was simple: doesIA32_FMASKclear AC, or does AC remain unmasked? I checked the mask in WinDbg: 0x4700 covers bits 8, 9, 10, and 14 (TF, IF, DF, NT). Bit 18 (0x40000) isnotset. SoSYSCALLdoes not clear AC via the hardware mask. But that only tells me what the hardware doesn’t do. MaybeKiSystemCall64clears it in software? Or maybe it sets it? I needed to know whether AC arrives in kernel mode because it survived from user mode, or because the kernel actively enables it. First, I built a minimal test driver that readRFLAGSat the very first instruction of itsIRP_MJ_DEVICE_CONTROLhandler. The user-mode client explicitly set AC=1 before callingDeviceIoControl: In the driver: AC was set. The IOCTL dispatch path entered the driver with RFLAGS.AC=1. This proved that AC survives the syscall transition, SYSCALL and the kernel entry path do not clear it. What if the user-mode client does the opposite: explicitly clears AC before the syscall? I modified the client to force AC=0:

Share this article