Skip to main content
Como funciona un Buffer Overflow - parte II

Como funciona un Buffer Overflow – parte II

Explotando una vulnerabilidad de Buffer Overflow podemos llegar a conseguir acceso root al servidor objetivo

Bienvenidos a la segunda parte de este post. Si te leiste la primera entrada o ya crees que comprendes como funciona podemos llevar acabo un ataque de Buffer Overflow. En caso contrario, podéis leer la primera entrada donde explicamos que es y como funciona en el siguiente enlace.

En este caso, nos encargaremos de desbordar una variable de la pila que nos permitirá alterar el flujo de ejecución normal del programa y sobreescribir el valor de la variable con el fin de modificar el valor de los registros RBP y RIP.

Por nuestra parte vamos a realizar el ataque en una instancia con la distribución Kali Linux de 64 bits.

Para poder llevar a cabo correctamente las pruebas, lo primero a realizar será deshabilitar la aleatorización del diseño del espacio de direcciones (ASLR). Cuando ejecutamos un programa, todos los datos asociados se almacenan en la memoria (incluida la pila). ASLR es el encargado de aleatorizar dónde deben de residir estos datos del programa a la memoria, lo que hace más difícil determinar donde se encuentran las cosas y como tomar el control del programa. Para deshabilitarlo pueden hacerlo con el siguiente comando:

 

Ya lo tenemos todo listo para comenzar. Antes de nada necesitaremos también de un código vulnerable con el cual realizar las pruebas, os dejo a continuación el código utilizado para el ejemplo:

 

Si nos fijamos en el código, este no admite ningún argumento, como primera opción nos mostrará un mensaje por pantalla «What’s your name?» y nos solicitará escribir un texto por pantalla. Para aquellos que no lo sepan, en C, los strings se definen como un array de caracteres que terminan siempre en un byte null (\0) y que la direción de memoria del mismo apuntará siempre a la dirección del primer carácter almacenado en el mismo. De esta forma, la función utilizada, read(), se limitará a leer el contenido de las posiciones de memoria del string de origen al de destino hasta encontrarse con el valor nulo ‘\0’ y los almacenará en la variable «arr».

Si analizamos el código descrito comprobamos que no se realiza ninguna comprobación del parámetro antes de almacenar el mismo con un total de 400 bytes, es decir, la función read irá leyendo el contenido hasta encontrar el valor nulo sin preocuparse de si el tamaño es o no de el número de bytes especificados.

Para ello vamos a ver el código ensamblador de la función principal main, pero antes de esto compilaremos nuestro programa para poder proceder a su ejecución. En un futuro publicaremos un post explicando todo esto en detalle pero para demostrar el buffer overflow nos bastará por el momento con estas indicaciones.

Una vez que ya tenemos nuestro código, por nuestra parte lo guardamos como bufferoverflow.c y ahora necesitaremos compilarlo con el siguiente comando:

En el comando anterior utilizamos gcc (Gnu Compiler Collection) para realizar la compilación de nuestro código. La opción -fno-stack-protector deshabilitará la protección de la pila dentro del programa compilado y la opción -z execstack permitirá especificamente que la pila pueda ser ejecutada. Por último la opción -o nos permitirá ingresar el nombre que queremos darle a nuestro programa compilado.

Ahora vamos a ejecutar nuestro programa para ver su funcionamiento:

Como funciona un Buffer Overflow - parte II

 

Una vez visto que el programa funciona correctamente, vamos a generar un fichero con el caracter «A» 500 veces con python y guardaremos el resultado en un fichero. Dejo a continuación el comando para realizarlo.

Y posteriormente probaremos a ejecutar el mismo como entrada de texto para nuestro script, dando un error como se observa en la siguiente captura de pantalla:

Como funciona un Buffer Overflow - parte II

 

Ahora vamos a debuguear nuestro programa con la herramienta gdb, para ello lanzaremos el siguiente comando:

Y procederemos a ejecutar nuestro programa con el siguiente comando:

Y le pasaremos nuestro fichero generado con 500 caracteres A a nuestro programa:

Podemos ver el funcionamiento en la siguiente captura de pantalla:

Como funciona un Buffer Overflow - parte II

 

