Relro
Reading time: 7 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Relro
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
).
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.
Ci sono due livelli di protezione che il linker può emettere:
Partial RELRO
- Prodotto con il flag
-Wl,-z,relro
(o semplicemente-z relro
quando si invocald
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
- 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.
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.
Come controllare lo stato RELRO di un binario
$ 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 e molte distribuzioni) analizza gli header ELF
e stampa il livello di protezione. Se non puoi usare checksec
, fai affidamento su readelf
:
# 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
# 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
:
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
Abilitare RELRO durante la compilazione del proprio codice
# 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:
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 comertld_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 allineaPT_GNU_RELRO
ai confini delle pagine, eliminando quel caso limite.
Riferimenti
- Documentazione di Binutils –
-z relro
,-z now
ePT_GNU_RELRO
- “RELRO – Tecniche Complete, Parziali e di Bypass” – post del blog @ wolfslittlered 2023
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.