STACK-THREE amd64
Última actualización
Última actualización
Este nivel nos enseña a sobrescribir los punteros de funciones para alterar el flujo del binario.
En este caso vamos a mirar primero el ensamblador y después contrastaremos lo descubierto con el código fuente.
Como en todos los anteriores, lo primero es utilizar rabin2
para obtener más información del binario:
Después lo podemos abrir en nuestro depurador, analizar todo y comprobar las funciones para ver qué estamos buscando:
Podemos observar las funciones gets, puts, fflush y exit, por lo que podemos asumir que se utilizan dentro del binario.
La función fflush no ha salido hasta ahora en los ejemplos por lo que vamos a ver que es lo que hace utilizando su página del man:
Para flujos de salida, fflush () fuerza una escritura de todos los datos almacenados en el búfer del espacio de usuario para el flujo de salida o actualización dado a través de la función de escritura subyacente del flujo. Para flujos de entrada, fflush () descarta cualquier dato almacenado en búfer que se haya obtenido del archivo subyacente, pero que la aplicación no haya consumido. El estado abierto de la secuencia no se ve afectado.
Si el argumento de flujo es NULL, fflush () vacía todos los flujos de salida abiertos.
Lo anterior significa que si utilizamos un fflush(stdin)
después de la función gets, borrara el buffer de stdin para que no afecte a otras funciones posteriormente. Por el contrario, si utilizamos fflush(stdout)
después de un printf, nos aseguramos que la información se almacena en el buffer del kernel, no en el de la función printf.
Por otro lado, llegados a este punto vemos algo muy interesante. Además de la función main, también vemos una función llamada complete_level. Ahora toca analizar en profundidad ambas funciones:
main:
complete_level:
Como en el ejercicio anterior, vamos a analizar el código. En este caso vamos a analizar primero main y posteriormente complete_level:
De lo leído anteriormente, vemos que en un momento dado de la función main se llama a una función cuyo puntero se encuentra almacenado en una variable, concretamente en [rbp - 0x10]
.
Además, antes de eso se ha introducido datos por stdin con la función gets en la variable que se encuentra almacenada en [rbp - 0x50]
.
Sabemos que la función gets tiene problemas de seguridad y es explotable para escribir en direcciones de memoria cercanas. En este caso, solo tenemos que escribir 0x50 - 0x10
bytes para empezar a sobrescribir el puntero de la función. Esto son 64 bytes.
Otra cosa a tener en cuenta es: ¿Qué dirección debo poner en el puntero para ganar el reto?
Lo importante para ganar este reto es redirigir el flujo de programa hacia la función complete_level. Por tanto, debemos introducir la dirección de la primera instrucción de este función en el puntero para redirigirme a ella. En este caso 0x0040069d
.
A continuación prepararemos el exploit y después confirmaremos que lo anterior es correcto.
Ahora que ya sabemos cómo funciona el binario y creemos saber como explotarlo, vamos a preparar un exploit en python para comprobar que estábamos en lo cierto:
Con este exploit, podemos ejecutar el binario para comprobar si hemos modificado el valor del puntero y trata de acceder a la función almacenada en 0x43434343
.
Ya hemos comprobado que nuestro analisis del código ensamblador ha sido correcto. Ahora podemos modificar el exploit para obtener el resultado que esperamos y vencer el reto:
Otra vez, no ha sido necesario conocer el código fuente para explotar el binario.
Después de comprobar que el exploit funciona podemos analizar el código fuente para comprobar qué hacía el binario exactamente.