Innanzitutto, è meglio provare a eseguire il overflow del eip
salvato di una funzione normale, non di main()
. Non è molto diverso, ma la funzione main()
è un po 'specifica e potrebbe darti una visione distorta della realtà.
Proviamo con questo codice:
#include <stdio.h>
#include <string.h>
void foo(char *msg)
{
char buf[64];
printf ("You entered value %s\n", msg);
strcpy (buf, msg);
printf ("%s\n", buf);
}
int main (int argc, char *argv[])
{
if (argc > 1)
foo(argv[1]);
printf ("Program is exiting normally!\n");
return 0;
}
Per prima cosa, compilarlo:
$> gcc -Wall -Wextra -std=c11 -m32 -g -o vulnerable vulnerable.c
Quindi, inizialo con gdb
:
$> gdb -q ./vulnerable
Reading symbols from ./vulnerable...done.
(gdb) b foo
Breakpoint 1 at 0x11db: file vulnerable.c, line 7.
(gdb) r $(python -c 'print("A" * 64)')
Starting program: /tmp/vulnerable $(python -c 'print("A" * 64)')
Breakpoint 1, foo (msg=0xffffd53a 'A' <repeats 64 times>) at vulnerable.c:7
7 printf ("You entered value %s\n", msg);
(gdb)
Ora, siamo proprio nel punto in cui possiamo sovrascrivere il buffer e sovrascrivere il eip
salvato che è memorizzato nello stack. Tuttavia, abbiamo inserito solo il 64% di% in% di spazio (la dimensione del buffer) e abbiamo bisogno di sapere quanti "% diA
" dobbiamo inserire per raggiungere il A
salvato nello stack.
Quello che cercheremo di fare è:
- Ottieni l'indirizzo del buffer;
- Ottieni l'indirizzo del
eip
salvato nello stack;
- Ottieni la differenza tra questi due indirizzi che dovrebbero darci la dimensione esatta del padding che dobbiamo inserire per raggiungere il
eip
salvato.
Andiamo:
(gdb) p &buf
$1 = (char (*)[64]) 0xffffd280
(gdb) info frame
Stack level 0, frame at 0xffffd2d0:
eip = 0x565561db in foo (vulnerable.c:7); saved eip = 0x56556249
called by frame at 0xffffd300
source language c.
Arglist at 0xffffd2c8, args: msg=0xffffd53a 'A' <repeats 64 times>
Locals at 0xffffd2c8, Previous frame's sp is 0xffffd2d0
Saved registers:
ebx at 0xffffd2c4, ebp at 0xffffd2c8, eip at 0xffffd2cc
(gdb) p 0xffffd2cc-0xffffd280
$3 = 76
Ora, sappiamo che se alimentiamo il programma con 76 caratteri di padding, sovrascriviamo eip
salvato con i 4 caratteri successivi (siete qui a 32-bit).
Proviamo:
(gdb) r $(python -c 'print("A" * 76 + "\xde\xad\xbe\xef"[::-1])')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /tmp/vulnerable $(python -c 'print("A" * 76 + "\xde\xad\xbe\xef"[::-1])')
Breakpoint 1, foo (
msg=0xffffd52a 'A' <repeats 76 times>, <incomplete sequence 6>)
at vulnerable.c:7
7 printf ("You entered value %s\n", msg);
(gdb) n
You entered value AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ�
8 strcpy (buf, msg);
(gdb) info frame
Stack level 0, frame at 0xffffd2c0:
eip = 0x565561f0 in foo (vulnerable.c:8); saved eip = 0x56556249
called by frame at 0xffffd2f0
source language c.
Arglist at 0xffffd2b8, args:
msg=0xffffd52a 'A' <repeats 76 times>, <incomplete sequence 6>
Locals at 0xffffd2b8, Previous frame's sp is 0xffffd2c0
Saved registers:
ebx at 0xffffd2b4, ebp at 0xffffd2b8, eip at 0xffffd2bc
(gdb) n
9 printf ("%s\n", buf);
(gdb) info frame
Stack level 0, frame at 0xffffd2c0:
eip = 0x56556202 in foo (vulnerable.c:9); saved eip = 0xdeadbeef
called by frame at 0xffffd2c4
source language c.
Arglist at 0xffffd2b8, args:
msg=0xffffd500 "S07Q73(gdb) x /32x $esp
0xffffd270: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd280: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd290: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2b0: 0x41414141 0x41414141 0x41414141 0xdeadbeef
0xffffd2c0: 0xffffd500 0xffffd384 0xffffd390 0x5655622b
0xffffd2d0: 0xffffd2f0 0x00000000 0x00000000 0xf7de79a1
0xffffd2e0: 0xf7fa4000 0xf7fa4000 0x00000000 0xf7de79a1
4x(5\ri686"
Locals at 0xffffd2b8, Previous frame's sp is 0xffffd2c0
Saved registers:
ebx at 0xffffd2b4, ebp at 0xffffd2b8, eip at 0xffffd2bc
Ora abbiamo il controllo completo del% co_de salvato, dato che abbiamo appena scritto eip
su di esso. Quindi, possiamo controllare l'esecuzione una volta che usciamo da questa funzione eip
.
Se vuoi vedere cosa hai appena scritto nello stack, ora puoi fare:
#include <stdio.h>
#include <string.h>
void foo(char *msg)
{
char buf[64];
printf ("You entered value %s\n", msg);
strcpy (buf, msg);
printf ("%s\n", buf);
}
int main (int argc, char *argv[])
{
if (argc > 1)
foo(argv[1]);
printf ("Program is exiting normally!\n");
return 0;
}
Come puoi vedere, trovi tutti i 76 ' 0xdeadbeef
' seguiti da foo()
. In effetti, questo è il tuo buffer A
, seguito dal% co_de salvato e dal% co_de salvato che hai appena riscritto.
Un'ultima nota sull'ASLR e sullo stack non eseguibile. Queste due protezioni non stanno giocando alcun ruolo qui. Devono essere disabilitati solo se si desidera eseguire uno shellcode che si inietta attraverso questo overflow. L'ASLR rende più difficile scrivere un indirizzo significativo al posto di 0xdeadbeef
perché il contesto della memoria cambierà continuamente. E lo stack non eseguibile rovinerà i tuoi tentativi di eseguire codice sullo stack.
Se vuoi solo controllare buf
salvato, devi solo disabilitare i canaries di stack (opzione ebp
). Il resto sarà significativo solo quando tenterai di utilizzare questo controllo per reindirizzare il codice a un tuo codice.