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. 








No hay comentarios:

Publicar un comentario