mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Translated ['src/binary-exploitation/common-binary-protections-and-bypas
This commit is contained in:
		
							parent
							
								
									0e312beacd
								
							
						
					
					
						commit
						a854d4271e
					
				@ -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.<br>2. **ret2dlresolve** – creare un falso `Elf64_Rela` & `Elf64_Sym` in un segmento scrivibile e chiamare `_dl_runtime_resolve`.<br>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).<br>2. Abusare delle primitività *lettura relativa* per rivelare libc e eseguire **SROP/ROP in libc**.<br>3. Iniettare un oggetto condiviso malevolo tramite **DT_RPATH**/`LD_PRELOAD` (se l'ambiente è controllato dall'attaccante) o **`ld_audit`**.<br>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}}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user