STACK-ONE amd64
Este nivel presenta el concepto de modificar variables a valores específicos en el programa y como se encuentran dichas variables guardadas en memoria.
CÓDIGO FUENTE
Tras analizar el código fuente anterior podemos ver que es prácticamente similar al código de Stack-Zero pero con la diferencia de que el condicional IF comprueba que el valor de locals.changeme
se ha modificado por 0x496c5962
y no únicamente que se ha modificado.
Por otro lado, el input no se introduce por stdin a través de una función gets()
sino que se introduce como argumento y se le da el valor a locals.buffer con una función strcpy()
. Aquí tenemos nuestra vulnerabilidad:
strcpy(3) - Linux man page
(...)
Bugs
If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there's enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.
(...)
Por tanto podemos dividir el programa en diferentes partes:
Declaración de las variables en una estructura.
Comprobación de que existe un argumento (si no lo hay, lanza un error por stderr). Se da valor a las variables:
locals.changeme
tiene el valor de 0.locals.buffer
coge el valor del argumento con la función vulnerable strcpy().
Comprobación del valor de locals.changeme
. Si coincide con el valor marcado da un resultado con puts() y sino da otro con printf().
En el último utiliza printf() por que referencia a una variable dentro del texto. Si el texto es estático, se utilizará puts().
Como podemos destacar del análisis del código fuente, tenemos dos decisiones lógicas dentro de la ejecución de main:
Comprobar si argc < 2.
Comprobar si locals.changeme = 0x496c5962.
De todo esto podemos imaginar que si genero el mismo payload que en el ejercicio anterior pero con el valor 0x496c5962
en vez del valor 0x43434343
, ganaré el reto.
PREPARANDO EL EXPLOIT
El programa es bastante similar al anterior por lo que la información podemos reutilizarla en su mayoría. Solo cambia el vector de ataque (strcpy() en vez de gets()) y como resultado de esto, que utilizamos un argumento y no stdin para introducir los datos.
Aún podemos desbordar el buffer con 64 + N caracteres solo que en este caso, changeme debe contener el valor 0x496c5962
. Esto lo podemos conseguir con un Payload en Python parecido al del ejercicio anterior. Esto lo haremos más adelante.
Para empezar vamos a utilizar rabin2 para obtener información del binario:
Como en el anterior, el binario no utiliza ningún mecanismo de seguridad.
También podemos comprobarlo con checksec
.
Por tanto, el exploit en Python sería una cosa así:
Utilizamos la función pack del módulo struct para introducir la dirección de memoria en formato Little Endian.
Por otro lado, deberíamos preparar también la plantilla de rarun2:
Como podemos ver, para coger el contenido del archivo en vez de un string literal se necesita utilizar el símbolo @ antes de la ruta del payload.
Solo así podemos hacer que funcione el argumento como queremos.
USANDO EL DEPURADOR
Como siempre que utilizamos el depurador, tras abrir el archivo procedemos a analizar todo y listar las funciones:
Después podemos decidir si utilizamos pdf main
(print disassembly from) o s main
(seek) para colocarse ya en las direcciones de memoria de main.
Pasamos a modo gráfico para ver que efectivamente hay dos puntos de decisión en el camino principal, como habíamos visto en el código fuente:
Para mejorar nuestro entendimiento del código ensamblador vamos a ponerlo al lado del código fuente:
Ahora en el modo visual navegamos por los modos con p
y aumentamos los bytes de stack que se muestran utilizando :
Con esta visual vamos a marcar un breakpoint justo antes de la llamada a la función strcpy():
Como podemos ver en el stack, la información se ha guardado invirtiendo el orden de los bytes, esto se debe a que la información se guarda en formato Little Endian.
En la última imagen vemos como se ha sobreescrito rax y ahora contiene el valor que necesitamos para superar el reto.
Para conseguir el mismo resultado fuera del depurador debemos utilizar uno de los siguientes comandos:
Última actualización