martes, 17 de diciembre de 2013

Vulnerabilidad en printf (y III)

Vamos a finalizar esta serie de artículos con un ejemplo, sencillito, en el que intentaremos explotar la vulnerabilidad de la función printf.

En el post anterior ya aclaré la diferencia entre vulnerabilidad y explotación de la vulnerabilidad. Vamos a analizar un programa que permite usar la función printf para obtener acceso a otras zonas de memoria. Un programa en el que podremos explota esta vulnerabilidad.
El programa en cuestión es este:

#include<stdio.h>

main()
{
char string1[] = "123456789abcdef";
int i, j, k;
    char _userin[80];
    i = 100;
    j = i + 1;
    k =  j + 1;
    scanf("%s", _userin);
    printf("%s, %s",_userin);
  

    return 0;

}

Podemos descargarnos el ejecutable y el fuente desde este enlace.

Vemos que hay una incorrecta llamada a la funcion printf

printf("%s, %s",_userin);

Este fallo nos va a permitir explotar la  vulnerabilidad y poder acceder a otras partes de memoria.
Si ejecutamos el programa normalmente veremos que el programa finalizará anormalmente.







Esto deberá ocurrir casi siempre. Supongamos que estas líneas de código forman parte de un gran sistemas de programas y que normalmente esta sentencia está un una de las ramas de algún programa y que ha pasado desapercibida en las pruebas de funcionalidad y de calidad que se han realizado.

Ahora carguemos el programa en Ollydbg y y pongamos un breakpoint justo antes de la llamada a la función printf y otro justo antes de la exit.

Para facilitar la búsqueda de estas instrucciones solo tenemos que fijarnos en la imagen superior.

La llamada a la printf está un poco encima de punto de entrada al programa y la salida por debajo.

Damos Run (F9) y se nos parará en la función scanf, introducimos cualquier texto, en el ejemplo hemos introducido "qwerty"

Observemos la pila:


La primera entrada es la cadena de formato y las dos siguientes entradas son los parámetros que se supone que tiene la función. El primer parámetro en la pila es una referencia a la al la cadena de caracteres que hemos introducido.
La siguiente entrada en la pila es la que va a asumir la función printf como dirección del segundo parámetro.

Volvamos al primer parámetros que sabemos contiene la dirección de valor que hemos introducido. Y vemos que apunta a la siguiente entrada en la pila. En esa entrada tenemos los la codificación ASCII de el literal "qwerty" cuyos primeros caracteres son: 72657771 y sabemos que la función printf usará ese valor para buscar la siguiente cadena de caracteres.
Normalmente este valor correspondera o bien a una dirección no válida o no accesible por lo que el sistema finalizará anormalmente como hemos visto antes.

Ya estamos en el punto clave: si yo en vez de meter la cadena "qwerty" pudiera introducir la dirección de la memoria de la que deseo sacar información obtendré dicha información.

Vamos a intentar obtener el valor de la cadena de caracteres string1 del programa. En un supuesto real este valor podría ser el hash de una clave que se procesa en ejecución o cualquier otra información comprometedora.

La dirección donde está la cadena que queremos conocer está en 0x12FF70 como se puede si investigamos un poco las entradas en la pila.


Y que no se corresponde con ningún caracter ASCII representable por teclado.


Esto tiene fácil solución ya que es posible intruducir cualquier caracter hexadecimal usando la tecla Alt y el teclado numérico (no vale usar los números que estan encima del teclado alfabético). Además debemos introducirlos al revés (formato litte endian) así pues haremos lo siguiente:



                            Alt 112 (soltar Alt) Alt 255 (soltar Alt) Alt 18 (soltar Alt) Intro
                           
El programa se parará en el siguiente breakpoint y observemos la pila:



Que tiene muy buena pinta, solo tenemos que volver a dar Run (f9) y dejaremos que el programa se pare antes de salir para que podamos ver el resultado:


Conclusiones.


Como se puede ver es tan fácil que asusta y hemos de aprender a vivir con esto y tomar medidas para que lo que hemos visto no pueda suceder.
  • La primera medida es una buena depuración y un buen juego de pruebas antes de liberar el programa. Las prisas y urgencias de última hora son un mal aliado.
  • Dar cierta aleatoriedad a la ubicación de las variables en el programa de manera que no sea fácil predecir donde podemos encontrar información sensible una vez el programa esté en ejecución. Algo que raramente se hace pero que da bastante robustez a los programas.

Y con esto acabamos el estudio de una vulnerabilidad en una función muy usada y que es aplicable al otras funciones que trabajen con cadenas de formato
                           

No hay comentarios:

Publicar un comentario