Ahora vamos a comprobar el estado del registro de nuestra aplicación. Para ello lanzaremos el siguiente comando:

Como vemos en la siguiente captura:

Como funciona un Buffer Overflow - parte II

Tal y como podemos observar en la parte resaltada, observamos que en el registro «rbp» observamos varias apariciones del caracter 0x41. Si comprobamos en una tabla ascii, observamos que corresponde con el caracter «A» que hemos ingresado en nuestro programa utilizando como entrada el fichero «textfile» en el cual habíamos guardado previamente con el comando de python el caracter «A» 500 veces.

 

¡Felicidades! has realizado tu primer buffer overflow causando la modificación del registro rbp con 0x4141414141414141.

 

Por el momento es todo correcto, y hemos observado que tal y como se indicaba es posible llevar a cabo un ataque de buffer overflow, pero vamos a complicar un poco más las cosas y a intentar demostrar el poder de esta vulnerabilidad.

Procederemos a continuación a obtener más información del programa. Siguiendo en la consola de gdb vamos a obtener el código ensamblador de la función vuln, para ello podemos utilizar el siguiente comando:

Como funciona un Buffer Overflow - parte II

Y crearemos un punto de ruptura (breakpoint en inglés) en el cual parar la ejecución de nuestro programa con el fin de poder debuguear el mismo. Para ello crearemos nuestro punto en la función read, que visualizando la captura anterior correspondería al valor 0x0000555555555180 o <+43>. Para ello lanzaremos el siguiente comando:

Ahora volveremos a ejecutar nuestro programa pasando como texto de entrada el fichero «textfile» generado previamente:

Como funciona un Buffer Overflow - parte II

Como vemos, la ejecución del programa se ha parado en el punto de ruptura que creamos previamente. Ahora nos interesa obtener el valor de los registros rbp y rsp, para ello podemos lanzar los siguientes comandos:

Como vemos en la siguiente imagen:

Como funciona un Buffer Overflow - parte II

Y nos guardaremos estos valores para utilizarlos más adelante.

Veamos ahora qué está pasando en nuestra pila. Para ello ejecutaremos el siguiente comando:

Este comando nos imprimirá 120 direcciones hexadecimales posteriores a la dirección del registro rsp. Debido a la dirección de memoria del registro rbp, este comando nos permitirá ver la pila completa.

Además, si nos fijamos en la siguiente captura de pantalla observaremos que el programa que se está ejecutando se ha parado justo antes de leer nuestra lista de 500 caracteres.

Como funciona un Buffer Overflow - parte II

Ahora utlizaremos un comando para continuar la ejecución hasta la siguiente operación:

Y volveremos a lanzar la sentencia para ver las 120 direcciones de la pila, como observamos hemos sobreescrito la pila completa con nuestro caracter A.

Como funciona un Buffer Overflow - parte II

Esto puede ser útil en muchas ocasiones pero no es nada útil llenar una pila del caracter A, en este caso, lo que nos interesa es descubrir en que parte de esa carga de 500 caracteres se está sobreescribiendo el registro rbp para provocar el salto.

Para ello podemos crear una cadena que no contenga ninguna sentencia de caracteres repetidos, lo que nos ayudará a determinar en qué parte de la secuencia se ha producico esta sobreescritura del registro.

Para conseguir esto, podemos utilizar el script de creación de patrones de Metasploit, generando con ello la cadena única mencionada anteriormente, y que guardaremos en un fichero llamado fuzzing. El comando a utilizar sería el siguiente:

Como funciona un Buffer Overflow - parte II

 

Ahora ejecutaremos nuestro programa con esta lista generada en el fichero «fuzzing». Como sobreescribimos anteriormente el valor del registro rbp, ahora necesitaremos indicar la dirección de la pila manualmente que obtuvimos anteriormente. Utilizaremos el siguiente comando en gdb:

Que en nuestro caso sería:

Como funciona un Buffer Overflow - parte II

 

Tal y como se explicó anteriormente obtuvimos el valor del registro rbp, el cual hemos utilizado para identificar el punto de salto en el cual se sobreescribe el valor del registro. Ahora convertiremos el valor hexadecimal obtenido en ascii, el cual dará el siguiente resultado:

0x6f41316f 0x336f4132 = oA1o3oA2

