Translated ['src/binary-exploitation/common-binary-protections-and-bypas

This commit is contained in:
Translator 2025-07-17 00:13:18 +00:00
parent 74edd3ea3a
commit 490f1140ab

View File

@ -4,22 +4,96 @@
## Relro
**RELRO** steht für **Relocation Read-Only** und ist eine Sicherheitsfunktion, die in Binärdateien verwendet wird, um die Risiken im Zusammenhang mit **GOT (Global Offset Table)**-Überschreibungen zu mindern. Es gibt zwei Arten von **RELRO**-Schutz: (1) **Partial RELRO** und (2) **Full RELRO**. Beide ordnen die **GOT** und **BSS** aus ELF-Dateien neu, jedoch mit unterschiedlichen Ergebnissen und Auswirkungen. Genauer gesagt, platzieren sie den **GOT**-Abschnitt _vor_ der **BSS**. Das bedeutet, dass **GOT** an niedrigeren Adressen als **BSS** liegt, wodurch es unmöglich wird, **GOT**-Einträge durch Überlaufvariablen in der **BSS** zu überschreiben (denken Sie daran, dass das Schreiben in den Speicher von niedrigeren zu höheren Adressen erfolgt).
**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`).
Lassen Sie uns das Konzept in seine beiden unterschiedlichen Typen zur Klarheit aufschlüsseln.
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.
### **Partial RELRO**
Es gibt **zwei Ebenen** des Schutzes, die der Linker ausgeben kann:
**Partial RELRO** verfolgt einen einfacheren Ansatz zur Verbesserung der Sicherheit, ohne die Leistung der Binärdatei erheblich zu beeinträchtigen. Partial RELRO macht **die .got schreibgeschützt (den nicht-PLT-Teil des GOT-Abschnitts)**. Beachten Sie, dass der Rest des Abschnitts (wie die .got.plt) weiterhin beschreibbar ist und daher Angriffen ausgesetzt ist. Dies **verhindert nicht, dass die GOT** durch **willkürliche Schreib**-Schwachstellen missbraucht wird.
### Partial RELRO
Hinweis: Standard
* 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
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
$ 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
```
## Bypass
Wenn Full RELRO aktiviert ist, besteht der einzige Weg, es zu umgehen, darin, einen anderen Weg zu finden, der nicht benötigt, in die GOT-Tabelle zu schreiben, um willkürliche Ausführung zu erhalten.
```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
```
---
Beachten Sie, dass **LIBC's GOT normalerweise Partial RELRO ist**, sodass es mit einem willkürlichen Schreibvorgang modifiziert werden kann. Weitere Informationen in [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
## 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}}