# Relro {{#include ../../banners/hacktricks-training.md}} ## 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](https://github.com/pwncollege/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-Stufe | Typische Primitive | Mögliche Ausnutzungstechniken | |-------------|-------------------|----------------------------------| | Keine / Teilweise | Arbiträres Schreiben | 1. Ü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. | | Voll | GOT ist schreibgeschützt | 1. 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 {{#include ../../banners/hacktricks-training.md}}