miércoles, 22 de enero de 2014

Buffer overflow a fondo (I)

La wikipedia define el desbordamiento de buffer (buffer overflow) como:

...un error de software que se produce cuando un programa no controla adecuadamente la cantidad de datos que se copian sobre un área de memoria reservada a tal efecto (buffer): Si dicha cantidad es superior a la capacidad preasignada, los bytes sobrantes se almacenan en zonas de memoria adyacentes, sobrescribiendo su contenido original. Esto constituye un fallo de programación.
Ejemplo:




0 0 0 0 0 0 0 0 0 3
Buffer ABuffer B

A continuación, el programa intenta almacenar la cadena de caracteres "demasiado" en el buffer A, seguido de bytes nulos para marcar el fin de string. Al no validarse la longitud de la cadena, se sobrescribe el valor de B:

'd' 'e' 'm' 'a' 's' 'i' 'a' 'd' 'o' 0
Buffer A Buffer B

El buffer overflow tiene como como consecuencias más previsibles una salida de datos incorrecta o bien una terminación anormal del programa.

Un ataque básico a un servicio que tuviera esta debilidad podría llevar a una denegación de servicio. Pero en determinadas circunstancias esta vulnerabilidad puede llegar a permitir que el atacante tome control de la máquina.

El registro EIP

Es uno de los registros apuntadores y contiene la dirección de la siguiente instrucción a ejecutar, es decir la la dirección de esta instrucción. 

Este registro según se van ejecutando las instrucciones va a aumentando en un múmero igual a la longitud de la instrucción ejecutada.

A diferencia de los registros generales el EIP no se puede modificar de forma directa aunque es posible modificar su valor de forma indirecta  mediante las instruciones JMP (y todas sus variantes: JE, JNZ, .....) y la instrucción RET.

A continuación veremos como a partir de un desbordamiento de buffer es posible llegar a controlar el EIP para dirigirlo hacia donde queramos

Creando el entorno necesario. 

Vamos a trabajar desde un entorno muy minimalista de forma que se pueda realizar sin necesidad de tener que recurrir a una distribución especializadas en pentesting como Backtrack o Kali y sin ni siquiera tener que instalar programas en nuestro equipo Windows.

Los programas que voy a enumerar a continuación son todos ejecutados desde un pendrive sin necesidad de instalación.

  • El depurador Ollydbg
  • Un editor de texto avanzado como notepad++ del que existe una edición portable
  • Un interprete de Python portable. Aunque puede ser válido cualquier otro intérprete aunque no se podrá aprovechar los scripts que acompañan a este post

Ahora necesitaremos un programa tenga esta vulnerabilidad. Vamos a usar la vunerabilidad CVE-2004-2271 que evidentemente no es una 0 Zero Day pero tiene la ventaja de que el programa afectado por esta vulnerabilidad tampoco necesita instalarse y es utilizado en muchas webs para explicar el buffer overflow.

Podéis descargar el programa de desde este enlace. Este programa es un servidor web mínimo, en su configuración por defecto usa el puerto 80. Si no podemos usar ese puerto podemos cambiarlo en el cuadro de diálogo del programa al que se accede haciendo click con el botón derecho del ratón cuando tenemos el cursor situado dentro de la ventana del programa. 

Además necesitaremos dos scripts escritos en python.

  • exployt2.py Nos servirá para ir avanzando en la creación del exploit.
  • creapatern.py que nos creara la plantilla que usaremos para localizar el offset dentro del buffer. 
 

Manos a la obra.


Arrancamos Ollydbg, abrimos el fichero de minishare y damos a run. El programa abrirá  una ventana y podremos observar en una ventana de DOS que tenemos el puerto 80 abierto (o el que hayamos definido).
Editamos el script exployt2.py y descomentamos la sentencia que carga el buffer de GET con una petición normal.






