domingo, 20 de julio de 2014

Programación dirigida por retorno. (y IV) - Encadenando retornos

En los anteriores post hemos ido viendo como era posible aprovechar un desbordamiento de stack para alterar el comportamiento de un programa evitando el mecanismo DEP  que impide la ejecución de código inyectado.

Otra manera de usar la Programación Orientada por retorno es conocida como Ret2libc (Return-to-libc) que en esencia consiste en sobreescribir la dirección de retorno con la llamada a una función del sistema y previamente se han escrito también en la pila los parámetros que pueda necesitar esa función. Por ejemplo la ejecución de un comando, (system("comando parametros")).

Existe una tercera forma de utilizar esta técnica: la construcción de "gadgets".

¿Que es un gadget?.


En la programación orientada por retorno se llama gadget a una serie de instrucciones, cada una seguida por una instrucción ret, que encadenadas permiten la ejecución de instrucciones.



Dicho así suena poco comprensible, lo mejor es verlo con un ejemplo.



Al ejecutarse la primera intrucción ret esta nos lleva a estas instrucciones



Una vez se ejecuta  primera instrucción  el registro A contiene el valor



Y la pila tiene el valor


por lo que siguiente instrucción nos lleva a la dirección apuntada por ese valor:



Las siguiente instrucción cargara el registro ECX el valor:




Y deja al valor para ser usado por la RET que le sigue.



La última instrucción moverá el valor del registro EAX a la dirección de memoria apuntada por el registro ECX.

En resumen hemos realizado lo siguiente:




La imagen más descriptiva de lo que es un gadget es aquellas notas que veíamos en las películas cuando los secuestradores enviaban un mensaje con las instrucciones para el dinero y este mensaje estaba formado por recortes de periódicos:



Una vez entendido como funciona el encadenado de returns la construcción de un gadget no es complicada aunque sí un poco laboriosa.

Hemos de identificar en el proceso un juego de instrucciones que nos sean válidas y que estén seguidas de un return. Recorreremos para ello todas las bibliotecas compartida, haremos un inventario de cada return y su instrucción precendent, y anotaremos la dirección de la instruccion anterior.

Armados ya con este inventario, solo tendremos que crear la adecuada sentencia para incorporar en la pila: valores para la instucción que queremos ejecutar y  dirección de la instrucción siguiente que necesitamos.

Lo dicho, fácil pero laborioso.

Y con este post finalizamos esta introducción a la Return Oriented Programming pero seguiremos profundizando en otras técnicas la explotación y defensa de los desbordamientos de buffer. 








martes, 8 de julio de 2014

Programación dirigida por retorno. (III) - Atacando el programa

El último post acababa con este figura:




En el que le flecha verde indica el flujo del programa cuando no coincide las contraseñas y la flecha roja señala lo que queremos hacer: que la intrucción RET nos lleve a la dirección de memoria 0x4017F2. 

Para conseguirlo sólo tendremos que colocar en la entrada adecuada de la pila el valor 0x4017F2 y habremos conseguido anular el control de contraseñas. 

Damos nuevamente una ejecución al programa y previamente pondremos un breakpoint en la instrucción LEAVE, cuando el programa se detenga analizaremos la pila: 



Podemos ver, de abajo a arriba y a partir de la dirección 0x22FF18 el espacio reservado para las variables in1 e in2.
A continuación a continuación la memoria reservado para la matriz arr[20] que al ser de tipo integer tiene cada entrada tiene una longitud de 4 octetos. Y encima de ella está la matriz var[20]  que tiene una longitud de 20 octetos. 
Como dije en el segundo post de esta serie inicialicé las matrices con los valores 6 y 7 para que nos fuera más fácil indentificar dentro de la pila. 

Los valores que tenemos en estas variables son:

in1: 0x02
in2: 0x5D (Dec 93)

La tercera entrada de la matriz arr (arr[2]) con el valor 0x5D y el string var tiene en la cadena de caracteres "93" con su finalización en 0x00.

Un poco más abajo encontramos una dirección  que también pertenece a nuestro programa y que por su proximidad tiene todas las probabilidades de ser la dirección que debemos modificar. Lo comprobamos pulsando F7 (paso a paso) y confirmamos nuestra suposición. 

Esta entrada dentro de la pila está siete palabras más allá de la finalización de la matriz arr (en arr[26]). Ya sabemos que colocar en el primer registro del fichero de parámetros. 
El segundo registro contendrá la dirección a donde queremos dirigir el retorno que, como hemos dicho arriba, es la dirección 0x4017F2 (4200434 en decimal).
Nuestro fichero de parámetros debe quedar entonces así:

26
4200434

Hacemos una nueva ejecución con esos valores y vamos a la ventana de ejecución vemos:



Recordemos el código fuente: 

if (arr[in1] != tabla[in1])
{
printf("La coordenada no es correcta\n");
return ;
}
printf("La coordenada es correcta\n");
printf(mensaje4);

Al no ser correcta la contraseña vemos el mensaje que nos lo indica y a continuación el programa debería de salir, pero hemos aprovechado una vulnerabilidad del programa que permite aprovechar un desbordamiento de pila y hemos alterado la dirección de retorno para en vez de salir nos lleve a la parte de código que se ejecuta cuando la contraseña es correcta. 

Y ni siquiera el tener el bit NX / DX activado hubiera impedido explotar esta vulnerabilidad......