ATAQUE RET2WIN
Ataque Smash the Stack con redirección de Flujo
Análisis del programa vulnerable
Stack 4
¿Qué hace el programa?
Se declaran dos variables locales: cookie
y buf
. Y una función gets(buf)
que toma datos de la entrada estándar y los guarda en buf
. La única diferencia con programas anteriores es que esta vez el valor de cookie
debe ser 0x000d0a00
.
Layout de la pila antes del exploit:
Es idéntico al de programas anteriores.
Antes de ejecutar gets(buf)
el mapa de la pila del programa es el siguiente:
Información útil del mapa de la pila:
Solución anterior: modificar valor de cookie
Probamos con la misma estrategia que en los problemas anteriores, modificando el valor de cookie
para que el condicional se evalúe como verdadero.
Pero observamos que el mensaje ganador no se imprime:
Si debugeamos el programa vulnerable con gdb
podemos observar porqué.
Avanzamos varias instrucciones (con el operador ni
en gdb
) hasta llegar a la evaluación condicional y nos detenemos en la instrucción encargada de comparar el valor de cookie
con 0x000d0a00
:
Si detenemos la ejecución antes de la comparación podemos observar el valor del registro eax
donde está almacenado el valor de cookie
.
Apesar de que en exploit.py
apuntamos a sobreescribir cookie
con el valor \x00\x0a\x0d\x00
, vemos que el registro eax
que almacena el valor de esa variable tiene un valor de 0x00000000
.
Si analizamos la documentación de gets()
(con man gets
) vemos que la lectura de caracteres se interrumpe con el caracter de nueva línea \n
(el carácter de salto de línea ASCII en hexa es 0a
). Por lo tanto la escritura en cookie
de \x00\x0a\x0d\x00
lee 0x00
y se interrumpe por el carácter 0x0a
que es reemplazado por un caracter nulo.
De esta manera cookie
no tiene el valor adecuado y no se cumple la condición.
¿Cuál es la dificultad principal?
En el valor de cookie
hay caracteres que controlan el funcionamiento de gets()
:
gets()
,fgets()
: leen por stdin hasta el byte de control\x0a
.strcpy()
,strlen()
,strcmp()
: manipulan el string hasta\x00
.
Redireccionando el flujo de ejecución
Previamente se usó una estrategia de corrupción de la pila para modificar el valor de una variable local.
No obstante, el ataque Smash the stack tradicional tiene como objetivo controlar el flujo de ejecución del programa vulnerable para ejecutar código malicioso. Para ello es necesario controlar el registro eip
. Este registro no puede ser modificado de manera directa sino que su valor cambia de acuerdo a las instrucciones de máquina. Por ejemplo, la instrucción ret
toma una dirección del tope de la pila y la almacena en eip
para que el flujo de ejecución salte inmediatamente después a ella.
De esta manera si es posible controlar las direcciones de retorno almacenadas en la pila es posible controlar, en última instancia, el valor del registro eip
y por ende el flujo de ejecución.
Para ello este tipo de ataques cuenta con dos pasos. Primero, gracias al desbordamiento de un búfer, se reescribe una dirección de retorno en la pila. Segundo, se inyecta código malicioso en la pila del proceso, para apuntar allí la dirección de retorno. Entonces, esta vez la corrupción de la pila tendrá como objetivo modificar el retorno de una rutina para lograr un salto a una dirección determinada dentro del programa vulnerable.
El objetivo ya no será modificar el valor de cookie
sino aprovecharnos de que printf("you win!\n")
es parte del programa vulnerable. De esta manera con una corrupción de la pila modificaremos la dirección de retorno de main()
para que, al retornar, el flujo de ejecución salte directamente a la línea de código printf("you win!\n")
, sin importar la evaluación de cookie
.
La estrategia será ingresar un input adecuado para sobreescribir la dirección de retorno de main()
almacenada en la pila, y reemplazarla por la dirección de printf()
que imprime el mensaje ganador.
Identificamos la dirección de la llamada a
printf()
Al ejecutar la instrucción leave, se reestablece el tope de la pila (
esp
apunta aebp
) y se actualiza el registroebp
al marco de la función anterior (de forma simplificada correspondería a_start
) con unpop ebp
. En este punto, el tope de la pilaesp
apunta a la dirección de retorno demain()
. La instrucciónret
desapila una dirección del tope de la pila y la almacena en el registroeip
. El programa continúa su ejecución en esa instrucción indicada poreip
.El objetivo es generar un layout de pila tal que la dirección de retorno en el tope de la pila al momento de ejecutar la instrucción
ret
sea la dirección de la primer instrucción delprintf()
(0x0804849c: push 0x8048548
).Planificamos el overflow por entrada estándar, considerando el formato little endian en la dirección de
printf
:
Armamos un archivo en Python para ingresar el input: exploit.py
Consideraciones: en python es posible convertir a formato little endian una dirección importando
struct
y usando la funciónpack
:pack("
Ejecutamos el exploit
La condición que evalúa el valor de
cookie
es falsa, pero después de ejecutarse el código demain()
la dirección de retorno apunta aprintf()
, por lo que se salta allí y se imprimeyou win!
por pantalla.Gráficamente logramos el siguiente resultado:
Para lograr sobreescribir la dirección de retorno de main()
tuvimos que pisar valores importantes como el puntero ebp
almacenado en la pila. Es por ello que luego de imprimir el mensaje ganador se produce una violación de segmento al intentar acceder a direcciones por fuera del mapa de memoria del proceso. Si reconstruyeramos el layout de la pila podríamos lograr una salida del programa sin errores. …………………………………………………………………………………………………………………………………………………
ATAQUE "SMASH THE STACK" CON INYECCIÓN DE CÓDIGO
Análisis del programa vulnerable
Stack 5
¿Qué hace el programa?
El programa es idéntico al de stack4, pero se imprime un mensaje diferente.
Solución anterior: aprovechar mensaje existente
No es de utilidad usar la misma estrategia que en el exploit del stack4, dado que si logramos saltear la evaluación condicional, el mensaje que logramos imprimir no es el deseado.
¿Cuál es la dificultad principal?
¡Ya no podemos saltar al mensaje ganador! El mensaje ganador a imprimir ya no es parte del programa vulnerable. Una opción entonces es crear un propio programa que imprima el mensaje ganador: nuestro shellcode.
RET2WIN
Necesitamos imprimir un mensaje ganador que no está en el programa vulnerable. Si bien es posible pensar diferentes ataques para lograr imprimir el string deseado, es una buena excusa para planificar una estrategia de inyección de código.
La inyección de código malicioso en la pila y su posterior ejecución conforman una técnica clásica para explotar programas vulnerables que es necesario conocer aunque su utilización no sea tan simple en escenarios actuales. Es por ello que aún es necesario deshabilitar artificialmente las mitigaciones que impiden ejecución de código en la pila (X^W) y la aleatoriedad en las direcciones de memoria.
La estrategia de ataque involucra crear un programa o shellcode que imprima por salida estándar el mensaje ganador, inyectarlo en la pila y ejecutarlo.
Layout de la pila deseado:
En buf
ya no va a ir basura sino el shellcode antecedido por varios NOPs. Aprovechamos gets()
para copiar los NOPs y el shellcode como string en la pila en buf
y sobreescribirmos la dirección de retorno para que apunte a los NOPs de buf
.
Programamos el shellcode Nuestro shellcode es un programa simple creado en assembler que con una llamada al sistema imprime “you win!” por salida estándar. Podemos aprender a hacerlo en el artículo sobre shellcodes.
Averiguamos dirección de
buf
en la pila ejecutando el programa vulnerablePlanificamos el overflow por entrada estándar, considerando el formato little endian:
Armamos un archivo en Python para ingresar el input
Ejecutamos el exploit
Gráficamente logramos el siguiente resultado:
Es interesante tener en cuenta que el shellcode utilizado realiza un syscall write
para imprimir el mensaje y luego un syscall exit
para finalizar el proceso exitosamente. A diferencia de lo que sucedía en el stack4 (ejercicio en el que la destrucción del layout de la pila provocaba una violación de segmento), en este caso por el modo en que fue construido el shellcode el programa finaliza sin errores.
Lograr privilegios de root
En escenarios reales el objetivo no será imprimir un mensaje ganador sino lograr privilegios de root para exponer archivos e información privada, manipular logs, etc.
Existen varias estrategias para lograr una shell con privilegios de root o directamente para escalar privilegios a partir de una shell, que exceden el objetivo de esta guía en este punto. Por ejemplo en un escenario en el que se ataca un binario compilado con setuid root, si se logra que el programa vulnerable realice una syscall execve y ejecute una shell con execve("/bin/sh")
ésta será una root shell. Es por ello que en la compilación del binario ejecutable modificamos los permisos de la siguiente manera:
Última actualización