Relro

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

Relro

RELRO stands for Relocation Read-Only and it is a mitigation implemented by the linker (ld) that turns a subset of the ELF’s data segments read-only after all relocations have been applied. The goal is to stop an attacker from overwriting entries in the GOT (Global Offset Table) or other relocation-related tables that are dereferenced during program execution (e.g. __fini_array).

I linker moderni implementano RELRO riordinando la GOT (e alcune altre sezioni) in modo che si trovino prima della .bss e — cosa più importante — creando un segmento dedicato PT_GNU_RELRO che viene rimappato R–X subito dopo che il loader 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 usate per sovrascrivere i puntatori a funzione che risiedono 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 invoca ld direttamente).
  • Solo la parte non-PLT della GOT (la parte usata per le relocazioni dati) viene inserita nel segmento sola-lettura. Le sezioni che devono essere modificate a run-time – soprattutto .got.plt che supporta il lazy binding – rimangono scrivibili.
  • Per questo motivo, una primitiva di scrittura arbitraria può ancora reindirizzare il flusso di esecuzione sovrascrivendo una voce PLT (o eseguendo ret2dlresolve).
  • L’impatto sulle prestazioni è trascurabile e quindi quasi tutte le distributioni distribuiscono pacchetti con almeno Partial RELRO da anni (è il default di GCC/Binutils dal 2016).

Full RELRO

  • Prodotto con entrambi i flag -Wl,-z,relro,-z,now (a.k.a. -z relro -z now). -z now forza il loader dinamico a risolvere tutti i simboli all’avvio (eager binding) in modo che .got.plt non debba più essere scritto e possa essere mappato in sola-lettura in sicurezza.
  • 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 mappato in sola-lettura.
  • Aggiunge un sovraccarico misurabile allo start-up (tutte le relocazioni dinamiche vengono processate al lancio) ma nessun overhead a run-time.

Dal 2023 diverse distributioni mainstream hanno iniziato a compilare la system tool-chain (e la maggior parte dei pacchetti) con Full RELRO by 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 della GOT è in sola-lettura.


Come verificare 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 le intestazioni ELF e stampa il livello di protezione. Se non puoi usare checksec, affidati a 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 es. un set-uid root helper), puoi comunque ispezionare l’eseguibile via /proc/$PID/exe:

readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO

Abilitare RELRO quando si compila il 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 con GCC/clang (passato dopo -Wl,) sia con ld direttamente. Quando si usa CMake 3.18+ puoi 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

RELRO levelTypical primitivePossible exploitation techniques
None / PartialArbitrary write1. Sovrascrivere la voce .got.plt e pivotare l’esecuzione.
2. ret2dlresolve – creare finti Elf64_Rela & Elf64_Sym in un segmento scrivibile e chiamare _dl_runtime_resolve.
3. Sovrascrivere puntatori a funzione in .fini_array / lista atexit().
FullGOT is read-only1. Cercare altri puntatori di codice scrivibili (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacks in custom .data sections, JIT pages).
2. Abusare di primitive relative read per effettuare un leak di libc ed eseguire SROP/ROP into libc.
3. Iniettare un shared object malevolo via DT_RPATH/LD_PRELOAD (se l’ambiente è controllato dall’attaccante) o ld_audit.
4. Sfruttare format-string o partial pointer overwrite per deviare il controllo del flusso senza toccare la GOT.

💡 Anche con Full RELRO la GOT delle shared libraries caricate (p.es. libc stessa) è solo Partial RELRO perché quegli oggetti sono già mappati quando il loader applica le relocazioni. Se ottieni una primitiva di arbitrary write che può puntare alle pagine di un altro shared object puoi comunque pivotare l’esecuzione sovrascrivendo le voci GOT di libc o lo stack __rtld_global, una tecnica regolarmente sfruttata nelle moderne challenge CTF.

Esempio reale di bypass (2024 CTF – pwn.college “enlightened”)

La challenge è stata fornita con Full RELRO. L’exploit ha usato un off-by-one per corrompere la size di un heap chunk, ha leakato libc con tcache poisoning, e alla fine ha sovrascritto __free_hook (fuori dal segmento RELRO) con un one-gadget per ottenere esecuzione di codice. Non è stata necessaria alcuna scrittura sulla GOT.


Ricerche recenti e vulnerabilità (2022-2025)

  • glibc hook removal (2.34 → present) – i malloc/free hooks sono stati estratti dal main libc in libc_malloc_debug.so opzionale, eliminando una comune primitiva di bypass per Full‑RELRO; gli exploit moderni devono mirare ad altri puntatori scrivibili.
  • GNU ld RELRO page‑alignment fix (binutils 2.39+/2.41) – il linker bug 30612 faceva sì che gli ultimi byte di PT_GNU_RELRO condividessero una pagina scrivibile sui sistemi con pagine da 64 KiB; le versioni attuali di binutils allineano RELRO a max-page-size, chiudendo quel “RELRO gap”.

Riferimenti

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