From a854d4271e00429640d25b03cc1f0c75b48bcc1e Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 17 Jul 2025 00:13:11 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/common-binary-protections-and-bypas --- .../relro.md | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md index 9acc7c7f9..019bdd8a5 100644 --- a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md +++ b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md @@ -4,30 +4,96 @@ ## Relro -**RELRO** sta per **Relocation Read-Only** ed è una funzione di sicurezza utilizzata nei binari per mitigare i rischi associati con le sovrascritture della **GOT (Global Offset Table)**. Ci sono due tipi di protezioni **RELRO**: (1) **Partial RELRO** e (2) **Full RELRO**. Entrambi riordinano la **GOT** e la **BSS** dai file ELF, ma con risultati e implicazioni diverse. Specificamente, pongono la sezione **GOT** _prima_ della **BSS**. Cioè, **GOT** si trova a indirizzi più bassi rispetto alla **BSS**, rendendo quindi impossibile sovrascrivere le voci della **GOT** sovrascrivendo le variabili nella **BSS** (ricorda che la scrittura in memoria avviene da indirizzi più bassi verso indirizzi più alti). +**RELRO** sta per **Relocation Read-Only** ed è una mitigazione implementata dal linker (`ld`) che rende un sottoinsieme dei segmenti di dati ELF **sola lettura dopo che tutte le relocazioni sono state applicate**. L'obiettivo è fermare un attaccante dall'overwriting delle voci nella **GOT (Global Offset Table)** o in altre tabelle relative alle relocazioni che vengono dereferenziate durante l'esecuzione del programma (ad es. `__fini_array`). -Analizziamo il concetto nei suoi due tipi distinti per chiarezza. +I linker moderni implementano RELRO **riordinando** la **GOT** (e alcune altre sezioni) in modo che vivano **prima** della **.bss** e – cosa più importante – creando un segmento dedicato `PT_GNU_RELRO` che viene rimappato `R–X` subito dopo che il caricatore dinamico ha finito di applicare le relocazioni. Di conseguenza, i tipici buffer overflow nella **.bss** non possono più raggiungere la GOT e le primitive di scrittura arbitraria non possono essere utilizzate per sovrascrivere i puntatori di funzione che si trovano all'interno di una pagina protetta da RELRO. -### **Partial RELRO** +Ci sono **due livelli** di protezione che il linker può emettere: -**Partial RELRO** adotta un approccio più semplice per migliorare la sicurezza senza influire significativamente sulle prestazioni del binario. Partial RELRO rende **la .got di sola lettura (la parte non-PLT della sezione GOT)**. Tieni presente che il resto della sezione (come la .got.plt) è ancora scrivibile e, quindi, soggetto ad attacchi. Questo **non impedisce alla GOT** di essere abusata **da vulnerabilità di scrittura arbitraria**. +### Partial RELRO -Nota: Per impostazione predefinita, GCC compila i binari con Partial RELRO. +* Prodotto con il flag `-Wl,-z,relro` (o semplicemente `-z relro` quando si invoca `ld` direttamente). +* Solo la parte **non-PLT** della **GOT** (la parte utilizzata per le relocazioni dei dati) viene messa nel segmento di sola lettura. Le sezioni che devono essere modificate a runtime – in particolare **.got.plt** che supporta il **lazy binding** – rimangono scrivibili. +* Per questo motivo, una primitive di **scrittura arbitraria** può ancora reindirizzare il flusso di esecuzione sovrascrivendo un'entrata PLT (o eseguendo **ret2dlresolve**). +* L'impatto sulle prestazioni è trascurabile e quindi **quasi ogni distribuzione ha spedito pacchetti con almeno Partial RELRO per anni (è il default di GCC/Binutils dal 2016)**. -### **Full RELRO** +### Full RELRO -**Full RELRO** aumenta la protezione rendendo **l'intera GOT (sia .got che .got.plt) e la sezione .fini_array** completamente **di sola lettura.** Una volta che il binario inizia, tutti gli indirizzi delle funzioni vengono risolti e caricati nella GOT, quindi, la GOT viene contrassegnata come di sola lettura, prevenendo efficacemente qualsiasi modifica durante l'esecuzione. +* Prodotto con **entrambi** i flag `-Wl,-z,relro,-z,now` (alias `-z relro -z now`). `-z now` costringe il caricatore dinamico a risolvere **tutti** i simboli in anticipo (eager binding) in modo che **.got.plt** non debba mai essere riscritta e possa essere mappata in modo sicuro come sola lettura. +* L'intera **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** e alcune tabelle interne aggiuntive di glibc finiscono all'interno di un segmento `PT_GNU_RELRO` di sola lettura. +* Aggiunge un sovraccarico misurabile all'avvio (tutte le relocazioni dinamiche vengono elaborate all'avvio) ma **nessun sovraccarico a runtime**. -Tuttavia, il compromesso con Full RELRO è in termini di prestazioni e tempo di avvio. Poiché deve risolvere tutti i simboli dinamici all'avvio prima di contrassegnare la GOT come di sola lettura, **i binari con Full RELRO abilitato potrebbero sperimentare tempi di caricamento più lunghi**. Questo sovraccarico aggiuntivo all'avvio è il motivo per cui Full RELRO non è abilitato per impostazione predefinita in tutti i binari. +Dal 2023 diverse distribuzioni principali hanno iniziato a compilare la **tool-chain di sistema** (e la maggior parte dei pacchetti) con **Full RELRO per default** – ad es. **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** e **Fedora 35+**. Come pentester, dovresti quindi aspettarti di incontrare binari in cui **ogni voce GOT è di sola lettura**. -È possibile vedere se Full RELRO è **abilitato** in un binario con: +--- + +## Come controllare lo stato RELRO di un binario ```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` (parte di [pwntools](https://github.com/pwncollege/pwntools) e molte distribuzioni) analizza gli header `ELF` e stampa il livello di protezione. Se non puoi usare `checksec`, fai affidamento su `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 -Se il Full RELRO è abilitato, l'unico modo per bypassarlo è trovare un altro modo che non richieda di scrivere nella tabella GOT per ottenere un'esecuzione arbitraria. +```bash +# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag +$ readelf -d ./vuln | grep BIND_NOW +0x0000000000000010 (FLAGS) FLAGS: BIND_NOW +``` +Se il binario è in esecuzione (ad esempio, un helper set-uid root), puoi comunque ispezionare l'eseguibile **via `/proc/$PID/exe`**: +```bash +readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO +``` +--- -Nota che **il GOT di LIBC è solitamente Partial RELRO**, quindi può essere modificato con una scrittura arbitraria. Maggiori informazioni in [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.** +## Abilitare RELRO durante la compilazione del proprio codice +```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` funziona sia per **GCC/clang** (passato dopo `-Wl,`) che per **ld** direttamente. Quando si utilizza **CMake 3.18+** è possibile richiedere Full RELRO con il preset integrato: +```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") +``` +--- + +## Tecniche di Bypass + +| Livello RELRO | Primitiva tipica | Tecniche di sfruttamento possibili | +|---------------|------------------|-------------------------------------| +| Nessuno / Parziale | Scrittura arbitraria | 1. Sovrascrivere l'entrata di **.got.plt** e cambiare l'esecuzione.
2. **ret2dlresolve** – creare un falso `Elf64_Rela` & `Elf64_Sym` in un segmento scrivibile e chiamare `_dl_runtime_resolve`.
3. Sovrascrivere puntatori di funzione in **.fini_array** / lista **atexit()**. | +| Completo | GOT è di sola lettura | 1. Cercare **altri puntatori di codice scrivibili** (vtables C++, `__malloc_hook` < glibc 2.34, `__free_hook`, callback in sezioni `.data` personalizzate, pagine JIT).
2. Abusare delle primitività *lettura relativa* per rivelare libc e eseguire **SROP/ROP in libc**.
3. Iniettare un oggetto condiviso malevolo tramite **DT_RPATH**/`LD_PRELOAD` (se l'ambiente è controllato dall'attaccante) o **`ld_audit`**.
4. Sfruttare **format-string** o sovrascrittura parziale di puntatori per deviare il flusso di controllo senza toccare il GOT. | + +> 💡 Anche con Full RELRO il **GOT delle librerie condivise caricate (ad es. libc stessa)** è **solo RELRO Parziale** perché quegli oggetti sono già mappati quando il loader applica le rilocazioni. Se ottieni una primitiva di **scrittura arbitraria** che può mirare alle pagine di un altro oggetto condiviso, puoi comunque cambiare l'esecuzione sovrascrivendo le entrate del GOT di libc o lo stack di `__rtld_global`, una tecnica regolarmente sfruttata nelle moderne sfide CTF. + +### Esempio di bypass nel mondo reale (2024 CTF – *pwn.college “illuminato”*) + +La sfida è stata fornita con Full RELRO. Lo sfruttamento ha utilizzato un **off-by-one** per corrompere la dimensione di un chunk di heap, ha rivelato libc con `tcache poisoning`, e infine ha sovrascritto `__free_hook` (fuori dal segmento RELRO) con un one-gadget per ottenere l'esecuzione di codice. Non è stata necessaria alcuna scrittura nel GOT. + +--- + +## Ricerche recenti & vulnerabilità (2022-2025) + +* **glibc 2.40 deprecates `__malloc_hook` / `__free_hook` (2025)** – La maggior parte degli exploit moderni dell'heap che abusavano di questi simboli devono ora passare a vettori alternativi come **`rtld_global._dl_load_jump`** o tabelle di eccezione C++. Poiché i ganci vivono **fuori** dal RELRO, la loro rimozione aumenta la difficoltà dei bypass Full-RELRO. +* **Fix “max-page-size” di Binutils 2.41 (2024)** – Un bug consentiva agli ultimi byte del segmento RELRO di condividere una pagina con dati scrivibili su alcune build ARM64, lasciando un piccolo **gap RELRO** che poteva essere scritto dopo `mprotect`. L'upstream ora allinea `PT_GNU_RELRO` ai confini delle pagine, eliminando quel caso limite. + +--- + +## Riferimenti + +* Documentazione di Binutils – *`-z relro`, `-z now` e `PT_GNU_RELRO`* +* *“RELRO – Tecniche Complete, Parziali e di Bypass”* – post del blog @ wolfslittlered 2023 {{#include ../../banners/hacktricks-training.md}}