No-exec / NX

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Basic Information

The No-Execute (NX) bit, also known as Execute Disable (XD) in Intel terminology, is a hardware-based security feature designed to mitigate the effects of buffer overflow attacks. When implemented and enabled, it distinguishes between memory regions that are intended for executable code and those meant for data, such as the stack and heap. The core idea is to prevent an attacker from executing malicious code through buffer overflow vulnerabilities by putting the malicious code in the stack for example and directing the execution flow to it.

Modern operating systems enforce NX through the page table attributes that back the ELF program headers. For example, the PT_GNU_STACK header combined with the GNU_PROPERTY_X86_FEATURE_1_SHSTK or GNU_PROPERTY_X86_FEATURE_1_IBT properties let the loader know whether the stack should be RW or RWX. When NX is enabled and the binary was linked with a non-executable stack (-z noexecstack), any attempt to pivot execution into attacker-controlled data pages (stack, heap, mmap’ed buffers, etc.) will raise a fault unless those pages were explicitly marked as executable.

Detecting NX quickly

  • checksec --file ./vuln will display NX enabled or NX disabled based on the GNU_STACK program header.
  • readelf -W -l ./vuln | grep GNU_STACK exposes the stack permissions; the presence of an E flag indicates that the stack is executable. Example:
$ readelf -W -l ./vuln | grep GNU_STACK
  GNU_STACK      0x000000 0x000000 0x000000 0x000000 0x000000 RW  0x10
  • execstack -q ./vuln (from prelink) is handy when auditing large collections of binaries because it prints X for binaries that still have an executable stack.
  • At runtime, /proc/<pid>/maps will show whether an allocation is rwx, rw-, r-x, etc., which is useful when verifying JIT engines or custom allocators.

Bypasses

Code-reuse primitives

It’s possible to use techniques such as ROP to bypass this protection by executing chunks of executable code already present in the binary. Typical chains include:

  • Ret2libc
  • Ret2syscall
  • Ret2dlresolve when the binary does not import system/execve
  • Ret2csu or Ret2vdso to synthesize syscalls
  • Ret2… — any dispatcher that lets you stitch controlled register state with existing executable code to invoke syscalls or library gadgets.

The workflow is usually: (1) leak a code or libc pointer through an info leak, (2) resolve function bases, and (3) craft a chain that never needs attacker-controlled executable bytes.

Sigreturn Oriented Programming (SROP)

SROP builds a fake sigframe on a writable page and pivots execution to sys_rt_sigreturn (or the relevant ABI equivalent). The kernel then “restores” the crafted context, instantly granting full control over all general-purpose registers, rip, and eflags. Recent CTF challenges (e.g., the Hostel task in n00bzCTF 2023) show how SROP chains first invoke mprotect to flip the stack to RWX, then reuse the same stack for shellcode, effectively bypassing NX even when only a single syscall; ret gadget is available. Check the dedicated SROP page for more architecture-specific tricks.

Ret2mprotect / ret2syscall to flip permissions

If you can call mprotect, pkey_mprotect, or even dlopen, you can legitimately request an executable mapping before running shellcode. A small pwntools skeleton looks like:

from pwn import *
elf = ELF("./vuln")
rop = ROP(elf)
rop.mprotect(elf.bss(), 0x1000, 7)
payload = flat({offset: rop.chain(), offset+len(rop.chain()): asm(shellcraft.sh())})

The same idea applies to ret2syscall chains that set rax=__NR_mprotect, point rdi to a mmap/.bss page, store the desired length in rsi, and set rdx=7 (PROT_RWX). Once a RWX region exists, execution can safely jump into attacker-controlled bytes.

RWX primitives from JIT engines and kernels

JIT engines, interpreters, GPU drivers, and kernel subsystems that dynamically emit code are a common way to regain executable memory even under strict NX policies. The 2024 Linux kernel vulnerability CVE-2024-42067 showed that failures in set_memory_rox() left eBPF JIT pages writable and executable, letting attackers spray gadgets or entire shellcode blobs inside the kernel despite NX/W^X expectations. Exploits that gain control over a JIT compiler (BPF, JavaScript, Lua, etc.) can therefore arrange for their payload to live in those RWX arenas and only need a single function pointer overwrite to jump into them.

Non-return code reuse (JOP/COP)

If ret instructions are hardened (e.g., CET/IBT) or the binary lacks expressive ret gadgets, pivot to Jump-Oriented Programming (JOP) or Call-Oriented Programming (COP). These techniques build dispatchers that use jmp [reg] or call [reg] sequences found in the binary or loaded libraries. They still respect NX because they reuse existing executable code, but they sidestep mitigations that specifically watch for large chains of ret instructions.

ROP & JOP

References

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks