Utumno - [OverTheWire]
![Cover Image for Utumno - [OverTheWire]](/assets/images/blog/Utumno-overthewire/Utumno.jpg)

Introducción
Utumno son una serie de retos que tienen que ver con la explotación y el reversing de binarios, más avanzados que Behemoth. Cada vez que solucionas un reto obtienes acceso a la contraseña empleada para acceder al siguiente nivel.
Para acceder a los retos tienes que emplear SSH.
Los retos se encuentran en :
Y las credenciales en:
Nota: Si es tu primera vez en el mundo de la explotación binaria, te recomiendo que empieces leyendo mis posts de Protostar, Narnia y Behemoth antes que comenzar por este nivel, pues se darán por hecho algunos conocimientos.
Level 0
El primer nivel es un ejecutable el cual quiere que leamos su contenido.
Sin embargo, no podemos leer nada porque no tenemos permisos de lectura, esto significa que no podemos usar programas como ltrace, gdb ni scp (para bajarlo a nuestra máquina y analizarlo allí).
Después de buscar bastante por Internet encontré este post, donde se explica como modificar la ejecución de un programa a través de la suplantación de librerías, empleadas por el binario.
Para comprobar si el secuestro de librerías funciona, se ha creado el siguiente programa en C, que contiene la función puts la cual imprime "HOLA", en vez de lo introducido como parámetro.
Es necesario compilarlo como una librería compartida de 32 bits. Para ello, se emplea el siguiente comando
Ahora, solo queda ejecutarlo, creando la variable de entorno LD_PRELOAD, que sobrescribe la librería empleada para la función puts, obteniendo el siguiente resultado.
Ahora que ya sabemos que es vulnerable a este tipo de ataques, podemos emplear printf dentro de nuestra función puts. Así nos muestrará todas las cadenas de caracteres que se encuentran dentro de nuestro programa.
Finalmente, solo queda compilarlo y ejecutarlo, obteniendo la contraseña para el siguiente nivel.
Level 1
Como siempre mediante ltrace jugamos un poquito con el programa para ver que hace.
El programa necesita que le pasemos como argumento la ruta de una carpeta, después examina en la carpeta archivos que contenga los tres primeros caracteres sh_. Si creamos una carpeta sh_AAAA, podemos observar como se produce una Segmentation Fault.
Usando Ghidra, podemos comprobar que nuestras suposiciones son ciertas.
Si existe un archivo que comience por sh_, se obtienen los caracteres posteriores y se llama a la función run, que ejecuta los caracteres como si fuese una función en ensamblador.
Lamentablmente, el típico shell script que nos descargamos de shell-storm.org no funciona correctamente porque contiene caracteres especiales que son interpretados por bash y que no nos permitiría crear un archivo con esos caracteres.
Para poder crear un código ensamblador que ejecutase el fichero sh, se he empleado el siguiente post. El resultado es el siguiente:
Básicamente, lo que hace este código, es realizar un system interrupt para la función sys_execve la cual recibe como parámetro el fichero que se desea ejecutar. En nuestro caso, lo he llamado hack, que no es más que un link simbolico a /bin/sh, pues no podemos crear un archivo que contenga el caracter /.
Como comentario, la penúltima instrucción no puede ser mov eax,0xb debido a que tras su compilación genera null bytes evitando que podamos crear un archivo en la terminal.
Empleando defuse.ca y el código ensamblador anterior, obtenemos la siguiente cadena en hexadecimal.
Para crear el archivo empleamos el siguiente comando.
Finalmente, solo necesitamos ejecutar el programa pasando la carpeta donde se encuentra el simbolic link y el el archivo sh_.
Level 2
Tras varias experimentos con el programa, lo único que he conseguido obtener es la cadena de caracteres "Aw.."
Por lo tanto, he procedido a analizarlo con con ghrida, obteniendo el siguiente código.
Comprueba si el número de argumentos es diferente a 0 y si no lo es almacena el valor del décimo argumento en la variable buffer.
Parece ser que para poder realizar nuestro Buffer Overflow, necesitamos que el número de argumentos pasados al programa sea 0. Y uno podría pensar que eso es imposible, ya que el número de argumentos siempre es 1, que coincide con el nombre del programa.
Pues bien, eso no es del todo cierto, porque si creamos un programa que ejecute a /utumno/utumno2, podemos no pasarle parámetros y utumno2 lo trataré como si no le hubiésemos introducido ningún argumento.
Al ejecutarlo vemos que se produce una violación del segmento ya que esta leyendo el valor del décimo argumento el cual no se encuentra inicializado.
Entonces, ¿Cómo podemos sobrescribir la variable argv[10] sino podemos pasar argumentos al programa?. Pues bien, la solución esta en la variables del entorno y la estructura del stack. Debido a que no existen los argumentos en el stack, las direcciones de entorno ocuparan su lugar, por lo que si generamos 10 variables de entorno podremos sobrescribir la variable argv[10].

