No-exec / NX

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Podstawowe informacje

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.

Nowoczesne systemy operacyjne egzekwują NX za pomocą atrybutów tabeli stron powiązanych z nagłówkami programu ELF. Na przykład nagłówek PT_GNU_STACK w połączeniu z właściwościami GNU_PROPERTY_X86_FEATURE_1_SHSTK lub GNU_PROPERTY_X86_FEATURE_1_IBT informuje loader, czy stack powinien być RW czy RWX. Gdy NX jest włączony, a binarka została zlinkowana z non-executable stack (-z noexecstack), każda próba przekierowania wykonania na strony danych kontrolowanych przez atakującego (stack, heap, mmap’ed buffers, itd.) spowoduje błąd, chyba że strony te zostały jawnie oznaczone jako wykonywalne.

Szybkie wykrywanie NX

  • 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) jest przydatne przy audycie dużych zbiorów binarek, ponieważ wypisuje X dla binarek, które wciąż mają wykonalny stos.
  • W czasie wykonywania /proc/<pid>/maps pokaże, czy przydział ma rwx, rw-, r-x itd., co jest przydatne przy weryfikacji JIT engines lub custom allocators.

Obejścia

Code-reuse primitives

Możliwe jest użycie technik takich jak ROP aby obejść tę ochronę poprzez wykonanie fragmentów wykonalnego kodu już obecnych w binarce. Typowe łańcuchy obejmują:

  • Ret2libc
  • Ret2syscall
  • Ret2dlresolve — gdy binarka nie importuje system/execve
  • Ret2csu lub Ret2vdso — do syntezy syscalls
  • Ret2… — każdy dispatcher, który pozwala poskładać kontrolowany stan rejestrów z istniejącym wykonalnym kodem, aby wywołać syscalls lub library gadgets.

Przebieg zwykle wygląda tak: (1) leak wskaźnika do kodu lub libc przez info leak, (2) rozwiązać bazy funkcji, oraz (3) stworzyć łańcuch, który nigdy nie potrzebuje wykonawczych bajtów kontrolowanych przez atakującego.

Sigreturn Oriented Programming (SROP)

SROP buduje fałszywy sigframe na zapisywalnej stronie i pivotuje wykonanie na sys_rt_sigreturn (lub odpowiednik ABI). Kernel następnie „przywraca” spreparowany kontekst, natychmiast dając pełną kontrolę nad wszystkimi rejestrami ogólnego przeznaczenia, rip i eflags. Najnowsze zadania CTF (np. zadanie Hostel w n00bzCTF 2023) pokazują, jak łańcuchy SROP najpierw wywołują mprotect, aby zmienić stos na RWX, a następnie ponownie używają tego samego stosu do shellcode, efektywnie omijając NX nawet gdy dostępny jest tylko pojedynczy gadget syscall; ret. Sprawdź dedykowaną SROP page po więcej trików specyficznych dla architektury.

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

Ta sama idea ma zastosowanie do łańcuchów ret2syscall, które ustawiają rax=__NR_mprotect, wskazują rdi na stronę mmap/.bss, zapisują żądaną długość w rsi i ustawiają rdx=7 (PROT_RWX). Gdy istnieje region RWX, wykonanie może bezpiecznie skoczyć do bajtów kontrolowanych przez atakującego.

Prymitywy RWX z silników JIT i jądra

Silniki JIT, interpretery, sterowniki GPU i podsystemy jądra, które dynamicznie emitują kod, są powszechnym sposobem na odzyskanie pamięci wykonalnej nawet przy restrykcyjnej polityce NX. Wada jądra Linux z 2024 r. CVE-2024-42067 wykazała, że błędy w set_memory_rox() pozostawiały strony eBPF JIT zapisywalne i wykonywalne, pozwalając atakującym umieścić gadgety lub całe shellcode blobs wewnątrz jądra pomimo oczekiwań NX/W^X. Eksploity, które przejmą kontrolę nad kompilatorem JIT (BPF, JavaScript, Lua itd.), mogą więc umieścić swój payload w tych obszarach RWX i potrzebują tylko jednego function pointer overwrite, aby w nie skoczyć.

Reużycie kodu bez powrotu (JOP/COP)

Jeśli instrukcje ret są wzmocnione (np. CET/IBT) lub binarka nie zawiera bogatych ret gadgetów, przejdź do Jump-Oriented Programming (JOP) lub Call-Oriented Programming (COP). Techniki te budują dyspozytory wykorzystujące sekwencje jmp [reg] lub call [reg] znalezione w binarce lub załadowanych bibliotekach. Nadal respektują NX, ponieważ ponownie używają istniejącego wykonalnego kodu, ale omijają środki łagodzące, które specyficznie monitorują długie łańcuchy instrukcji ret.

ROP & JOP

Referencje

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks