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 stands for Relocation Read-Only i jest to mitigacja zaimplementowana przez linker (ld), która ustawia podzbiór segmentów danych ELF jako tylko do odczytu po zastosowaniu wszystkich relokacji. Celem jest uniemożliwienie atakującemu nadpisania wpisów w GOT (Global Offset Table) lub innych tabel związanych z relokacjami, które są dereferencjonowane podczas wykonywania programu (np. __fini_array).

Nowoczesne linkery implementują RELRO poprzez ponowne uporządkowanie 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 remapowany jako R–X zaraz po tym, jak dynamic loader zakończy stosowanie relokacji. W konsekwencji typowe buffer overflows w .bss nie mogą już dotrzeć do GOT, a arbitrary‐write primitives nie mogą być użyte do nadpisania wskaźników funkcji znajdujących się na stronie chronionej przez RELRO.

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

Partial RELRO

  • Produced with the flag -Wl,-z,relro (or just -z relro when invoking ld directly).
  • Only the non-PLT part of the GOT (the part used for data relocations) is put into the read-only segment. Sections that need to be modified at run-time – most importantly .got.plt which supports lazy binding – remain writable.
  • Because of that, an arbitrary write primitive can still redirect execution flow by overwriting a PLT entry (or by performing ret2dlresolve).
  • The performance impact is negligible and therefore almost every distribution has been shipping packages with at least Partial RELRO for years (it is the GCC/Binutils default as of 2016).

Full RELRO

  • Produced with both flags -Wl,-z,relro,-z,now (a.k.a. -z relro -z now). -z now forces the dynamic loader to resolve all symbols up-front (eager binding) so that .got.plt never needs to be written again and can safely be mapped read-only.
  • The entire GOT, .got.plt, .fini_array, .init_array, .preinit_array and a few additional internal glibc tables end up inside a read-only PT_GNU_RELRO segment.
  • Adds measurable start-up overhead (all dynamic relocations are processed at launch) but no run-time overhead.

Since 2023 several mainstream distributions have switched to compiling the system tool-chain (and most packages) with Full RELRO by default – e.g. Debian 12 “bookworm” (dpkg-buildflags 13.0.0) and Fedora 35+. As a pentester you should therefore expect to encounter binaries where every GOT entry is read-only.


How to Check the RELRO status of a binary

$ 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 wypisuje poziom ochrony. Jeśli nie możesz użyć checksec, polegaj na readelf:

# 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
# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
$ readelf -d ./vuln | grep BIND_NOW
0x0000000000000010 (FLAGS)              FLAGS: BIND_NOW

Jeśli binary jest uruchomiony (np. set-uid root helper), nadal możesz zbadać plik wykonywalny przez /proc/$PID/exe:

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

Włączanie RELRO podczas kompilacji własnego kodu

# 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 (przekazywane po -Wl,) jak i bezpośrednio dla ld. Przy użyciu CMake 3.18+ możesz wymusić Full RELRO za pomocą wbudowanego presetu:

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 obejścia

RELRO levelTypical primitivePossible exploitation techniques
None / PartialArbitrary write1. Nadpisz .got.plt entry i przekieruj wykonanie.
2. ret2dlresolve – stwórz fałszywe Elf64_Rela & Elf64_Sym w zapisywalnym segmencie i wywołaj _dl_runtime_resolve.
3. Nadpisz wskaźniki funkcji w .fini_array / liście atexit().
FullGOT is read-only1. Szukaj other writable code pointers (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacki w niestandardowych sekcjach .data, strony JIT).
2. Wykorzystaj prymitywy relative read do leak libc i przeprowadź SROP/ROP into libc.
3. Wstrzyknij złośliwy shared object przez 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 przekierować przepływ sterowania bez dotykania GOT.

💡 Nawet przy Full RELRO GOT załadowanych bibliotek współdzielonych (np. libc) jest tylko Partial RELRO, ponieważ te obiekty są już zmapowane, gdy loader stosuje relokacje. Jeśli zdobędziesz prymityw arbitrary write, który może celować w strony innego obiektu współdzielonego, nadal możesz przekierować wykonanie przez nadpisanie wpisów GOT libc lub stosu __rtld_global — technika regularnie wykorzystywana w nowoczesnych zadaniach CTF.

Real-world bypass example (2024 CTF – pwn.college “enlightened”)

The challenge shipped with Full RELRO. The exploit used an off-by-one to corrupt the size of a heap chunk, leaked libc with tcache poisoning, and finally overwrote __free_hook (outside of the RELRO segment) with a one-gadget to get code execution. No GOT write was required.


Najnowsze badania i luki (2022-2025)

  • glibc hook removal (2.34 → present) – malloc/free hooks zostały przeniesione z głównego libc do opcjonalnego libc_malloc_debug.so, eliminując powszechny prymityw obejścia Full‑RELRO; współczesne exploity muszą celować w inne zapisywalne wskaźniki.
  • GNU ld RELRO page‑alignment fix (binutils 2.39+/2.41) – linker bug 30612 powodował, że ostatnie bajty PT_GNU_RELRO dzieliły zapisywalną stronę w systemach ze stroną 64 KiB; obecne binutils wyrównują RELRO do max-page-size, zamykając tę „RELRO gap”.

References

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