PreviousMode Manipulation
Windows enforces the boundary between user mode and kernel mode at every system call. When a thread invokes a system call, the kernel checks ExGetPreviousMode() to determine whether the call originated from user mode (value 1) or kernel mode (value 0). If the previous mode is UserMode, the kernel validates all pointers passed by the caller: ProbeForRead and ProbeForWrite verify that addresses fall within user-mode address space, preventing a user-mode caller from passing kernel addresses to functions like NtReadVirtualMemory or NtWriteVirtualMemory. If the previous mode is KernelMode, these probes become no-ops, because kernel-mode callers are trusted to pass valid addresses.
The PreviousMode value is stored in the KTHREAD structure of the current thread. It is a single byte. Overwriting it from 1 to 0 causes the kernel to treat all subsequent system calls from that thread as kernel-originated. Every probe is skipped. Every address validation is bypassed. The thread can pass kernel addresses to NtReadVirtualMemory and NtWriteVirtualMemory, achieving full arbitrary kernel read/write through the kernel's own, legitimate APIs. No pool spray. No I/O Ring. No pipe attributes. Just standard NT system calls operating on kernel memory because the kernel no longer validates the caller's addresses.
This is arguably the cleanest kernel R/W technique available, because the read and write operations are performed by ntoskrnl.exe itself through well-tested code paths. There is no risk of corrupting pool metadata, no concern about allocation lifetimes, and no need for build-specific structure offsets beyond the KTHREAD.PreviousMode offset itself.
How PreviousMode is set
During a system call transition, the CPU switches from ring 3 to ring 0 through the SYSCALL instruction. The kernel's system call handler (KiSystemCall64 on x64) saves the thread's previous context and sets KTHREAD.PreviousMode to 1 (UserMode) to record that the call came from user mode. Kernel-mode callers (other kernel drivers or subsystems) that invoke system services through Zw* wrappers set PreviousMode to 0 (KernelMode) before entering the service routine.
The field persists for the duration of the system call. All functions within the call chain that need to distinguish user-mode from kernel-mode callers read this single field. This centralized design is efficient but creates a single point of failure: if the field is corrupted before or during a system call, every downstream check is affected.
Exploitation paths
There are two primary ways to achieve the PreviousMode flip.
Decrement-by-one is the most common approach and connects directly to the arbitrary increment/decrement primitive. In CVE-2025-3464, the ASUS AsIO3.sys driver exposed an IOCTL that called ObfDereferenceObject on a user-supplied pointer. ObfDereferenceObject decrements the value at (pointer - 0x30). By supplying KTHREAD.PreviousMode_address + 0x30 as the pointer, the decrement lands on the PreviousMode field, changing it from 1 (UserMode) to 0 (KernelMode). A single IOCTL call, and the thread has kernel-level access.
Direct write uses a write-what-where primitive to write 0 to the PreviousMode field. This is straightforward when the attacker has a write primitive that accepts both address and value, but most write-what-where vulnerabilities can achieve the same result through token swapping more directly. The PreviousMode path is preferred when the write value is constrained (e.g., the attacker can only write zero) or when the attacker wants to avoid token-related detection mechanisms.
Post-flip exploitation
Once PreviousMode is 0, the thread can call NtReadVirtualMemory and NtWriteVirtualMemory with kernel addresses as the source or destination. The kernel's probe checks are skipped, and the memory operations proceed using kernel-mode access rights.
The typical escalation sequence after a PreviousMode flip is to use NtReadVirtualMemory to traverse the ActiveProcessLinks doubly-linked list starting from the current process's _EPROCESS, searching for PID 4 (the System process). Once found, read the System process's Token field. Then use NtWriteVirtualMemory to overwrite the current process's Token field with the System token, performing a token swap.
This sequence is performed entirely through documented NT API calls, using the kernel's own memory management code paths. The operations are indistinguishable from legitimate kernel-mode memory access, making detection through API hooking difficult.
Detection and restoration
The PreviousMode flip is detectable through several mechanisms. EDR products that monitor thread context can compare the PreviousMode field against the expected value for user-mode threads. Kernel callbacks registered through PsSetCreateThreadNotifyRoutine can audit thread state. The Windows Security Event Log may record anomalous privilege use if the thread performs operations that would normally require kernel-mode origin.
After completing the exploitation, the attacker should restore PreviousMode to 1. Leaving it at 0 can cause system instability (the kernel may skip necessary user-mode address validation in contexts where it is actually required) and increases the detection window. Most exploits write 1 back to the field as part of their cleanup sequence.
Related CVEs
| CVE | Driver | Description |
|---|---|---|
| CVE-2024-26229 | csc.sys |
Missing access check allows EoP |
| CVE-2025-3464 | AsIO3.sys |
ObfDereferenceObject decrement-by-one flips PreviousMode from 1 to 0 |
AutoPiff Detection
AutoPiff identifies patches related to PreviousMode vulnerabilities through two rules. The previous_mode_gating_added rule fires when a patch introduces explicit checks on the caller's previous mode before performing a privileged operation. The access_mode_enforcement_added rule detects cases where a patch adds access mode validation (comparing against KernelMode or UserMode) in code paths that previously lacked it.
previous_mode_gating_addedaccess_mode_enforcement_added
Mitigations
HVCI makes PreviousMode exploitation harder indirectly by constraining the initial corruption primitive. On HVCI-enabled systems, function pointer corruption (which might otherwise be used to reach the PreviousMode overwrite) is blocked by code integrity enforcement. However, HVCI does not protect the KTHREAD.PreviousMode field itself. If the attacker achieves a data-only write to the field (through a write-what-where or an increment/decrement), HVCI provides no defense.
No current Windows mitigation directly protects the PreviousMode field from modification. Kernel Data Protection (KDP) could theoretically mark KTHREAD structures as read-only through VBS, but this would impose significant performance overhead on context switching and system call handling, since PreviousMode is legitimately written on every system call transition. The field's design as a writable, single-byte flag in a frequently accessed structure makes it inherently difficult to protect without architectural changes.
See Also
- Arbitrary Increment/Decrement -- the most common initial primitive for achieving the PreviousMode flip
- Write-What-Where -- the alternative initial primitive for direct PreviousMode overwrite
- Token Swapping -- the typical privilege escalation step after PreviousMode-based kernel R/W
- ACL / SD Manipulation -- an alternative post-R/W escalation path