Relro

Reading time: 7 minutes

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 steht für Relocation Read-Only und ist eine Minderung, die vom Linker (ld) implementiert wird, der einen Teil der ELF-Datensegmente nach Anwendung aller Relokationen schreibgeschützt macht. Das Ziel ist es, einen Angreifer daran zu hindern, Einträge in der GOT (Global Offset Table) oder anderen relokationsbezogenen Tabellen zu überschreiben, die während der Programmausführung dereferenziert werden (z. B. __fini_array).

Moderne Linker implementieren RELRO, indem sie die GOT (und einige andere Abschnitte) neu anordnen, sodass sie vor der .bss leben und – am wichtigsten – indem sie ein dediziertes PT_GNU_RELRO-Segment erstellen, das direkt nach Abschluss der Relokationen durch den dynamischen Loader R–X umgemappt wird. Folglich können typische Pufferüberläufe in der .bss die GOT nicht mehr erreichen, und willkürliche Schreibprimitive können nicht verwendet werden, um Funktionszeiger zu überschreiben, die sich innerhalb einer RELRO-geschützten Seite befinden.

Es gibt zwei Ebenen des Schutzes, die der Linker ausgeben kann:

Partial RELRO

  • Produziert mit dem Flag -Wl,-z,relro (oder einfach -z relro, wenn ld direkt aufgerufen wird).
  • Nur der nicht-PLT Teil der GOT (der Teil, der für Datenrelokationen verwendet wird) wird in das schreibgeschützte Segment gelegt. Abschnitte, die zur Laufzeit geändert werden müssen – am wichtigsten .got.plt, das lazy binding unterstützt – bleiben beschreibbar.
  • Aufgrund dessen kann ein willkürliches Schreiben immer noch den Ausführungsfluss umleiten, indem ein PLT-Eintrag überschrieben wird (oder durch Ausführen von ret2dlresolve).
  • Der Leistungsimpact ist vernachlässigbar und daher versenden fast alle Distributionen seit Jahren Pakete mit mindestens Partial RELRO (es ist der GCC/Binutils-Standard seit 2016).

Full RELRO

  • Produziert mit beiden Flags -Wl,-z,relro,-z,now (auch bekannt als -z relro -z now). -z now zwingt den dynamischen 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 gesamte GOT, .got.plt, .fini_array, .init_array, .preinit_array und einige zusätzliche interne glibc-Tabellen landen in einem schreibgeschützten PT_GNU_RELRO-Segment.
  • Fügt messbare Startkosten hinzu (alle dynamischen Relokationen werden beim Start verarbeitet), aber keine Laufzeitkosten.

Seit 2023 haben mehrere gängige Distributionen damit begonnen, die System-Toolchain (und die meisten Pakete) standardmäßig mit Full RELRO zu kompilieren – z. B. Debian 12 “bookworm” (dpkg-buildflags 13.0.0) und Fedora 35+. Als Pentester sollten Sie daher erwarten, auf Binärdateien zu stoßen, bei denen jeder GOT-Eintrag schreibgeschützt ist.


So überprüfen Sie den RELRO-Status einer Binärdatei

bash
$ 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) analysiert ELF-Header und gibt das Schutzniveau aus. Wenn Sie checksec nicht verwenden können, verlassen Sie sich auf 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

Wenn die Binärdatei läuft (z. B. ein set-uid root Helper), können Sie die ausführbare Datei weiterhin über /proc/$PID/exe inspizieren:

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

Aktivieren von RELRO beim Kompilieren Ihres eigenen Codes

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

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

Bypass-Techniken

RELRO-StufeTypische PrimitiveMögliche Ausnutzungstechniken
Keine / TeilweiseArbiträres Schreiben1. Überschreiben des .got.plt-Eintrags und Ausführung umschalten.
2. ret2dlresolve – gefälschte Elf64_Rela & Elf64_Sym in einem beschreibbaren Segment erstellen und _dl_runtime_resolve aufrufen.
3. Funktionszeiger in .fini_array / atexit()-Liste überschreiben.
VollGOT ist schreibgeschützt1. Nach anderen beschreibbaren Codezeigern suchen (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, Rückrufe in benutzerdefinierten .data-Sektionen, JIT-Seiten).
2. Missbrauch von relativen Lese-Primitiven, um libc auszulesen und SROP/ROP in libc durchzuführen.
3. Ein bösartiges Shared Object über DT_RPATH/LD_PRELOAD injizieren (wenn die Umgebung vom Angreifer kontrolliert wird) oder ld_audit.
4. Format-String oder teilweise Zeigerüberschreibung ausnutzen, um den Kontrollfluss umzuleiten, ohne die GOT zu berühren.

💡 Selbst mit Voll-RELRO ist die GOT von geladenen Shared Libraries (z.B. libc selbst) nur Teilweise RELRO, da diese Objekte bereits gemappt sind, wenn der Loader die Relokationen anwendet. Wenn Sie ein arbiträres Schreiben-Primitive erhalten, das auf die Seiten eines anderen Shared Objects abzielt, können Sie die Ausführung weiterhin umschalten, indem Sie die GOT-Einträge von libc oder den __rtld_global-Stack überschreiben, eine Technik, die regelmäßig in modernen CTF-Herausforderungen ausgenutzt wird.

Beispiel für einen echten Bypass (2024 CTF – pwn.college “enlightened”)

Die Herausforderung wurde mit Voll-RELRO ausgeliefert. Der Exploit nutzte ein Off-by-One, um die Größe eines Heap-Chunks zu korrumpieren, leakte libc mit tcache poisoning und überschreibt schließlich __free_hook (außerhalb des RELRO-Segments) mit einem One-Gadget, um Codeausführung zu erhalten. Es war kein GOT-Schreiben erforderlich.


Aktuelle Forschung & Schwachstellen (2022-2025)

  • glibc 2.40 deprecates __malloc_hook / __free_hook (2025) – Die meisten modernen Heap-Exploits, die diese Symbole ausnutzten, müssen nun auf alternative Vektoren wie rtld_global._dl_load_jump oder C++-Ausnahmetabellen umschalten. Da Hooks außerhalb von RELRO leben, erhöht ihre Entfernung die Schwierigkeit von Voll-RELRO-Bypässen.
  • Binutils 2.41 “max-page-size” Fix (2024) – Ein Fehler erlaubte es, dass die letzten paar Bytes des RELRO-Segments eine Seite mit beschreibbaren Daten auf einigen ARM64-Bauten teilten, was eine kleine RELRO-Lücke hinterließ, die nach mprotect geschrieben werden konnte. Der Upstream richtet jetzt PT_GNU_RELRO an Seitengrenzen aus, wodurch diesen Randfall beseitigt wird.

Referenzen

  • Binutils-Dokumentation – -z relro, -z now und PT_GNU_RELRO
  • “RELRO – Voll, Teilweise und Bypass-Techniken” – Blogbeitrag @ wolfslittlered 2023

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