Relro

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

Relro

RELRO stands for Relocation Read-Only and it is a mitigation implemented by the linker (ld) that turns a subset of the ELF’s data segments read-only after all relocations have been applied. The goal is to stop an attacker from overwriting entries in the GOT (Global Offset Table) or other relocation-related tables that are dereferenced during program execution (e.g. __fini_array).

Moderne Linker implementieren RELRO durch Umordnung der GOT (und einiger anderer Sections), sodass sie vor der .bss liegen und – am wichtigsten – durch das Anlegen eines dedizierten PT_GNU_RELRO-Segments, das unmittelbar nachdem der dynamic loader die Relocations angewendet hat als R–X neu gemappt wird. Folglich können typische Buffer Overflows in der .bss die GOT nicht mehr erreichen und arbitrary-write primitives können nicht mehr verwendet werden, um Funktionszeiger zu überschreiben, die innerhalb einer RELRO-geschützten Seite liegen.

Es gibt zwei Schutzstufen, die der Linker ausgeben kann:

Partial RELRO

  • Erzeugt mit dem Flag -Wl,-z,relro (oder einfach -z relro, wenn ld direkt aufgerufen wird).
  • Nur der non-PLT-Teil der GOT (der Teil, der für Datenrelocations verwendet wird) wird in das schreibgeschützte Segment verschoben. Sections, die zur Laufzeit verändert werden müssen – am wichtigsten .got.plt, das lazy binding unterstützt – bleiben schreibbar.
  • Aufgrund dessen kann eine arbitrary write-Primitive die Ausführung immer noch umleiten, z. B. durch Überschreiben eines PLT-Eintrags (oder durch Ausführen von ret2dlresolve).
  • Die Performance-Auswirkung ist vernachlässigbar und deshalb liefert fast jede Distribution seit Jahren Pakete mit mindestens Partial RELRO aus (es ist seit 2016 das GCC/Binutils-Default).

Full RELRO

  • Erzeugt mit beiden Flags -Wl,-z,relro,-z,now (a.k.a. -z relro -z now). -z now zwingt den dynamic loader, alle Symbole im Voraus aufzulösen (eager binding), sodass .got.plt nie wieder geschrieben werden muss und sicher schreibgeschützt gemappt werden kann.
  • Die komplette GOT, .got.plt, .fini_array, .init_array, .preinit_array und einige zusätzliche interne glibc-Tabellen landen innerhalb eines schreibgeschützten PT_GNU_RELRO-Segments.
  • Fügt messbaren Start-Overhead hinzu (alle dynamischen Relocations werden beim Start verarbeitet), verursacht jedoch keinen Laufzeit-Overhead.

Seit 2023 haben mehrere Mainstream-Distributionen begonnen, die system tool-chain (und die meisten Pakete) standardmäßig mit Full RELRO zu kompilieren – e.g. Debian 12 “bookworm” (dpkg-buildflags 13.0.0) und Fedora 35+. Als pentester solltest du daher damit rechnen, auf Binärdateien zu stoßen, bei denen jeder GOT-Eintrag schreibgeschützt ist.


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 (Teil von pwntools und vielen Distributionen) parst ELF-Header und gibt das Schutzniveau aus. Wenn du checksec nicht verwenden kannst, greife auf readelf zurück:

# 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

Wenn das Binary läuft (z. B. ein set-uid root helper), kannst du die ausführbare Datei trotzdem über /proc/$PID/exe inspizieren:

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

RELRO aktivieren beim Kompilieren des eigenen Codes

# 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 funktioniert sowohl mit GCC/clang (übergeben nach -Wl,) als auch direkt mit ld. Wenn Sie CMake 3.18+ verwenden, können Sie Full RELRO mit dem eingebauten Preset anfordern:

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")

Umgehungstechniken

RELRO levelTypical primitivePossible exploitation techniques
None / PartialArbitrary write1. Overwrite .got.plt entry and pivot execution.
2. ret2dlresolve – craft fake Elf64_Rela & Elf64_Sym in a writable segment and call _dl_runtime_resolve.
3. Overwrite function pointers in .fini_array / atexit() list.
FullGOT is read-only1. Look for other writable code pointers (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacks in custom .data sections, JIT pages).
2. Abuse relative read primitives to leak libc and perform SROP/ROP into libc.
3. Inject a rogue shared object via DT_RPATH/LD_PRELOAD (if environment is attacker-controlled) or ld_audit.
4. Exploit format-string or partial pointer overwrite to divert control-flow without touching the GOT.

💡 Selbst bei Full RELRO ist die GOT of loaded shared libraries (e.g. libc itself) nur only Partial RELRO weil diese Objekte bereits gemappt sind, wenn der Loader Relocations anwendet. Wenn du eine arbitrary write-Primitive erhältst, die Seiten eines anderen shared object anvisieren kann, kannst du trotzdem die Ausführung pivotieren, indem du libc’s GOT-Einträge oder den __rtld_global stack überschreibst — eine Technik, die regelmäßig in modernen CTF-Challenges ausgenutzt wird.

Praxisbeispiel (2024 CTF – pwn.college “enlightened”)

Die Challenge wurde mit Full RELRO ausgeliefert. Der Exploit nutzte einen off-by-one, um die Größe eines Heap-Chunks zu korrumpieren, leaked libc mit tcache poisoning, und überschrieb schließlich __free_hook (außerhalb des RELRO-Segments) mit einem one-gadget, um Codeausführung zu erlangen. Kein GOT write war erforderlich.


Aktuelle Forschung & Verwundbarkeiten (2022-2025)

  • glibc hook removal (2.34 → present) – malloc/free hooks wurden aus der main libc in die optionale libc_malloc_debug.so ausgelagert, wodurch ein häufiger Full‑RELRO bypass primitive entfiel; moderne Exploits müssen andere beschreibbare Pointer anvisieren.
  • GNU ld RELRO page‑alignment fix (binutils 2.39+/2.41) – linker bug 30612 verursachte, dass die letzten Bytes von PT_GNU_RELRO auf 64 KiB-Page-Systemen eine beschreibbare Seite teilten; aktuelle binutils richtet RELRO an max-page-size aus und schließt diese “RELRO gap”.

References

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