100 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `RX` 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.<br>2. **ret2dlresolve** gefälschte `Elf64_Rela` & `Elf64_Sym` in einem beschreibbaren Segment erstellen und `_dl_runtime_resolve` aufrufen.<br>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).<br>2. Missbrauch von *relativen Lese*-Primitiven, um libc auszulesen und **SROP/ROP in libc** durchzuführen.<br>3. Ein bösartiges Shared Object über **DT_RPATH**/`LD_PRELOAD` injizieren (wenn die Umgebung vom Angreifer kontrolliert wird) oder **`ld_audit`**.<br>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}}