Relro

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

Relro

RELRO oznacza Relocation Read-Only i jest to środek zaradczy wdrożony przez linker (ld), który zmienia podzbiór segmentów danych ELF na tylko do odczytu po zastosowaniu wszystkich relokacji. Celem jest uniemożliwienie atakującemu nadpisania wpisów w GOT (Global Offset Table) lub innych tabelach związanych z relokacją, które są dereferencjonowane podczas wykonywania programu (np. __fini_array).

Nowoczesne linkery implementują RELRO poprzez przestawienie GOT (i kilku innych sekcji) tak, aby znajdowały się przed .bss i – co najważniejsze – poprzez utworzenie dedykowanego segmentu PT_GNU_RELRO, który jest mapowany jako R–X zaraz po tym, jak dynamiczny loader zakończy stosowanie relokacji. W konsekwencji typowe przepełnienia bufora w .bss nie mogą już dotrzeć do GOT, a prymitywy do nadpisywania nie mogą być używane do nadpisywania wskaźników funkcji, które znajdują się w chronionej stronie RELRO.

Istnieją dwa poziomy ochrony, które linker może wygenerować:

Partial RELRO

  • Wytwarzany z flagą -Wl,-z,relro (lub po prostu -z relro, gdy wywołuje się ld bezpośrednio).
  • Tylko nie-PLT część GOT (część używana do relokacji danych) jest umieszczana w segmencie tylko do odczytu. Sekcje, które muszą być modyfikowane w czasie wykonywania – co najważniejsze .got.plt, która wspiera lazy binding – pozostają zapisywalne.
  • Z tego powodu prymityw arbitrary write może nadal przekierować przepływ wykonania, nadpisując wpis PLT (lub wykonując ret2dlresolve).
  • Wpływ na wydajność jest znikomy i dlatego prawie każda dystrybucja od lat dostarcza pakiety z przynajmniej Partial RELRO (jest to domyślne ustawienie GCC/Binutils od 2016 roku).

Full RELRO

  • Wytwarzany z obu flag -Wl,-z,relro,-z,now (znany również jako -z relro -z now). -z now zmusza dynamiczny loader do rozwiązania wszystkich symboli z góry (eager binding), aby .got.plt nigdy nie musiał być ponownie zapisywany i mógł być bezpiecznie mapowany jako tylko do odczytu.
  • Cały GOT, .got.plt, .fini_array, .init_array, .preinit_array oraz kilka dodatkowych wewnętrznych tabel glibc trafia do segmentu PT_GNU_RELRO tylko do odczytu.
  • Dodaje mierzalny narzut przy uruchamianiu (wszystkie dynamiczne relokacje są przetwarzane przy uruchomieniu), ale nie ma narzutu w czasie wykonywania.

Od 2023 roku kilka głównych dystrybucji przeszło na kompilowanie system tool-chain (i większości pakietów) z Full RELRO domyślnie – np. Debian 12 “bookworm” (dpkg-buildflags 13.0.0) i Fedora 35+. Jako pentester powinieneś zatem spodziewać się napotkania binarnych plików, w których każdy wpis GOT jest tylko do odczytu.


Jak sprawdzić status RELRO binarnego pliku

bash
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch:     amd64-64-little
RELRO:    Full
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

checksec (część pwntools i wielu dystrybucji) analizuje nagłówki ELF i wyświetla poziom ochrony. Jeśli nie możesz użyć checksec, polegaj na readelf:

bash
# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent*
$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW"
GNU_RELRO      0x0000000000600e20 0x0000000000600e20
bash
# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
$ readelf -d ./vuln | grep BIND_NOW
0x0000000000000010 (FLAGS)              FLAGS: BIND_NOW

Jeśli binarny program jest uruchomiony (np. pomocnik z ustawionym uid root), nadal możesz sprawdzić plik wykonywalny via /proc/$PID/exe:

bash
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO

Włączanie RELRO podczas kompilacji własnego kodu

bash
# GCC example – create a PIE with Full RELRO and other common hardenings
$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure

-z relro -z now działa zarówno dla GCC/clang (przekazane po -Wl,), jak i bezpośrednio dla ld. Używając CMake 3.18+ możesz zażądać pełnego RELRO za pomocą wbudowanego preset:

cmake
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO
set(CMAKE_ENABLE_EXPORTS OFF)
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now")

Techniki omijania

Poziom RELROTypowy prymitywMożliwe techniki eksploatacji
Brak / CzęściowyDowolny zapis1. Nadpisanie wpisu .got.plt i zmiana wykonania.
2. ret2dlresolve – stworzenie fałszywego Elf64_Rela i Elf64_Sym w zapisywalnym segmencie i wywołanie _dl_runtime_resolve.
3. Nadpisanie wskaźników funkcji w .fini_array / liście atexit().
PełnyGOT jest tylko do odczytu1. Szukaj innych zapisywalnych wskaźników kodu (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, wywołania zwrotne w niestandardowych sekcjach .data, strony JIT).
2. Wykorzystaj prymitywy relatywnego odczytu do wycieku libc i wykonania SROP/ROP w libc.
3. Wstrzyknij złośliwy obiekt współdzielony za pomocą DT_RPATH/LD_PRELOAD (jeśli środowisko jest kontrolowane przez atakującego) lub ld_audit.
4. Wykorzystaj format-string lub częściowe nadpisanie wskaźnika, aby zmienić przepływ kontroli bez dotykania GOT.

💡 Nawet przy Pełnym RELRO GOT załadowanych bibliotek współdzielonych (np. sama libc) jest tylko Częściowy RELRO, ponieważ te obiekty są już mapowane, gdy loader stosuje relokacje. Jeśli uzyskasz prymityw dowolnego zapisu, który może celować w strony innego obiektu współdzielonego, nadal możesz zmienić wykonanie, nadpisując wpisy GOT libc lub stos __rtld_global, technikę regularnie wykorzystywaną w nowoczesnych wyzwaniach CTF.

Przykład omijania w rzeczywistości (2024 CTF – pwn.college “enlightened”)

Wyzwanie dostarczono z Pełnym RELRO. Eksploatacja wykorzystała off-by-one do uszkodzenia rozmiaru fragmentu sterty, wyciekła libc za pomocą tcache poisoning, a na koniec nadpisała __free_hook (poza segmentem RELRO) za pomocą jednego gadżetu, aby uzyskać wykonanie kodu. Nie było wymagane żadne zapisanie do GOT.


Ostatnie badania i luki (2022-2025)

  • glibc 2.40 deprecjonuje __malloc_hook / __free_hook (2025) – Większość nowoczesnych eksploatacji sterty, które wykorzystywały te symbole, musi teraz przejść do alternatywnych wektorów, takich jak rtld_global._dl_load_jump lub tabele wyjątków C++. Ponieważ haki znajdują się poza RELRO, ich usunięcie zwiększa trudność omijania Pełnego RELRO.
  • Poprawka “max-page-size” w Binutils 2.41 (2024) – Błąd pozwalał ostatnim kilku bajtom segmentu RELRO dzielić stronę z zapisywalnymi danymi w niektórych kompilacjach ARM64, pozostawiając mały RELRO gap, który mógł być zapisany po mprotect. Teraz upstream wyrównuje PT_GNU_RELRO do granic stron, eliminując ten przypadek brzegowy.

Odniesienia

  • Dokumentacja Binutils – -z relro, -z now i PT_GNU_RELRO
  • “RELRO – Pełny, Częściowy i Techniki Omijania” – post na blogu @ wolfslittlered 2023

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