Caso di studio 3
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char *gets(char *);

void complete_level() {
  printf("Congratulations, you've finished stack3 :-) Well done!\n");
  exit(0);
}

void start_level() {
  char buffer[64];
  gets(buffer);
}

int main(int argc, char **argv) {
  printf("Welcome to stack4\n"); 
  // la sfida si chiama stack4, ma per noi è il terzo caso di studio
  start_level();
}
C

Fase 1: cosa notiamo ad una prima vista?
...

Come al solito, c'è un buffer e una funzione vulnerabile: gets().
Notiamo che la funzione complete_level non viene chiamata dalla funzione start_level invocata dal main.
Per cui quando si entra nella funzione start_level viene allocato spazio sullo stack per un array di caratteri. Sappiamo che prima di entrare nella funzione start_level viene salvato sullo stack il vecchio valore del frame pointer e il return address. Possiamo provare a riempire il buffer e accedere al campo di return address e inserire l'indirizzo in cui viene eseguita la funzione complete_level.

Fase 2: abbiamo bisogno di conoscere l'indirizzo di complete_level
...

Eseguiamo il debugger gdb e gli passiamo il programma e richiediamo la stampa dell'indirizzo della funzione complete_level:
Pasted image 20231005162217.png
Adesso abbiamo l'indirizzo (non in formato little endian) della funzione complete_level.

Il prossimo passo è eseguire il codice e controllare lo stato della memoria, ovviamente non ci basta eseguire il codice normalmente, poiché non abbiamo modo di dare uno sguardo alla memoria. Usiamo sempre gdb.
Quello che facciamo è disassemblare la funzione start_level.
Pasted image 20231005162647.png
Anzitutto possiamo notare, che viene messo ebp sullo stack: viene salvato il frame pointer.
ebp viene fatto puntare a esp.
esp viene esteso due volte, una volta di 64 byte (0x48) e un'altra di 12 byte (0xc).
Poi vediamo che vi è l'istruzione lea -0x48(%ebp), %eax, la funzione lea calcola un indirizzo a partire dall'operando -0x48(%ebp) in questo caso, non fa altro che calcolare l'indirizzo che viene fuori quando si sottraggono 0x48 byte a ebp e lo mette in eax.
Tale indirizzo viene poi messo sullo stack e viene chiamata gets per cui il punto da cui si cominciano a copiare i byte che prende gets partono proprio da ebp - 0x48 (72 byte), ricordiamo che da quel punto i byte saranno copiati andando verso indirizzi più alti, quindi verso sopra. Proprio sopra quei 72 byte, c'è il vecchio frame pointer, e sopra di esso ci dovrebbe essere il return address posizionato sullo stack dal main. Quindi sappiamo che ebp si trova a 72 byte di distanza dall'inizio del buffer, inoltre sappiamo che nel punto in cui punta attualmente ebp c'è il vecchio valore del frame pointer e che sopra di esso +4 byte dovrebbe essere il return address, per cui il return addresso dovrebbe trovare a 76 byte di distanza dal punto in cui comincia il buffer.

Per verificare dove si trova tale indirizzo, eseguiamo il debug di questo programmino con gdb, prepariamo un file da dare in pasto al programma.
Pasted image 20231005165053.png
Piazziamo un breakpoint proprio alla fine della funzione start_level, guardando l'immagine che riguarda il suo disassemblamento, ci riferiamo alla riga <+21>
Pasted image 20231005165237.png
Adesso continuiamo l'esecuzione utilizzando sempre il comando run, ma per passare in input anche il file creato prima utilizziamo il comando nel seguente modo:
Pasted image 20231005165419.png
Per avere una maggiore sicurezza del calcolo fatto prima, usiamo gdb, per vedere dove si trova ebp:
Pasted image 20231005164850.png
questo non basta a confermare il calcolo fatto precedentemente, allora stampiamo lo stato della memoria a parte da esp (la cima dello stack):
Pasted image 20231005171407.png
come abbiamo detto più volte, l'inizio del buffer sta più in basso, mentre la fine sta più in alto.
gdb ci ha detto che ebp si trova a 0xbfffef38. Come si vede viene indicata una linea di memoria 0xbfffef30, poi i 4 byte adiacenti, poi gli altri 4 byte adiacenti e così via. Proprio in quella linea dopo 8 byte 0xbfffef38 c'è ebp, i quattro byte adiacenti all'indirizzo di ebp è l'indirizzo di old_ebp quindi dopo c'è il return address. Dopo l'ultimo 0x41414141 (riquadro più a destra, in rosso), ci sono 3 gruppetti di 4 byte che in totale sono 12 byte. Il buffer è 64 byte, per cui 64 + 12 = 76, dobbiamo riempire il buffer e in più altri 12 byte, dopo di questo piazzeremo l'indirizzo della funzione complete_level in little endian usando python, creiamo il file:
Pasted image 20231005171919.png
nello script che abbiamo creato abbiamo scelto di utilizzare due locazioni in cui mettere il return address, una dopo 72 byte e l'altra dopo 76, il motivo è che lo stack potrebbe essere più corto di quanto effettivamente quando viene debuggato con gdb.
Pasted image 20231005172123.png