Para comprobar esta teoría se ha creado el siguiente programa.
Al ejecutarlo y analizarlo con ltrace, vemos que se lee la variable 10 que tiene el valor "JJJJ".
Ahora tenemos que comprobar cuantos caracteres son necesarios para que se produzca el Buffer Ovewflow.
La dirección de retorno se ha sobrescrito con "E"s, por lo que solo necesitamos 20 caracteres para sobrescribir la dirección de retorno.
El shellcode utilizado es el mismo que se empleo en behemoth level 1, solo que añadiendo la dirección del jmp esp a mano (Los 4 primeros bytes del shellcode).
Finalmente, solo queda ejecutarlo, obteniendo la contraseña para el siguiente nivel.
Level 3
Ejecutando el programa a simple vista parece que solamente pide valores sin ningún tipo de reacción.
Decompilandolo con ghidra, podemos ver que el programa solicita 2 chars: el primero determina la posición del vector b donde se escribirá el valor del segundo getchar.
Como tenemos control sobre la posición de escritura del vector b, podemos sobrescribir cualquier dirección de memoria dentro del stack.
Después de mucho prueba y error, encontramos que a partir de la posición 40 (0x28en hexadecimal) podemos introducir una A en la dirección de retorno del programa.
Ahora tenemos que encontrar un valor que realizando la operación xor con(char)i * '\x03' , nos de los valores 0x29,0x29,0x2a,0xb . Como se trata de una operación XOR, que se basa en los siguiente principios:

El que nos interesa es el último. Gracias a el podemos generar unos valores que al producirse la operación XOR nos devuelva las direcciones de memoria en las que queremos escribir. Para un mejor entendimiento, se pone el siguiente ejemplo.
Realizando el mismo proceso con las demás direcciones obtenemos los siguiente valores:
Una vez los tenemos todos y ejecutado el programa, podemos observar que hemos sobrescrito correctamente la dirección de retorno.
Ahora necesitamos saber a donde saltar, como solamente podemos sobrescribir la dirección de retorno se ha decidido saltar a una variable de entorno llamada EGG, inicializada con nuestro shellcode para que nos imprima el contenido del fichero /etc/utumno_pass/utumno4, el assembly code es el siguiente:
Nota: Se hace de esta forma porque no se ha conseguido ejecutar una shell interactiva.
Tras compilarlo en defuse.ca, obtenemos el siguiente código.
Ahora necesitamos obtener una dirección de memoria en el stack que apunte a nuestro nop slide. Para ello se ha empleado GDB, buscando los nops.
Se ha elegido la dirección 0xffffde2c.
Finalmente, solamente queda ejecutar el programa con los valores obtenidos anteriormente.
Level 4
Sorprendentemente, en el nivel 4 ya tenemos un Segmentation Fault nada más ejecutar el programa y que si le pasamos un argumento, este es empleado en la función atoi y después se llama a memcpy
Empleando Ghidra, se puede apreciar que el programa usa el primer argumento de entrada como parámetro de la función atio, que transforma los números pasados como string a int y si le pasamos una letra devuelve 0.
Después, comprueba que el valor es menor a 63, casteando la variable _na ushort, cuyo valor máximo es 65535, por lo que podemos realizar un Integer Overflow. Además, gracias al Integer Overflow podemos escribir más de 63 caracteres en el stack, por lo que podremos realizar un Buffer Overflow.
Si escribimos 65536, podemos ver con ltrace como el programa no solamente copia lo que le hemos introducido como argumento, sino que también copia todas las variables de entorno de nuestra terminal.
Añadiendo 655520 caracteres como argumento se sobrescribe la dirección de retorno.
Tras prueba y error, encontramos que escribiendo 65286 caracteres y cuatro 'B's sobrescribimos la dirección de retorno con Bs.
Por lo tanto, solo queda sustituir las Bs por el payload que empleamos en el Level 2, que realiza un jmp ESP + <SHELLCODE>, y ejecutar el programa, para obtener una shell.
Level 5
Al ejecutar el nivel 5, me doy cuenta de que obtenemos la misma salida por pantalla que en el Level 2.
Por lo que procedo a decompilarlo con Ghidra para ver si han añadido algo nuevo.
En este nivel el argumento 10 es pasado como argumento de la función hihi, que comprueba si la longitud de la cadena es menor que 20, copiandola en caso afirmativo.
Por lo tanto, no es posible realizar la misma técnica 2 veces, ya que solamente nuestro exploit ocupa más de 21 bytes.
Sin embargo, nada nos impide sobrescribir la dirección de retorno por la dirección de una variable de entorno, que contiene la dirección de otra variable de entorno. Así, al ejecutarse el programa, saltará a la primera variable de entorno, leerá su contendio y saltará a la segunda variable de entorno donde se encuentra nuestro shellcode.
Para saber donde se encuentra nuestro shellcode se ha empleado GDB, eligiendo la dirección 0xffffdfa8.
El exploit quedaría de la siguiente forma.
Finalmente, solo queda ejecutarlo y obtendríamos nuestra shell.
Level 6
En el level 6 son necesarios 3 valores, los cuales son tratados de maneras diferentes.
Empleando Ghidra podemos ver que el programa pide como el programa comprueba si ha recibido 3 argumentos. En caso afirmativo, reserva 32 bytes en el heap, almacenando la dirección de memoria en el atributo p de la estructura a. Compuesta por un vector de diez valores de tipo entero y un puntero de tipo char.

