Usando la stringa di formato per controllare il flusso di un processo?

6

Oggi stavo pulendo del codice su consiglio di qualcuno che conosco, e sono stato reso consapevole del potenziale per un hacker di ottenere il controllo del flusso del processo usando le stringhe di formato, in particolare l'opzione "% n".

Non sono del tutto sicuro, sia concettualmente che praticamente, di come sarebbe stato raggiunto.

Ho ragione nel pensare che abbia qualcosa a che fare con la sovrascrittura degli indirizzi di ritorno? Supponendo che io sappia dove sto scrivendo,% n mi dà ancora solo il numero di caratteri, quindi sicuramente la prevenzione dell'overflow del buffer renderebbe questo quasi impossibile? O c'è un modo per spostare il numero 0xbbff nella posizione di memoria 0xb7001122 con una stringa come 0xb7001122 0xbbff %n ?

Di questo c'è relativamente poco online! Sono abbastanza confuso!

    
posta Richard 30.11.2012 - 22:50
fonte

2 risposte

5

Per capire cosa sta succedendo, il punto importante è che in C, le funzioni non "sanno" quanti argomenti gli ha dato il chiamante. Per le normali funzioni, il compilatore rileva usi non validi. Ad esempio, se scrivi questo:

#include <string.h>
/* ... */
    memcpy(a, b);

allora il compilatore si lamenterà rumorosamente e rifiuterà di completare la compilazione: l'intestazione inclusa <string.h> dichiara la funzione memcpy() come prendendo tre parametri (due puntatori e un intero di tipo size_t ). Se il compilatore vede una chiamata a memcpy() con solo due parametri, la mancata corrispondenza lo farà urlare.

Tuttavia, alcune funzioni richiedono un numero variabile di parametri, ad es. %codice%. Per queste funzioni, il compilatore non può fare molto controllo. printf() usa come primo argomento una "stringa di formato" il cui contenuto indica printf() quanti altri argomenti dovrebbe trovare e il loro tipo previsto. Quando la stringa di formato è una costante letterale, i compilatori intelligenti possono eseguire alcuni controlli aggiuntivi (con printf() , ciò avviene con " formato "attributo , un'estensione specifica per gcc). Ma non può fare nulla quando si ottiene la stringa di formato in fase di esecuzione. Immagina, ad esempio, il seguente codice:

printf(s);

dove gcc è una stringa che l'utente malintenzionato può scegliere (ad esempio, alcuni dati inviati tramite la rete o un parametro di chiamata). Il programmatore ha appena assunto che la stringa fosse "semplice" (solo caratteri alfanumerici) ma l'autore dell'attacco può inserire segni " s " al suo interno, che % interpreta osservando parametri aggiuntivi. Non ci sono parametri extra, quindi printf() guarderà effettivamente agli slot di memoria dove i parametri extra sarebbero stati , se ce ne fossero stati. Questi slot sono "più in basso nella pila" (tecnicamente, a indirizzi più alti, dal momento che gli stack "crescono" nella maggior parte delle architetture); le variabili locali, l'indirizzo di ritorno della funzione e altri elementi dello stack per la funzione corrente (e il relativo chiamante e il chiamante del chiamante e così via) sono trovati lì.

Usando printf() specificatori, l'attaccante può leggere tutti questi slot ( %u stamperà diligentemente il loro contenuto), e questo è già cattivo. Con il parametro printf() , i poteri dell'attaccante sono addirittura migliorati: %n prende il parametro successivo, interpreta come valore di puntatore, lo segue e scrive nell'indice a cui è indirizzato il numero corrente di caratteri finora (il numero di caratteri stampati da %n fino a printf() ). Se la chiamata %n errata si verifica in una funzione in cui è presente una variabile locale che è anch'essa sotto il controllo dell'utente malintenzionato (ad esempio un valore intero semplice), l'utente malintenzionato può creare printf() per utilizzare tale variabile locale come puntatore e scrivi i dati a cui punta. In parole più brevi, questo dà all'aggressore la possibilità di scrivere qualunque byte desideri, ovunque voglia nella RAM. A quel punto, sei praticamente condannato.

Il codice corretto sarebbe stato:

printf("%s", s);

che ha forzato %n a stampare la stringa controllata dall'attore "così com'è", senza fare nulla di speciale con i caratteri " printf() ".

Questa vulnerabilità % mette in evidenza il fatto che C è un linguaggio piuttosto pericoloso: nessun controllo sui limiti, nessun controllo sui tipi forti ... puoi fare in modo che il programma guardi gli slot di memoria come se fossero parametri, mentre non lo sono, e anche interpretare i modelli di byte come se fossero dei puntatori, che non sono. I programmatori C competenti scriveranno il loro codice in modo da fornire al compilatore il maggior numero possibile di informazioni, in modo da rilevare gli errori (i buchi di sicurezza sono solo errori di programmazione con un effetto che può essere distorto a vantaggio dell'attaccante). Ma competenza è una risorsa scarsa, e anche i migliori programmatori a volte commettono errori.

    
risposta data 01.12.2012 - 18:41
fonte
2

Il processo di estrazione della stringa di formato dipende da come viene utilizzata la stringa di formato nell'applicazione vulnerabile. Ad esempio printf () è solo memoria di lettura, che può consentire all'utente malintenzionato di leggere un indirizzo ASLR o valore canarino che potrebbe essere utilizzato in coniugazione con un buffer overflow per ottenere il controllo del processo. Se l'applicazione utilizza sprintf () , l'applicazione sta scrivendo in memoria a partire da un indirizzo di base specifico. Fornendo troppe opzioni di stringhe di formato come %n , l'autore dell'attacco può sovrascrivere la memoria al di fuori dei limiti dell'indirizzo di destinazione. Se la destinazione è una variabile locale dichiarata nello stack, allora è possibile che l'attaccante corrompa il frame dello stack e ottenga il controllo del puntatore dell'istruzione.

Questi attacchi e molti altri sono trattati in Software di exploit: come rompere il codice e Hacking: The Art of Exploitation .

    
risposta data 01.12.2012 - 00:09
fonte

Leggi altre domande sui tag