Translated ['src/binary-exploitation/basic-stack-binary-exploitation-met

This commit is contained in:
Translator 2025-08-18 20:37:30 +00:00
parent a07a12dee0
commit 93fecbc389

View File

@ -1,8 +1,8 @@
# Informazioni di base su ELF
# ELF Informazioni di Base
{{#include ../../banners/hacktricks-training.md}}
## Intestazioni del programma
## Intestazioni del Programma
Descrivono al loader come caricare l'**ELF** in memoria:
```bash
@ -37,31 +37,39 @@ Segment Sections...
07
08 .init_array .fini_array .dynamic .got
```
Il programma precedente ha **9 intestazioni di programma**, quindi, la **mappatura dei segmenti** indica in quale intestazione di programma (da 00 a 08) **si trova ciascuna sezione**.
The previous program has **9 program headers**, then, the **segment mapping** indicates in which program header (from 00 to 08) **each section is located**.
### PHDR - Program HeaDeR
Contiene le tabelle delle intestazioni di programma e i metadati stessi.
Contiene le tabelle degli header del programma e i metadati stessi.
### INTERP
Indica il percorso del loader da utilizzare per caricare il binario in memoria.
> Tip: I binari staticamente collegati o static-PIE non avranno un'entrata `INTERP`. In questi casi non è coinvolto alcun loader dinamico, il che disabilita le tecniche che si basano su di esso (ad es., `ret2dlresolve`).
### LOAD
Queste intestazioni vengono utilizzate per indicare **come caricare un binario in memoria.**\
Ogni intestazione **LOAD** indica una regione di **memoria** (dimensione, permessi e allineamento) e indica i byte del binario ELF **da copiare lì**.
Questi header sono utilizzati per indicare **come caricare un binario in memoria.**\
Ogni header **LOAD** indica una regione di **memoria** (dimensione, permessi e allineamento) e indica i byte del binario ELF **da copiare lì**.
Ad esempio, la seconda ha una dimensione di 0x1190, dovrebbe trovarsi a 0x1fc48 con permessi di lettura e scrittura e sarà riempita con 0x528 dall'offset 0xfc48 (non riempie tutto lo spazio riservato). Questa memoria conterrà le sezioni `.init_array .fini_array .dynamic .got .data .bss`.
Ad esempio, il secondo ha una dimensione di 0x1190, dovrebbe trovarsi a 0x1fc48 con permessi di lettura e scrittura e sarà riempito con 0x528 dall'offset 0xfc48 (non riempie tutto lo spazio riservato). Questa memoria conterrà le sezioni `.init_array .fini_array .dynamic .got .data .bss`.
### DYNAMIC
Questa intestazione aiuta a collegare i programmi alle loro dipendenze di libreria e ad applicare le rilocalizzazioni. Controlla la sezione **`.dynamic`**.
Questo header aiuta a collegare i programmi alle loro dipendenze di libreria e ad applicare le rilocalizzazioni. Controlla la sezione **`.dynamic`**.
### NOTE
Questo memorizza informazioni sui metadati del fornitore riguardo al binario.
- Su x86-64, `readelf -n` mostrerà i flag `GNU_PROPERTY_X86_FEATURE_1_*` all'interno di `.note.gnu.property`. Se vedi `IBT` e/o `SHSTK`, il binario è stato costruito con CET (Indirect Branch Tracking e/o Shadow Stack). Questo impatta ROP/JOP perché i target di branch indiretti devono iniziare con un'istruzione `ENDBR64` e i ritorni sono controllati contro uno shadow stack. Vedi la pagina CET per dettagli e note di bypass.
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
### GNU_EH_FRAME
Definisce la posizione delle tabelle di unwind dello stack, utilizzate dai debugger e dalle funzioni di runtime per la gestione delle eccezioni in C++.
@ -70,21 +78,29 @@ Definisce la posizione delle tabelle di unwind dello stack, utilizzate dai debug
Contiene la configurazione della difesa contro l'esecuzione dello stack. Se abilitato, il binario non sarà in grado di eseguire codice dallo stack.
- Controlla con `readelf -l ./bin | grep GNU_STACK`. Per forzare il toggle durante i test puoi usare `execstack -s|-c ./bin`.
### GNU_RELRO
Indica la configurazione RELRO (Relocation Read-Only) del binario. Questa protezione contrassegnerà come di sola lettura alcune sezioni della memoria (come il `GOT` o le tabelle `init` e `fini`) dopo che il programma è stato caricato e prima che inizi a essere eseguito.
Nell'esempio precedente sta copiando 0x3b8 byte a 0x1fc48 come di sola lettura, influenzando le sezioni `.init_array .fini_array .dynamic .got .data .bss`.
Nota che RELRO può essere parziale o completo, la versione parziale non protegge la sezione **`.plt.got`**, che viene utilizzata per il **lazy binding** e ha bisogno di questo spazio di memoria per avere **permessi di scrittura** per scrivere l'indirizzo delle librerie la prima volta che viene cercata la loro posizione.
Nota che RELRO può essere parziale o completo, la versione parziale non protegge la sezione **`.plt.got`**, che è utilizzata per il **lazy binding** e ha bisogno di questo spazio di memoria per avere **permessi di scrittura** per scrivere l'indirizzo delle librerie la prima volta che la loro posizione viene cercata.
> Per tecniche di sfruttamento e note di bypass aggiornate, controlla la pagina dedicata:
{{#ref}}
../common-binary-protections-and-bypasses/relro.md
{{#endref}}
### TLS
Definisce una tabella di voci TLS, che memorizza informazioni sulle variabili locali al thread.
Definisce una tabella di voci TLS, che memorizza informazioni sulle variabili locali del thread.
## Section Headers
Le intestazioni delle sezioni forniscono una visione più dettagliata del binario ELF.
Gli header delle sezioni forniscono una vista più dettagliata del binario ELF.
```
objdump lnstat -h
@ -145,7 +161,7 @@ CONTENTS, READONLY
25 .gnu_debuglink 00000034 0000000000000000 0000000000000000 000101bc 2**2
CONTENTS, READONLY
```
Indica anche la posizione, l'offset, i permessi ma anche il **tipo di dati** che ha la sua sezione.
It indicates anche la posizione, l'offset, i permessi ma anche il **tipo di dati** che ha la sua sezione.
### Sezioni Meta
@ -159,12 +175,12 @@ Indica anche la posizione, l'offset, i permessi ma anche il **tipo di dati** che
- **`.data`**: Variabili globali con un valore definito nel programma.
- **`.bss`**: Variabili globali lasciate non inizializzate (o inizializzate a zero). Le variabili qui sono automaticamente inizializzate a zero, prevenendo quindi l'aggiunta di zeri inutili al binario.
- **`.rodata`**: Variabili globali costanti (sezione di sola lettura).
- **`.tdata`** e **`.tbss`**: Come .data e .bss quando si utilizzano variabili locali al thread (`__thread_local` in C++ o `__thread` in C).
- **`.tdata`** e **`.tbss`**: Come .data e .bss quando si utilizzano variabili locali per thread (`__thread_local` in C++ o `__thread` in C).
- **`.dynamic`**: Vedi sotto.
## Simboli
I simboli sono una posizione nominata nel programma che potrebbe essere una funzione, un oggetto di dati globale, variabili locali al thread...
I simboli sono una posizione nominata nel programma che potrebbe essere una funzione, un oggetto di dati globale, variabili locali per thread...
```
readelf -s lnstat
@ -185,15 +201,19 @@ Num: Value Size Type Bind Vis Ndx Name
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
```
Ogni voce simbolo contiene:
Ogni voce di simbolo contiene:
- **Nome**
- **Attributi di binding** (debole, locale o globale): Un simbolo locale può essere accessibile solo dal programma stesso, mentre i simboli globali sono condivisi al di fuori del programma. Un oggetto debole è, ad esempio, una funzione che può essere sovrascritta da un'altra.
- **Tipo**: NOTYPE (nessun tipo specificato), OBJECT (variabile di dati globale), FUNC (funzione), SECTION (sezione), FILE (file di codice sorgente per debugger), TLS (variabile locale al thread), GNU_IFUNC (funzione indiretta per rilocazione)
- **Indice della sezione** in cui si trova
- **Sezione** indice in cui si trova
- **Valore** (indirizzo in memoria)
- **Dimensione**
#### Versionamento dei simboli GNU (dynsym/dynstr/gnu.version)
La moderna glibc utilizza versioni di simbolo. Vedrai voci in `.gnu.version` e `.gnu.version_r` e nomi di simboli come `strlen@GLIBC_2.17`. Il linker dinamico può richiedere una versione specifica quando risolve un simbolo. Quando si creano rilocazioni manuali (ad es. ret2dlresolve) è necessario fornire l'indice di versione corretto, altrimenti la risoluzione fallisce.
## Sezione Dinamica
```
readelf -d lnstat
@ -231,9 +251,26 @@ Tag Type Name/Value
```
La directory NEEDED indica che il programma **deve caricare la libreria menzionata** per continuare. La directory NEEDED si completa una volta che la **libreria condivisa è completamente operativa e pronta** per l'uso.
## Relocations
### Ordine di ricerca del caricatore dinamico (RPATH/RUNPATH, $ORIGIN)
Il loader deve anche rilocare le dipendenze dopo averle caricate. Queste rilocazioni sono indicate nella tabella di rilocazione nei formati REL o RELA e il numero di rilocazioni è fornito nelle sezioni dinamiche RELSZ o RELASZ.
Le voci `DT_RPATH` (deprecato) e/o `DT_RUNPATH` influenzano dove il caricatore dinamico cerca le dipendenze. Ordine approssimativo:
- `LD_LIBRARY_PATH` (ignorato per programmi setuid/sgid o altrimenti "sicuri")
- `DT_RPATH` (solo se `DT_RUNPATH` assente)
- `DT_RUNPATH`
- `ld.so.cache`
- directory predefinite come `/lib64`, `/usr/lib64`, ecc.
`$ORIGIN` può essere utilizzato all'interno di RPATH/RUNPATH per riferirsi alla directory dell'oggetto principale. Dal punto di vista di un attaccante, questo è importante quando controlli il layout del filesystem o l'ambiente. Per i binari rinforzati (AT_SECURE) la maggior parte delle variabili ambientali sono ignorate dal caricatore.
- Ispeziona con: `readelf -d ./bin | egrep -i 'r(path|unpath)'`
- Test rapido: `LD_DEBUG=libs ./bin 2>&1 | grep -i find` (mostra le decisioni sul percorso di ricerca)
> Suggerimento per l'escamotage: Preferisci abusare di RUNPATH scrivibili o percorsi relativi a `$ORIGIN` mal configurati di tua proprietà. LD_PRELOAD/LD_AUDIT sono ignorati nei contesti di esecuzione sicura (setuid).
## Rilocazioni
Il caricatore deve anche rilocare le dipendenze dopo averle caricate. Queste rilocazioni sono indicate nella tabella di rilocazione nei formati REL o RELA e il numero di rilocazioni è fornito nelle sezioni dinamiche RELSZ o RELASZ.
```
readelf -r lnstat
@ -274,7 +311,6 @@ Offset Info Type Sym. Value Sym. Name + Addend
00000001fea0 000900000402 R_AARCH64_JUMP_SL 0000000000000000 perror@GLIBC_2.17 + 0
00000001fea8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0
00000001feb0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 putc@GLIBC_2.17 + 0
00000001feb8 000d00000402 R_AARCH64_JUMP_SL 0000000000000000 opendir@GLIBC_2.17 + 0
00000001fec0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 fputc@GLIBC_2.17 + 0
00000001fec8 001100000402 R_AARCH64_JUMP_SL 0000000000000000 snprintf@GLIBC_2.17 + 0
00000001fed0 001200000402 R_AARCH64_JUMP_SL 0000000000000000 __snprintf_chk@GLIBC_2.17 + 0
@ -310,7 +346,7 @@ Offset Info Type Sym. Value Sym. Name + Addend
Se il **programma è caricato in un luogo diverso** dall'indirizzo preferito (di solito 0x400000) perché l'indirizzo è già utilizzato o a causa di **ASLR** o per qualsiasi altro motivo, una relocazione statica **corregge i puntatori** che avevano valori che si aspettavano che il binario fosse caricato nell'indirizzo preferito.
Ad esempio, qualsiasi sezione di tipo `R_AARCH64_RELATIV` dovrebbe avere modificato l'indirizzo al bias di relocazione più il valore addend.
Ad esempio, qualsiasi sezione di tipo `R_AARCH64_RELATIV` dovrebbe avere modificato l'indirizzo al bias di relocazione più il valore additivo.
### Dynamic Relocations and GOT
@ -320,11 +356,29 @@ La relocazione potrebbe anche fare riferimento a un simbolo esterno (come una fu
La sezione PLT consente di eseguire il lazy binding, il che significa che la risoluzione della posizione di una funzione verrà eseguita la prima volta che viene accesso.
Quindi, quando un programma chiama malloc, in realtà chiama la posizione corrispondente di `malloc` nel PLT (`malloc@plt`). La prima volta che viene chiamato risolve l'indirizzo di `malloc` e lo memorizza, quindi la prossima volta che viene chiamato `malloc`, quell'indirizzo viene utilizzato invece del codice PLT.
Quindi, quando un programma chiama malloc, in realtà chiama la posizione corrispondente di `malloc` nel PLT (`malloc@plt`). La prima volta che viene chiamato, risolve l'indirizzo di `malloc` e lo memorizza, quindi la prossima volta che viene chiamato `malloc`, viene utilizzato quell'indirizzo invece del codice PLT.
#### Modern linking behaviors that impact exploitation
- `-z now` (Full RELRO) disabilita il lazy binding; le voci PLT esistono ancora ma GOT/PLT è mappato in sola lettura, quindi tecniche come **GOT overwrite** e **ret2dlresolve** non funzioneranno contro il binario principale (le librerie possono ancora essere parzialmente RELRO). Vedi:
{{#ref}}
../common-binary-protections-and-bypasses/relro.md
{{#endref}}
- `-fno-plt` fa sì che il compilatore chiami funzioni esterne attraverso l'**entry GOT direttamente** invece di passare attraverso il PLT stub. Vedrai sequenze di chiamate come `mov reg, [got]; call reg` invece di `call func@plt`. Questo riduce l'abuso di esecuzione speculativa e cambia leggermente la ricerca di gadget ROP attorno agli stub PLT.
- PIE vs static-PIE: PIE (ET_DYN con `INTERP`) necessita del loader dinamico e supporta la consueta meccanica PLT/GOT. Static-PIE (ET_DYN senza `INTERP`) ha relocazioni applicate dal kernel loader e nessun `ld.so`; aspettati nessuna risoluzione PLT a runtime.
> Se GOT/PLT non è un'opzione, pivotare su altri puntatori di codice scrivibili o utilizzare ROP/SROP classico in libc.
{{#ref}}
../arbitrary-write-2-exec/aw2exec-got-plt.md
{{#endref}}
## Program Initialization
Dopo che il programma è stato caricato, è tempo che venga eseguito. Tuttavia, il primo codice che viene eseguito **non è sempre la funzione `main`**. Questo perché, ad esempio, in C++ se una **variabile globale è un oggetto di una classe**, questo oggetto deve essere **inizializzato** **prima** che main venga eseguito, come in:
Dopo che il programma è stato caricato, è tempo di farlo funzionare. Tuttavia, il primo codice che viene eseguito **non è sempre la funzione `main`**. Questo perché, ad esempio, in C++ se una **variabile globale è un oggetto di una classe**, questo oggetto deve essere **inizializzato** **prima** che main venga eseguito, come in:
```cpp
#include <stdio.h>
// g++ autoinit.cpp -o autoinit
@ -345,7 +399,7 @@ printf("Main\n");
return 0;
}
```
Nota che queste variabili globali si trovano in `.data` o `.bss`, ma nelle liste `__CTOR_LIST__` e `__DTOR_LIST__` gli oggetti da inizializzare e distruggere sono memorizzati per tenerne traccia.
Nota che queste variabili globali si trovano in `.data` o `.bss`, ma nelle liste `__CTOR_LIST__` e `__DTOR_LIST__` gli oggetti da inizializzare e distruggere sono memorizzati in ordine per tenerne traccia.
Dal codice C è possibile ottenere lo stesso risultato utilizzando le estensioni GNU:
```c
@ -354,29 +408,66 @@ __attributte__((destructor)) //Add to the destructor list
```
Dal punto di vista di un compilatore, per eseguire queste azioni prima e dopo l'esecuzione della funzione `main`, è possibile creare una funzione `init` e una funzione `fini` che sarebbero referenziate nella sezione dinamica come **`INIT`** e **`FIN`**. e sono collocate nelle sezioni `init` e `fini` dell'ELF.
L'altra opzione, come menzionato, è riferirsi alle liste **`__CTOR_LIST__`** e **`__DTOR_LIST__`** nelle voci **`INIT_ARRAY`** e **`FINI_ARRAY`** nella sezione dinamica e la lunghezza di queste è indicata da **`INIT_ARRAYSZ`** e **`FINI_ARRAYSZ`**. Ogni voce è un puntatore a funzione che verrà chiamato senza argomenti.
L'altra opzione, come menzionato, è fare riferimento alle liste **`__CTOR_LIST__`** e **`__DTOR_LIST__`** nelle voci **`INIT_ARRAY`** e **`FINI_ARRAY`** nella sezione dinamica e la lunghezza di queste è indicata da **`INIT_ARRAYSZ`** e **`FINI_ARRAYSZ`**. Ogni voce è un puntatore a funzione che verrà chiamato senza argomenti.
Inoltre, è anche possibile avere un **`PREINIT_ARRAY`** con **puntatori** che verranno eseguiti **prima** dei puntatori **`INIT_ARRAY`**.
#### Nota di sfruttamento
- Sotto Partial RELRO, questi array vivono in pagine che sono ancora scrivibili prima che `ld.so` cambi `PT_GNU_RELRO` in sola lettura. Se ottieni una scrittura arbitraria abbastanza presto o puoi mirare agli array scrivibili di una libreria, puoi dirottare il flusso di controllo sovrascrivendo un'entrata con una funzione a tua scelta. Sotto Full RELRO sono di sola lettura a runtime.
- Per l'abuso del lazy binding del linker dinamico per risolvere simboli arbitrari a runtime, vedere la pagina dedicata:
{{#ref}}
../rop-return-oriented-programing/ret2dlresolve.md
{{#endref}}
### Ordine di Inizializzazione
1. Il programma viene caricato in memoria, le variabili globali statiche vengono inizializzate in **`.data`** e quelle non inizializzate vengono azzerate in **`.bss`**.
2. Tutte le **dipendenze** per il programma o le librerie vengono **inizializzate** e il **collegamento dinamico** viene eseguito.
3. Le funzioni **`PREINIT_ARRAY`** vengono eseguite.
4. Le funzioni **`INIT_ARRAY`** vengono eseguite.
5. Se c'è una voce **`INIT`**, viene chiamata.
6. Se è una libreria, dlopen termina qui, se è un programma, è il momento di chiamare il **vero punto di ingresso** (funzione `main`).
5. Se c'è un'entrata **`INIT`**, viene chiamata.
6. Se è una libreria, dlopen termina qui, se è un programma, è tempo di chiamare il **punto di ingresso reale** (funzione `main`).
## Memoria Locale per Thread (TLS)
## Thread-Local Storage (TLS)
Sono definiti utilizzando la parola chiave **`__thread_local`** in C++ o l'estensione GNU **`__thread`**.
Ogni thread manterrà una posizione unica per questa variabile, quindi solo il thread può accedere alla sua variabile.
Ogni thread manterrà una posizione unica per questa variabile in modo che solo il thread possa accedere alla sua variabile.
Quando questo viene utilizzato, le sezioni **`.tdata`** e **`.tbss`** vengono utilizzate nell'ELF. Che sono simili a `.data` (inizializzato) e `.bss` (non inizializzato) ma per TLS.
Ogni variabile avrà un'entrata nell'intestazione TLS che specifica la dimensione e l'offset TLS, che è l'offset che utilizzerà nell'area di dati locale del thread.
Ogni variabile avrà un'entrata nell'intestazione TLS che specifica la dimensione e l'offset TLS, che è l'offset che utilizzerà nell'area di dati locali del thread.
Il `__TLS_MODULE_BASE` è un simbolo utilizzato per riferirsi all'indirizzo base della memoria locale per thread e punta all'area in memoria che contiene tutti i dati locali per thread di un modulo.
Il `__TLS_MODULE_BASE` è un simbolo utilizzato per riferirsi all'indirizzo base della memoria locale del thread e punta all'area in memoria che contiene tutti i dati locali del thread di un modulo.
## Auxiliary Vector (auxv) e vDSO
Il kernel Linux passa un vettore ausiliario ai processi contenente indirizzi e flag utili per il runtime:
- `AT_RANDOM`: punta a 16 byte casuali utilizzati da glibc per il canarino dello stack e altri semi PRNG.
- `AT_SYSINFO_EHDR`: indirizzo base della mappatura vDSO (utile per trovare syscalls e gadget `__kernel_*`).
- `AT_EXECFN`, `AT_BASE`, `AT_PAGESZ`, ecc.
Come attaccante, se puoi leggere la memoria o i file sotto `/proc`, puoi spesso rivelare questi senza una infoleak nel processo target:
```bash
# Show the auxv of a running process
cat /proc/$(pidof target)/auxv | xxd
# From your own process (helper snippet)
#include <sys/auxv.h>
#include <stdio.h>
int main(){
printf("AT_RANDOM=%p\n", (void*)getauxval(AT_RANDOM));
printf("AT_SYSINFO_EHDR=%p\n", (void*)getauxval(AT_SYSINFO_EHDR));
}
```
Leaking `AT_RANDOM` ti dà il valore del canary se riesci a dereferenziare quel puntatore; `AT_SYSINFO_EHDR` ti fornisce una base vDSO da esplorare per gadget o per chiamare syscalls veloci direttamente.
## References
- ld.so(8) Ordine di ricerca del Dynamic Loader, RPATH/RUNPATH, regole di esecuzione sicura (AT_SECURE): https://man7.org/linux/man-pages/man8/ld.so.8.html
- getauxval(3) Vettore ausiliario e costanti AT_*: https://man7.org/linux/man-pages/man3/getauxval.3.html
{{#include ../../banners/hacktricks-training.md}}