sábado, 14 de julio de 2007

Integer Overflow

Un Integer Overflow se produce cuando una operación aritmética intenta almacenar un valor numérico demasiado grande para la variable que lo contiene. De esta forma, si añadimos 1 al valor máximo que una variable puede almacenar, nos encontraremos con resultados curiosos. Veamos que ocurre en un sistema Linux x86.




int main()
{
unsigned short c = 65535;
printf("%d\n", c);
c++;
printf("%d\n", c);
return 0;
}




$ gcc ex1.c
$ ./a.out
65535
0

Como vemos en el ejemplo, al incrementar una variable que ya está en su valor máximo, esta se desborda, empezando de nuevo en el valor cero.
Podemos conocer el valor máximo que se puede almacenar en una variable del lenguaje C usando "sizeof":




int main()
{
printf("size of char: %d bytes\n", sizeof(char));
printf("size of short: %d bytes\n", sizeof(short));
printf("size of int: %d bytes\n", sizeof(int));
printf("size of float: %d bytes\n", sizeof(float));
return 0;
}



$ gcc ex2.c
$ ./a.out
size of char: 1 bytes
size of short: 2 bytes
size of int: 4 bytes
size of float: 4 bytes


Así, por ejemplo, en un char podremos guardar valores menores que 2^8, en un short valores menores que 2^16 y en un int valores menores que 2^32.

En el caso de las variables unsigned, cuando se produce un desbordamiento, nos encontramos con algo similar. Pues si intentamos almacenar un valor negativo en una variable sin signo, se realizará la conversión correspondiente. Por ejemplo, si en una variable "short" podemos almacenar valores menores que 2^16, en una variable "unsigned short" solo podremos almacenar la mitad. Pues se usaran los 2 bytes tanto para almacenar valores positivos como negativos. De esta manera, asignar un valor -1 a un "unsigned short" corresponderá a almacenar el valor 65535, almacenar -2 corresponderá a 65534, y así sucesivamente.




int main()
{
unsigned short c = -1;
printf("%d\n", c);
c++;
printf("%d\n", c);
return 0;
}




$ gcc ex3.c
$ ./a.out
65535
0

No resulta nada complicado hacerse a la idea de como una vulnerabilidad de este tipo puede ser explotada. Por ejemplo, modificando el flujo de ejecución de un programa. En el caso siguiente, se pasa una condición "if" que no debería.




int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: %s num\n", argv[0]);
return -1;
}

size_t num = atoi(argv[1]);

if(num>100)
printf("num > 100\n");
else
printf("num < 100\n);

return 0;
}




$ gcc ex4.c
$ ./a.out 1
num < 100
$ ./a.out 101
num > 101
$ ./a.out -1
num > 100



Para finalizar, veamos un ejemplo de buffer overflow en el que se muestra cómo usar la longitud de una cadena para escribir fuera del buffer. En este caso la variable que permite el overflow es len, dado que existe la posibilidad de que la longitud de la cadena sea zero. Entonces, el valor de len es -1, o lo que viene a ser lo mismo, 255 por ser una variable "unsigned" de 8 bits. Por este motivo, el memcpy copiará 255 bytes a ptr, unos cuantos más de los necesarios. Realmente lo que se desborda es el heap, por lo que se trata, concretamente, de un heap overflow.




int main(int argc, char *argv[])
{
if(argc<=1)
return -1;
char *ptr = malloc(strlen(argv[1]));
int *login_ok = malloc(sizeof(int));
*login_ok=0;
printf("size: %d\n", strlen(argv[1]));
uint8_t len = strlen(argv[1]) -1;
memcpy(ptr, argv[1], len+1);
printf("Login: %d\n", *login_ok);

if(*login_ok)
{
printf("Access Accept\n");
}
else
{
printf("Access Denied\n");
}
return 1;
}




$ gcc ex5.c
$ ./a.out hola
size: 4
Login: 0
Access Denied

$ ./a.out adiossssssssssssssssssssss
size: 26
Login: 0
Access Denied

$ ./a.out ""
size: 0
Login: 3618355
Access Accept




No hay comentarios: