lunedì 26 gennaio 2009

Capitolo su FORTIFY_SOURCE terminato: Enter No-Execute

Ho appena terminato il capitolo su FORTIFY_SOURCE, una patch per GCC implementata oramai su diverse distribuzioni linux, ma utilizzata ancora da poche (Fedora e Red Hat in primis) per proteggere i pacchetti precompilati ed aggiungere un ulteriore livello di protezione contro Stack, Heap e Format String Overflow. La patch non è comunque infallibile, nel senso che vi sono dei casi (soprattutto in presenza di buffer allocati dinamicamente) che non riesce efficacemente a controllare.

Facciamo invece il punto della situazione del lavoro fin qui svolto:

- Ho scritto in tutto al momento 7 capitoli per un totale di 188 pagine nette

Nella prima parte del libro all'appello mancano ancora i seguenti argomenti:

- Come il processore gestisce i calcoli matemici (in particolare sugli interi)
- Integer Overflow
- Descrizione dell'Heap
- Heap Overflow

Nella seconda parte devo invece ancora parlare di:

- Protezione No-Execute
- Propolice
- Sfruttamento avanzato degli Heap Overflow

Penso di procedere in questo modo. Il prossimo capitolo che tratterò sarà No-Execute. Poi toglierò di mezzo gli Integer Overflow, quindi parlerò di Propolice ed alla fine farò tutta una tirata con le tematiche Heap.

Tempo stimato alla conclusione del testo: francamento non lo so. Spero per fine Febbraio ma è più probabile Marzo. Dipenderà molto dai carichi di lavoro che dovrò sopportare in queste settimane.

Un saluto.

5 commenti:

Gnix ha detto...

Mi fa veramente piacere vedere che non hai mollato, che stai quasi per finire il libro, e che i temi trattati sono decisamente interessanti.

Inutile dire che non vedo l'ora di leggerlo. :D
Anche perchè ora mi sono imbattuto in un problema con l'ASLR che non mi fa dormire.

Ora so che non dovrei disturbarti, ma magari puoi darmi una mano. Per non riscrivere ecco il link al post http://groups.google.ch/group/ptrace/browse_thread/thread/900ffdab05086ba8?hl=it dove faccio notare che l'uso di execle non va più in OpenSuse 11.1.

Se hai una qualche idea scrivi pure un e-mail a me (gnixmail@gmail.com) o direttamente alla ML.

Ciao
gnix

Marco Ortisi ha detto...

Ciao. Noto con piacere che mi segui ancora (almeno qualche lettore del blog è rimasto dato che non lo aggiorno da un mese). Comunque veniamo a noi. Ti spiego per grandi linee cosa puoi fare, tenendo presente che ogni bug è una vulnerabilità a sé e che solo con un'attenta analisi con un debugger puoi capire quale è il modo migliore di procedere.

Il primo trucco è un banale JMP to ESP o CALL to ESP. Devi trovare un'istruzione (lascia perdere il VDSO...lì oggi non la troverai mai più) all'interno del binario che sia la "rappresentazione" dell'istruzione assembly "jmp *%esp" o "call *%esp" (quindi la puoi cercare anche in zone del binario non eseguibili) e configuri il buffer nel seguente modo:

- riempi il buffer di garbage data
- l'indirizzo di ritorno lo sovrascrivi con il punto in cui hai trovato (se la trovi) l'istruzione jmp *%esp (puoi utilizzare objdump o readelf per questo).
- a seguire metti lo shellcode

