# Relro {{#include ../../banners/hacktricks-training.md}} ## 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](https://github.com/pwncollege/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 RELRO | Typowy prymityw | Możliwe techniki eksploatacji | |--------------|------------------|-------------------------------| | Brak / Częściowy | Dowolny zapis | 1. 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łny | GOT jest tylko do odczytu | 1. 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 {{#include ../../banners/hacktricks-training.md}}