DEP bypass

DEP bypass

TL;DR: Data Execution Prevention marks the stack and heap NX, so attackers chain ROP gadgets to call VirtualProtect / VirtualAlloc / WriteProcessMemory and turn an existing buffer back into RX before jumping to shellcode.

What it is

DEP (NX on the CPU side) prevents instruction fetch from pages without the executable bit. With DEP on, dropping shellcode into the stack and jumping to it crashes with STATUS_ACCESS_VIOLATION. The standard bypass is a return-orientated programming chain that re-marks attacker-controlled memory executable using a documented Win32 API, then transfers control to the shellcode placed there.

Preconditions / where it applies

  • Process opted into DEP (AlwaysOn policy, all 64-bit, most 32-bit since Win7)
  • A control-of-stack primitive — saved-RIP / SEH overwrite / stack pivot
  • Knowledge of (or leak of) at least one module base so gadget addresses are usable; if ASLR is enabled see aslr-bypass

Technique

The canonical chain calls VirtualProtect to flip an existing RW page (often the stack or a heap buffer holding shellcode) to RWX:

1
2
3
4
5
6
7
8
9
[ pop_pad gadgets to set up arguments per __stdcall (x86) ]

ROP layout (x86):
  &VirtualProtect              ; ret-to
  &return_to_shellcode         ; return address after VirtualProtect
  lpAddress                    ; pointer to shellcode page
  dwSize                       ; e.g. 0x1000
  flNewProtect = 0x40          ; PAGE_EXECUTE_READWRITE
  &writable_dword              ; lpflOldProtect

x86_64 follows fastcall: rcx/rdx/r8/r9 → use pop rcx ; ret, pop rdx ; ret, etc. mona.py automates chain generation:

1
2
!mona rop -m *.dll -cp nonull -n
!mona modules

Alternative API targets:

  • VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE) then memcpy shellcode
  • WriteProcessMemory on the current process to copy shellcode into a known RX page
  • NtSetInformationProcess with ProcessExecuteFlags to disable DEP for the whole process (only works if the process is DEP-OptOut)
  • SetProcessDEPPolicy(0) — legacy 32-bit only

Once the chain returns into the now-executable page, the shellcode runs normally. On modern Windows pair with aslr-bypass, control-flow-guard, and cet-shadow-stack consideration — naive ret-based chains die against CET.

Detection and defence

  • DEP AlwaysOn (default since Win8 x64) blocks per-process opt-out APIs
  • CFG (control-flow-guard) breaks indirect-call gadget reuse; CET (cet-shadow-stack) breaks the ret chain itself
  • ACG (Arbitrary Code Guard) blocks VirtualProtect from making pages executable in protected processes (Edge content processes)
  • EDR telemetry on VirtualProtect/VirtualAlloc with executable flag from non-image regions is a high-signal hunt

References

Related: rop-chains, aslr-bypass, mona-py, stack-buffer-overflow