Ahora utilizaremos la herramienta de desplazamiento del patrón de Metasploit, con la cual identificaremos en qué parte de la cadena única se encuentra el valor identificado, lo que nos dará el punto en el cual se sobreescribe el registro, para ello usaremos el siguiente comando:

Lo que nos dará el punto de desplazamiento 424, lo que significa que tenemos 424 bytes para jugar con nuestra carga útil, como vemos en la siguiente captura:

Como funciona un Buffer Overflow - parte II

 

Ahora que ya tenemos una idea aproximada de cual es la carga útil, vamos a crear un sencillo script en python que se encargará de generar las especificaciones únicas para nuestro programa.

El script creará un ejemplo de nuestra carga útil con una longitud de 424 bytes, más la dirección de retorno. Esta dirección de retorno será la dirección del registro rsp.

Esta es la parte de nuestro programa donde se está produciendo el desbordamiento, y será la parte del programa donde atacaremos con el fin de introducir en el mismo nuestro código mlaicioso.

Nuestra carga útil comenzará con un NOP. El programa ignorará NOP durante la ejecución, lo que significa que lo evitará pasando al siguiente paso de la ejecución. Debido a que nuestra intención es redirigir la ejecución dle programa a nuestro código malicioso, este valor NOP nos será muy útil para lograr nuestro objetivo.

Añadiremos también el relleno a la carga útil, ya que lo necesitaremos para sobreescribir el registro rbp y no podremos completarlo si la carga útil no es lo suficientemente larga. Veamos como ha quedado nuestro script:

Generearemos nuestro programa en el fichero payload.py, lo ejecutaremos y guardaremos su contenido en el fichero fuzzing:

Como funciona un Buffer Overflow - parte II

Utilizando de nuevo gdb ejecutaremos de nuevo nuestro programa utilizando como entrada el nuevo fichero fuzzing generado con nuestro script en python.

Como funciona un Buffer Overflow - parte II

Si nos fijamos la dirección de segmentación en este caso es diferente, siendo ahora 0x00007fffffffdfc4.

Esto es debido a que al causar el desbordamiento del buffer con nuestra carga útil se inicio el programa y se ejecutó hasta el valor 0x7fffffffdf60. Desde este punto, el programa ontinuó a través de la opción NOP, hasta la siguiente operacón ejecutable. Al no contener nuestra carga útil un código ejecutable, el programa finaliza y falla.

Ahora ya tenemos la respuesta correcta, como se muestra en la anterior captura, después de NOP vemos nuestro relleno de la letra B o en hexadecimal 0x42.

Llegados a este punto necesitaremos generar un código ejecutable con el que explotar esta vulnerabilidad en nuestro programa. Utilizaremos en este caso msfvenom para generar nuestro código malicioso:

Para ello utilizaremos el siguiente comando:

Explicamos a continuación las opciones utilizadas anteriormente:

  • -p -> especificamos el payload a utilizar
  • LHOST -> indicamos la ip del host
  • LPORT -> indicamos el puerto del host
  • -b -> indicamos un caracter NULL para indicar el final de línea
  • -f -> indicamos el formato del payload

Y quedar’ia de la siguiente forma:

Como funciona un Buffer Overflow - parte II

Utilizaremos shellcode generado en la variable buf obtenida para completar nuestro payload, quedando de la siguiente manera:

Con nuestro script completo, vamos a intentar explotar la vulnerabilidad.

Generaremos primero de nuevo nuestro fichero fuzzing mediante la ejecución del payload:

Como funciona un Buffer Overflow - parte II

Ahora colocaremos el host de destino a la escucha a través del puerto 4444 con la ayuda de netcat:

Y ejecutaremos nuestro programa con el fichero fuzzing generado:

Como funciona un Buffer Overflow - parte II

¡¡¡ Conseguido !!! Hemos conseguido explotar la vulnerabilidad y conseguir una shell en la instancia objetivo ejecutando nuestro código arbitrario en la misma, lo que significa que si el programa se está ejecutando con el usuario root, obtendriamos control total sobre la máquina.

 

Espero que les haya gustado y como siempre, cualquier duda, opinión o sugerencia es bienvenida y pueden indicarla en los comentarios.

Gracias por leernos y nos vemos en el siguiente post.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *