Buffer overflow
Un buffer overflow si verifica quando un programma scrive in un buffer più dati di quanti ne possa contenere, sovrascrivendo la memoria adiacente. Ad esempio, prendiamo questo codice c.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
struct user {
char name[32];
char password[32];
};
int main() {
struct user u1;
strcpy(u1.password, "secret_password");
// leggi il nome utente
scanf("%64s", u1.name);
printf("nome: %s\npassword: %s\n", u1.name, u1.password);
}
Questo codice prende in ingresso un nome utente e stampa il nome utente e la password. Infatti inserendo un nome utente ci viene restituito il seguente output.
$ ./main
pippo
nome: pippo
password: secret_password
In qualche modo, è possibile che l'utente modifichi la password?
Proviamo a mandare in ingresso 32 caratteri A
seguiti da 32 caratteri B
.
$ ./main
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
password: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
La password è cambiata, anche se la funzione scanf
dovrebbe scrivere su u1.name
. Il bug si trova nel fatto che scanf
accetta 64 bytes, ma il buffer è composto da 32 bytes.
La funzione scanf
, però, non ha modo di sapere quanto sia grande il buffer, e scrive anche nella regione di memoria adiacente, che contiene proprio password
.
Inoltre, abbiamo anche un secondo bug! La funzione scanf
piazza un null byte \x00
alla fine della stringa come terminazione, ma lo piazza solo alla fine, dopo tutti i caratteri.
Questo null byte viene posizionato proprio un byte dopo password
!
Possiamo vederlo tramite questo esempio leggermente modificato.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
struct user {
char name[32];
char password[32];
unsigned long token;
};
int main() {
struct user u1;
strcpy(u1.password, "secret_password");
u1.token = 0x0123456789ABCDEF;
// leggi il nome utente
scanf("%64s", u1.name);
printf("nome: %s\npassword: %s\ntoken: %p\n", u1.name, u1.password, u1.token);
}
Come al solito, se inseriamo un nome corto il programma esegue quello che ci aspettiamo.
$ ./main
pippo
nome: pippo
password: secret_password
token: 0x0123456789abcdef
Ma se proviamo a inserire l'input precedente, possiamo notare che il primo (l'ultimo perché il numero è interpretato in formato little endian) è stato modificato in un null byte, al posto di ef
.
$ ./main
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
password: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
token: 0x0123456789abcd00