Protostar – Heap Buffer Overflow – Heap 1

Tempo di lettura: 7 minuti

Rispetto al precedente livello, in questo caso il focus inizia ad essere sullo heap e sulla sua struttura, ed andremo quindi a comprendere meglio come è fatto e come è possibile, ovviamente, exploitarlo.

Heap 1

Codice Heap 1
Codice Heap 1

Come per lo scorso esercizio, andiamo ad inserire un breakpoint alla fine del codice (riga 35) e eseguiamo dentro gdb con il comando r AA BB. Dopodiché analizziamo lo heap

Contenuto dello heap
Contenuto dello heap

Si può notare che al suo interno abbiamo:

  • 0x00000001: è la variabile _priority _dello struct internet i1, assegnata a riga 24
  • 0x0804b018: è il puntatore all’array name di i1, che vale 0x00004141 (AA)
  • 0x00000002: è la variabile _priority _dello struct internet i2, assegnata a riga 28
  • 0x0804b038: è il puntatore all’array name di i2, che vale 0x00004242 (BB)

Ora, osservando la riga 31 e 32 e la struttura appena analizzata, possiamo ipotizzare che l’overflow avvenga proprio durante il primo strcpy, in quanto se andiamo ad inserire più caratteri di quanti l’array name ne contenga (8), dovremmo riuscire a sovrascrivere la struct i2. Facciamo una prova con ltrace

i2.name punta a 0x41414141
i2.name punta a 0x41414141

Ed ecco che, come si può vedere, ora il puntatore dentro lo strcpy vale 0x41414141. Andando ad indagare con GDB e lo stesso payload

Contenuto dello heap
Contenuto dello heap

Non esiste più il puntatore alla variabile i2.name ed il programma è andato in crash. Ma come possiamo sfruttare questo overflow per avere un exploit funzionante?

Per prima cosa dobbiamo calcolare l’offset in modo tale da sovrascrivere l’indirizzo in maniera precisa, ed è presto detto.

Calcolo dell'offset
Calcolo dell'offset

In questo modo, abbiamo calcolato la differenza tra la posizione del puntatore a i2.name (0x804b02c) e la posizione di i1.name (0x0804b018). Facciamo un test rapido per vedere se è giusto con il payload

gdb-peda$ r `perl -e 'print "A"x20 . "BBBB"' CC
Offset calcolato correttamente
Offset calcolato correttamente

Viene sovrascritto in maniera perfetta.

Ora che abbiamo trovato il punto da sovrascrivere, dobbiamo trovare quale indirizzo sovrascrivere. Ci potrebbero essere più vie, vediamone un paio.

Metodo 1 - GOT

Un file ELF è composto da più sezioni, ognuna delle quali composta dai propri dati e dalle proprie peculiarità. Ad esempio, se guardiamo quelle di heap1

Sezioni di heap1
Sezioni di heap1

ne abbiamo molteplici,come:

  • .text, che contiene la maggior parte del codice del programma
  • .rodata, che contiene le costanti
  • .data, che contiene le variabili inizializzate all’inizio
  • .plt, .got e .plt.got che si occupano del cosidetto Lazy Binding.

Il Lazy Binding si assicura sostanzialmente che un dynamic linker non perda tempo a cercare tutte le librerie necessarie ad un programma ma solo quelle che sono necessarie a runtime. Questa procedura è implementata grazie a due sezioni:

  1. Procedure Linkage Tabel (.plt): è utilizzato per chiamare procedure/funzioni esterne il cui indirizzo non è noto al momento del linking e che deve essere risolto dal linker dinamico al momento dell’esecuzione.
  2. Global Offset Table (.got): esso mappa i simboli nel codice ai loro corrispondenti indirizzi di memoria assoluti per facilitare l’esecuzione dello stesso
GOT e PLT
GOT e PLT

Per chi non avesse chiaro il procedimento, consiglio questo ottimo articolo Ma quindi, se andassimo a sovrascrivere sul puntatore a i2.name la chiamata a printf della funzione winner?

Prima di tutto, con GDB-Peda analizzo la sezione GOT dell’eseguibile

Contenuto di GOT
Contenuto di GOT

Una volta identificata, dovremo andare ad inserire quale indirizzo andremo a far chiamare la printf. Per ricapitolare l’idea è:

  1. Sovrascrivere l’indirizzo di memoria a cui punta i2.name con l’indirizzo della printf presente nella GOT
  2. Dentro la GOT, inserire l’indirizzo di memoria che vogliamo far stampare alla printf, quindi la funzione winner

Troviamo winner con objdump

osboxes@osboxes:~/Desktop/Protostar$ objdump -d heap1|grep winner
080484cb winner:
Esecuzione con il payload appena creato
Esecuzione con il payload appena creato

Sembra non avere funzionato. Osservando meglio la GOT, oltre ad una printf c’è anche una puts. Proviamo con quella

Bingo!
Bingo!

Analizzando dentro GDB, possiamo vedere il giro completo che siamo riusciti a creare

Chain completa del payload
Chain completa del payload

Metodo 2 – Return Address

Ammetto di aver lasciato il metodo due dopo GOT perché decisamente più semplice rispetto al precedente. In questo caso, possiamo semplicemente sovrascrivere il puntatore a i2.name con l’indirizzo del return address e successivamente far puntare ad esso la funzione winner. Procedendo sempre per passi, dobbiamo quindi:

  1. Trovare l’indirizzo a cui punta RET e usarlo per sovrascriverlo su *i2.name
  2. Iniettare subito dopo l’indirizzo della funzione winner

Inseriamo un breakpoint a main e troviamo EIP

Comando info frame
Comando info frame

Ora, ci basta sostituire questo indirizzo con quello di GOT del precedente payload

Esecuzione del payload creato
Esecuzione del payload creato

Ma sembra non funzionare. Non sono riuscito a capire come mai, e ho iniziato a sparare nel mucchio trovando poi l’indirizzo di EIP corretto (0xbffff60c)

Bingo!
Bingo!

Cosa molto simile fuori da GDB, in quanto ho dovuto modificare l’indirizzo del return address in 0xbffff67c per ottenere un exploit funzionante

Esercizio risolto
Esercizio risolto

Se sapete come mai questo comportamento di GDB commentate pure!

Conclusioni

Già con questo esercizio abbiamo approfondito molti più argomenti e l’heap inizia ad essere un pelo più chiaro. Gli approfondimenti li lasciamo per il prossimo esercizio, già in questo di cose da studiare ne sono spuntate!