Todo va OK, ahora dejamos descomentada la  carga de buffer con la cadena que va a generar el desbordamiento (tiene una longitud 2220 octetos y todos con la letra  "A"
Nuestro programa finalizará anormalmente y si nos fijamos en el EIP veremos que tiene 0x41414141 que corresponde a cuatro carácteres A. (A veces hemos de repetir el intento varias veces hasta que ocurra el error)
Tambien podremos ver que la pila continua con la cadena que hemos mandado al servidor.



Lo que no tenemos, todavía es posibilidad, saber el desplazamiento dentro del buffer de los caracteres que se cargan en el EIP.
Si mandamos un string secuenciado podremos disitinguir que caracteres van al EIP.
Descomentamos la tercera parte de la construcción del buffer y volvemos a ejecutar el script contra el programa minishare que previamente hemos vuelto a rearrancar y ejecutar bajo Ollydbg.



El programa se vuelve a parar y vemos como el ESP apunta a la cadena Ch7Ch8..... y el EIP tiene el valor  0x36685335 que se corresponde en ASCII a 6hC5 (no olvidar que el procesador usa Little Endian).

Sólo nos queda localizar el desplazamiento de esos caracteres en el buffer. Usamos el editor avanzado notepad++ que nos dice el desplazamiento del caracter en el que se tiene el cursor
No sale 1791 (columna 1792 que es un desplazamiento 1791) y EIP 1787


Solo nos queda verificarlo.
Usamos nuevamente el script exploit2.py  y descomentamos la construcción del último buffer. Rearracamos minishare y volvemos a ejecutar.
Como no nos hemos confundido los valores cuadran.



Solo tenemos que hacer ahora dos cosas:

  • Localizar un valor que colocar para que se cargue en el EIP
  • Sustituir la parte posterior de buffer por el código ejecutable de la shell.

Eso lo veremos en las siguientes entrega




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
                           

Vulnerabilidad en printf (II)


Continuamos analizando la función printf y la vulnerabilidad que lleva incorporada. En el post anterior finalizamos viendo como quitando una variable en la llamada a la función se lograba acceder a la siguiente entrada en la pila.

Lo mismo se hubiera obtenido si en vez de eliminar una variable hubiéramos colocado una definición de formato más.

Partiendo de la llamada original a la función

printf("%s - i = %d - J = %d - k = %d\n", string1, i, j, k);

Hemos llegado a obtener un valor que estaba más afuera del ámbito del programa modificando la llamada

printf("%s - i = %d - J = %d - k = %d\n", string1, i, j);

Y se hubiera obtenido el siguiente valor con la siguiente llamada a la función
printf("%s - i = %d - J = %d - k = %d  %s\n", string1, i, j, k);

Podremos avanzar en la lectura de la pila más allá del ámbito de la función según la siguiente resta:

(Núm. de especificaciones de formato - Núm. de variables)

Ejecutemos nuevamente el programa  hasta el breakpoint que está junto antes de la llamada a la rutina de impresión. Y  observemos la pila.


Según lo visto hasta ahora se podría acceder a cualquier valor, por ejemplo a la cadena ASCII que contiene el valor del valor del nombre del programa.

Pomgamos en nuestro programa entonces la siguiente llamada de función

printf("%x%x%x%x%x%x%x%x%x%x%x%x\n%s");

Compilamos y ejecutamos el programa

 Nos interesa la segunda línea de la salida que es el valor que buscábamos.

Vulnerabilidades y explotación de las mismas.

 Para finalizar  y antes de entrar en el siguiente post donde veremos si es posible ir directamente a una posición de memoria directamente, hay que aclarar la diferencia que hay entre vulnerabilidad y explotación de la vulnerabilidad. 

Es evidente que en este ejemplo, la información que hemos obtenido pues no nos sirve de mucho. Pero podría ocurrir que  una de las entradas en la pila fuera el puntero a una dirección de memoria con información sensible (por ejemplo el hash de una clave). Podríamos obtener ese hash y poder efectuar contra el un ataque de diccionario o de fuerza bruta.
También en este caso  estamos trabajando con un control total sobre la cadena de formato que confeccionamos a nuestro gusto. 
 En una situación real de intento de ataque aprovechando esta vulnerabilidad no tendremos esa posibilidad de forma directa y probablemente debamos llegar a ella a traves de otra vulnerabilidad, quizá un desbordamiento de memoria.
Pero lo que tiene que quedar claro es que una cosa es la existencia de una vulnerabilidad y  otra que esa vulnerabilidad sea explotable para sacar algún beneficio.

. 

Hasta la próxima