un'altra cosa che puoi fare è controllare al momento dell'overflow a cosa puntano i registri EAX,EBX,ECX,EDX,ESI o EDI. Vedi se puntano nello stack alla tua stringa. In tal caso (supponendo che EAX punti ad un'indirizzo nello stack dove comincia la tua seuqenza di AAA...) puoi organizzare il tutto in questo modo:

- Collochi all’inizio del buffer lo shellcode

- Sovrascrivi il resto con garbage data;

- Modifichi l’indirizzo di ritorno con quello dell’ istruzione "call *%eax" che devi cercare nel binario o nel VDSO (è abbastanza comune trovarla, sopratutto nelle routine di inizializzazione prima che venga invocato il main() o nelle routine di analisi del dtors).

Naturalmente la maggior parte di questi trucchi risulta inutile se utilizzi un'applicazione banale che fa una semplice stncpy(). Su applicazioni più complesse e reali trovi invee che tra una funzione e l'altra vengono passati o ritornati puntatori e ciò incrementa la possibilità che venga a crearsi una situazione ideale magari per un JMPtoREG generico.

Questi sono un paio di trucchi che ho estratto dal mio libro. Ce ne sta ancora qualcuno ma va a finire che ti scriva l'intero libro qui. Puoi contattarmi in privato se vuoi all'indirizzo marco.ortisi@segfault.it

Bau.

Marco Ortisi ha detto...

Non sono un utilizzatore di OpenSuSE (non è tra le distribuzioni che preferisco ma devo ammettere che la 11.1 ha fatto salti da gigante). Il tuo problema su questa distro non è tanto l'Address Space Layout Randomization, quanto la funzionalità No-Execute. Ti spiego. Supponendo che tu riesca a collocare uno shellcode nello stack e farlo puntare da EIP, il codice non verrà eseguito perchè lo stack non ha i permessi in esecuzione. Ti trovi di fronte due strade:

- A) modo più semplice: disattivi NX al boot e riprovi :)

- B) trovi un modo per bypassare NX oltre ad ASLR

Tralasciando le tecniche per bypassare NX che spiego in un capitolo a parte, ti spiego invece come lavorare sul punto A:

- al boot disattivi la funzionalità NX specifidando come parametro d'avvio noexex=off

- crei un programma vulnerabile (utilizzando magari strcpy() in modo malsano) ed aggiungi al codice la seguente funzione:

void funzione_mai_chiamata()
{
__asm__("jmp *%esp");
}


- ti cerchi nel binario l'indirizzo dell'istruzione jmp *%esp (che sarà sempre lo stesso da esecuzione ad esecuzione) e sfrutti la tecnica del JMPtoESP che ti ho spiegato nel commento precedente. Vedrai che questa volta ti apparirà una bella shell :-)

Fammi sapere.

Gnix ha detto...

Ti ringrazio per le risposte che mi hai dato.

Al momento stavo cercando di capire, il motivo del funzionamento solo all'interno del debugger. Perchè il programma eseguito in gdb è exploitabile, mentre nella shell no? Per ora non lo so, ma diciamo che ci sto lavorando.

Ho comunque guardato i tuoi consigli e voglio provare a cambiare tecnica, usando magari un jmp2esp o call2esp. Chiaramente spero che anche sta volta non mi succeda che l'exploit va solo nel debugger. :)

Per quanto riguarda l'ultimo intervento, non credo che la funzionalità No-Execute sia attivata al boot, in quanto disattivando l'ASLR il mio exploit funziona anche nella shell (e lo shellcode è in una variabile d'ambiente nello stack). Tuttavia potro accertarmi meglio domani, quando sarò davanti al server OpenSuse.

Ti farò sapere di più nei prossimi giorni. Grazie ancora per il tempo che mi hai dedicato nelle risposte.

Marco Ortisi ha detto...

Ho installato openSUSE 11.1 su una macchina di test per fare delle prove ed il supporto NX era attivato di default. Per questo avevo affermato che il tuo problema fosse in realtà No-eXecute. Ora sono curioso del perchè della differenza tra la mia e la tua installazione. Sai per caso se l'hardware in cui hai installato la tua distro supporta NX di default (magari riesci a dirmi se è databile dopo o prima del 2004??).

Se l'exploit nel debugger va ma non da shell vuol dire che cambia (magari minimamente) l'ambiente di esecuzione.

Ti consiglio quindi, se non lo hai già fatto, di lanciare questo comando prima di eseguire l'exploit da prompt:

ulimit -c unlimited

Rilanci a questo punto l'exploit da shell ed assieme al segmentation fault viene creato un file "core" che puoi esaminare con:

gdb nome_eseguibile core

A questo punto da GDB prova a vedere se tutte le assunzioni che hai fatto sono corrette osservando lo stato dei registri e della memoria. Capita spesso che devi modificare di poco gli indirizzi che usi quando operi da shell rispetto a quando ti trovi ad operare da debugger.