Posteriormente, a través de la función strtoul convierte el primer argumento a base 10 y el segundo argumento a base 16 (Hexadecimal)
Finalmente, si la variable pos es menor que 10, se almacena el segundo argumento en la tabla de la estructura b, en la posición pos; y se copia el argumento 3 a la dirección almacenada en el atributo p, de la estructura b.
De forma parecida al nivel anterior, si se escribe un valor negativo se produce un Integer Overflow. Esto es debido porque se esta casteando una variable de tipo unsigned long int a int, por lo que casualmente podemos sobrescribir la dirección de retorno con lo introducido como segundo argumento.
Sin embargo, empleando los mismos argumentos en GDB obtenemos una violación de segmento diferente.
Jugando un poco más con GDB, me doy cuenta de que el segundo argumento puede ser empleado para indicar donde se quiere escribir el valor del tercer argumento.
Por tanto, podemos sobrescribir la dirección de la función printf en la tabla plt, por la dirección de nuestro shellcode que guardaremos en una variable de entorno.
Con GDB obtenemos una dirección de nuestro "nop slide". Se ha elegido la dirección 0xffffde30.
Además, también podemos obtener la dirección a sobrescribir de printf.
Finalmente, solo queda introducirlo todo como argumento y obtendremos nuestra shell.
Level 7
En el último nivel si introducimos un valor como argumento, nos muestra una cadena de caracteres, llama a la función setjmp, copia lo introducido como argumento y llama a la función longjmp.
Empleando Ghidra, podemos ver como el primer argumento es pasado como argumento a la función vuln, que ejecuta las funcionas anteriormente mencionadas y copia el parámetro en la variable buf.
Nota: La segunda función se ejecuta cuando se llama a jmp(0x17), pero solo se aprecia en GDB.
Aunque puede parecer un simple Buffer Overflow, no lo es del todo, pues las funciones setjmpy longjmp son las encargadas de guardar y restaurar el estado del programa (Los registros del programa). Por lo que complica la sobreescritura de la dirección de retorno, como se puede ver en la ejecución de GDB, ya que varían los últimos bytes de la dirección.
Si en vez de 'C's empleamos una dirección del stack que controlamos, por el ejemplo alguna dirección dentro del buffer. Por ejemplo 0xffffd504.
Vemos que podemos acceder a nuestro buffer sin problema.
No obstante si declaramos una variable de entorno con nuestro shellcode e intentamos acceder a él directamente nos salta un error.
Esto es debido a que emplea el valor de la dirección de memoria, como dirección para ejecutar la instrucción que se encuentre en esa dirección de memoria. Entonces, necesitamos escribir la dirección de memoria de nuestro shellcode en el argumento que nosotros le pasamos y obtener la dirección de memoria donde se encuentra almacenado nuestro argumento para sobrescribir la dirección de retorno.

Para poder obtener la dirección donde se almacena nuestro argumento se ha empleado ltrace (0xffffd49c).
Finalmente, una vez obtenida la dirección del comienzo de nuestro buffer solo falta ejecutarlo, obteniendo nuestra shell.
Nota: Puede ser que tengas que jugar con los últimos bytes de la última dirección, pues los últimos bytes varían como se dijo al principio del nivel, pues a la hora de resolver el reto tuve que jugar con ellos, pero a la hora de realizar el post, me ha funcionado sin tener que modificar nada.
Esto se debe a que la funciónsiglongjmp suma un valor aleatorio a los últimos bytes de la dirección de retorno como mecanismo de seguridad.