No-exec / NX

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundlegende Informationen

Das No-Execute (NX) Bit, auch bekannt als Execute Disable (XD) in Intel-Terminologie, ist eine hardwarebasierte Sicherheitsfunktion, die dazu entwickelt wurde, die Auswirkungen von buffer overflow-Angriffen zu mildern. Wenn implementiert und aktiviert, unterscheidet sie zwischen Speicherbereichen, die für ausführbaren Code vorgesehen sind, und solchen, die für data bestimmt sind, wie dem stack und dem heap. Die Kernidee ist, einem Angreifer zu verhindern, bösartigen Code über buffer overflow-Schwachstellen auszuführen, indem er z. B. den bösartigen Code auf dem stack ablegt und den Kontrollfluss darauf lenkt.

Moderne Betriebssysteme setzen NX über die Seitentabellenattribute durch, die die ELF-Programheader untermauern. Zum Beispiel teilt der PT_GNU_STACK Header in Kombination mit den Eigenschaften GNU_PROPERTY_X86_FEATURE_1_SHSTK oder GNU_PROPERTY_X86_FEATURE_1_IBT dem Loader mit, ob der stack RW oder RWX sein sollte. Wenn NX aktiviert ist und das Binary mit einem nicht-ausführbaren stack (-z noexecstack) gelinkt wurde, führt jeder Versuch, die Ausführung in attacker-controlled data pages (stack, heap, mmap’ed buffers, etc.) zu pivotieren, zu einem Fehler, es sei denn, diese Seiten wurden explizit als ausführbar markiert.

NX schnell erkennen

  • checksec --file ./vuln zeigt NX enabled oder NX disabled basierend auf dem GNU_STACK Programheader an.
  • readelf -W -l ./vuln | grep GNU_STACK zeigt die Stack-Berechtigungen; das Vorhandensein eines E-Flags weist darauf hin, dass der stack ausführbar ist. Beispiel:
$ readelf -W -l ./vuln | grep GNU_STACK
GNU_STACK      0x000000 0x000000 0x000000 0x000000 0x000000 RW  0x10
  • execstack -q ./vuln (from prelink) ist beim Audit großer Binary-Sammlungen praktisch, weil es X für Binaries ausgibt, die noch einen executable stack haben.
  • Zur Laufzeit zeigt /proc/<pid>/maps, ob eine Allokation rwx, rw-, r-x usw. ist, was nützlich ist, um JIT engines oder custom allocators zu verifizieren.

Umgehungen

Code-reuse primitives

Es ist möglich, Techniken wie ROP zu nutzen, um diesen Schutz zu umgehen, indem man bereits im Binary vorhandene ausführbare Codeabschnitte ausführt. Typische Chains beinhalten:

  • Ret2libc
  • Ret2syscall
  • Ret2dlresolve wenn das Binary system/execve nicht importiert
  • Ret2csu oder Ret2vdso zum Erzeugen von Syscalls
  • Ret2… — jeder Dispatcher, der es erlaubt, kontrollierten Registerzustand mit vorhandenem ausführbarem Code zu verknüpfen, um Syscalls oder Library-Gadgets aufzurufen.

Der Ablauf ist normalerweise: (1) leak eines code- oder libc-Pointers durch ein info leak, (2) Basen von Funktionen auflösen und (3) eine Chain basteln, die niemals vom Angreifer-kontrollierte ausführbare Bytes benötigt.

Sigreturn Oriented Programming (SROP)

SROP baut ein gefälschtes sigframe auf einer writeable Page auf und pivottet die Ausführung zu sys_rt_sigreturn (oder dem entsprechenden ABI-Äquivalent). Der Kernel stellt dann den konstruierten Kontext wieder her und gewährt sofort volle Kontrolle über alle General-Purpose-Register, rip und eflags. Neuere CTF-Challenges (z. B. die Hostel-Aufgabe in n00bzCTF 2023) zeigen, wie SROP-Chains zuerst mprotect aufrufen, um den Stack auf RWX zu flippen, und dann denselben Stack für Shellcode wiederverwenden — wodurch NX effektiv umgangen wird, selbst wenn nur ein einzelnes syscall; ret-Gadget verfügbar ist. Siehe die dedizierte SROP-Seite für mehr architekturspezifische Tricks: SROP page.

Ret2mprotect / ret2syscall, um Berechtigungen umzuschalten

Wenn Sie mprotect, pkey_mprotect oder sogar dlopen aufrufen können, können Sie legitimerweise eine ausführbare Speicherzuordnung anfordern, bevor Sie shellcode ausführen. Ein kleines pwntools-Skelett sieht so aus:

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())})

Die gleiche Idee gilt für ret2syscall-Ketten, die rax=__NR_mprotect setzen, rdi auf eine mmap/.bss-Seite zeigen, die gewünschte Länge in rsi speichern und rdx=7 (PROT_RWX) setzen. Sobald eine RWX-Region existiert, kann die Ausführung sicher in vom Angreifer kontrollierte Bytes springen.

RWX primitives from JIT engines and kernels

JIT-Engines, Interpreter, GPU-Treiber und Kernel-Subsysteme, die dynamisch Code erzeugen, sind ein häufiger Weg, um ausführbaren Speicher selbst unter strikten NX-Richtlinien zurückzugewinnen. Die 2024 erschienene Linux-Kernel-Schwachstelle CVE-2024-42067 zeigte, dass Fehler in set_memory_rox() eBPF JIT-Seiten sowohl schreibbar als auch ausführbar ließen, wodurch Angreifer Gadgets oder ganze shellcode-Blobs innerhalb des Kernels platzieren konnten, trotz NX/W^X-Erwartungen. Exploits, die Kontrolle über einen JIT-Compiler (BPF, JavaScript, Lua, etc.) erlangen, können daher dafür sorgen, dass ihre Payload in diesen RWX-Bereichen liegt und benötigen nur eine einzige Function-Pointer-Überschreibung, um hineinzuspringen.

Non-return code reuse (JOP/COP)

Wenn ret-Instruktionen gehärtet sind (z. B. CET/IBT) oder das Binary expressive ret-Gadgets vermissen lässt, weiche auf Jump-Oriented Programming (JOP) oder Call-Oriented Programming (COP) aus. Diese Techniken bauen Dispatcher, die jmp [reg]- oder call [reg]-Sequenzen verwenden, die im Binary oder in geladenen Bibliotheken vorhanden sind. Sie respektieren weiterhin NX, weil sie existierenden ausführbaren Code wiederverwenden, umgehen jedoch Mitigations, die speziell nach langen Ketten von ret-Instruktionen suchen.

ROP & JOP

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks