Challenge / Pwn

Restaurant

Restaurant is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

EasyPublished 2025-04-18Sanitized local writeup

Scenario

Restaurant attack path

Restaurant is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

Objective

Challenge walkthrough focused on Pwn evidence, validation, and reusable operator lessons.

Restaurant sanitized attack graph

Walkthrough flow

01

Extract the HTB archive and identify the target ELF...

02

Use static disassembly to audit the menu handlers.

03

Identify fill() as the vulnerable path: a large...

04

Derive saved-RIP overwrite offset from the stack...

05

Check mitigations: NX on, no canary, no PIE, partial...

Source coverage

High source coverage

Status: complete. This article is generated from 6 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.

100% coverage
Evidence verdict

High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.

  • Pwn/Restaurant/writeup.md
  • htb-challenge/Pwn/Restaurant/notes.md
  • htb-challenge/Pwn/Restaurant/memory-summary.md
  • htb-challenge/Pwn/Restaurant/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Pwn__Restaurant__memory-summary.md.2e28b074fa.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Pwn__Restaurant__notes.md.e0fe44637a.md

Technical Walkthrough

Writeup

Challenge

  • Name: Restaurant
  • Category: Pwn
  • Difficulty: Easy
  • Mode: hybrid

Summary

Restaurant is a classic x86-64 ret2libc challenge. The fill() function reads 0x400 bytes into a 0x20 stack buffer, giving control of the saved return address at offset 40. The binary has NX enabled, no stack canary, no PIE, and partial RELRO. The final exploit leaks puts from the GOT to compute the provided libc base, returns to main, then uses libc gadgets to call execve('/bin/sh', 0, 0) and read the flag from the spawned shell.

Artifact Inventory

Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.

  • files/a12c733b-e972-4dfc-9e2c-c943c63fe653.zip: original HTB archive.
  • analysis/extracted/pwn_restaurant/restaurant: target ELF.
  • analysis/extracted/pwn_restaurant/libc.so.6: provided libc used for offsets.
  • analysis/function-disasm.txt: disassembly of setup, fill, drink, main, and CSU.
  • analysis/elf-mitigations.txt: static mitigation summary.
  • analysis/gadgets.txt: binary gadget scan.
  • analysis/multileak-check.txt: remote libc-match validation using several GOT leaks.
  • solve/solve.py: final reproducible exploit.

Analysis

  1. file identifies restaurant as a 64-bit Linux ELF, dynamically linked and not stripped.
  2. Static mitigation parsing shows an executable type EXEC binary with PIE disabled, NX enabled, GNU RELRO present but no BIND_NOW, and no stack canary symbol.
  3. fill() allocates 0x20 bytes on the stack and calls read(0, buffer, 0x400).
  4. The saved RIP offset is therefore 0x20 + 8 = 40 bytes.
  5. The binary has a pop rdi; ret gadget at 0x4010a3, puts@plt at 0x400650, puts@got at 0x601fa8, and main at 0x400f68.
  6. Stage one overflows fill(), calls puts(puts@got), and returns to main.
  7. The leaked puts address minus the provided libc puts offset gives a page-aligned libc base. Multi-symbol leak checks confirmed the remote uses the provided libc.
  8. system() was tested but did not produce a usable command channel in this deployment.
  9. The final stage uses libc gadgets for pop rsi; ret and pop rdx; ret, then calls execve('/bin/sh', 0, 0).
  10. The solver sends cat commands to the spawned shell and captures the returned HTB-format flag.

Solve

Run:

bash
python3 Pwn/Restaurant/solve/solve.py --host <TARGET> --port 30685

The script performs the two-stage exploit, prints only the HTB-format flag, and writes a sanitized transcript when --transcript is supplied.

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • If system('/bin/sh') does not yield interaction, verify libc matching first, then switch to direct execve with explicit register-control gadgets.
  • Keep the receiver idle window long enough after spawning a shell; otherwise the exploit can close the socket after the immediate vulnerable-program output but before shell command output arrives.
  • Multi-symbol GOT leaks are a cheap way to validate that a remote service is using the provided libc.

Source-Backed Dossier

The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.

Notes

Scope

  • Challenge: Restaurant
  • Category: Pwn
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T11:05:40Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c733b-e972-4dfc-9e2c-c943c63fe653.zip883854<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T11:05:40Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T11:06:02Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T11:07:52Zhypothesis recordedhypothesis-board.mdUse fill() stack overflow for ret2libc: leak puts@got with puts@plt, return to main, compute libc base, then call system('/bin/sh')MediumBuild local payload with 40-byte offset and one remote leak stage; parse leaked puts pointer and confirm it maps to provided libc alignment
2026-06-10T11:08:11Zresearch recordanalysis/research/research-records.mdResearch tagged MISSINGMediumValidate against current evidence
2026-06-10T11:08:47Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T11:08:48Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T110848910767Z-88239e3d.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T11:09:23Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T11:09:35Zevaluatoranalysis/evaluator-20260610T110935705108Z-c2edc4ea.mdProceedHighRun gate before exploit, then execute solve/solve.py through challenge_exec.
2026-06-10T11:24:22Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T11:25:45Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T11:27:11Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The archive extracts to restaurant and a matching libc.so.6.
  • restaurant is an x86-64 Linux ELF, dynamically linked, not stripped, non-PIE.
  • Static mitigation check: NX enabled, no stack canary, partial RELRO, PIE disabled.
  • fill() allocates a 0x20 stack buffer and calls read(0, buffer, 0x400), giving a direct saved-RIP overwrite.
  • Saved RIP offset is 0x20 + 0x8 = 40 bytes.
  • Useful binary addresses:

- pop rdi; ret: 0x4010a3

- plain ret: 0x40063e

- puts@plt: 0x400650

- puts@got: 0x601fa8

- main: 0x400f68

  • Provided libc offsets:

- puts: 0x80aa0

- /bin/sh: 0x1b3e1a

- execve: 0xe4c00

- pop rsi; ret: libc offset 0x23eea

- pop rdx; ret: libc offset 0x130546

  • Multi-symbol leak testing confirmed the remote libc matches the provided libc.
  • system()-based chains returned without useful shell output, so the final solver uses a direct execve('/bin/sh', 0, 0) chain.
  • Final exploit: choose menu option 1, overflow fill(), leak puts@got via puts@plt, return to main, compute libc base, overflow again into execve('/bin/sh'), then read flag.txt through the spawned shell.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Pwn
  • Challenge: Restaurant
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extract the HTB archive and identify the target ELF plus provided libc.
  2. Use static disassembly to audit the menu handlers.
  3. Identify fill() as the vulnerable path: a large read() into a small stack buffer.
  4. Derive saved-RIP overwrite offset from the stack frame.
  5. Check mitigations: NX on, no canary, no PIE, partial RELRO.
  6. Build stage one ROP to leak puts@got with puts@plt, then return to main.
  7. Compute libc base from the leaked puts address and validate the remote libc against the provided libc using additional GOT leaks.
  8. Test system() and close it as unreliable for this deployment.
  9. Build final stage with libc gadgets to call execve('/bin/sh', 0, 0).
  10. Send shell commands to read the flag and capture through the harness.

Reusable Lessons

  • Direct execve('/bin/sh', 0, 0) can be more reliable than system('/bin/sh') when system() returns without useful command output.
  • Verify remote libc with more than one symbol when a ret2libc chain behaves unexpectedly.
  • Receiver idle windows matter for shell-based pwn exploits; do not close the socket immediately after the vulnerable program emits its final line.
  • Sanitized transcripts should redact flag-shaped output outside loot/.

Dead Ends

  • RAG/private memory did not contain a matching Restaurant solve pattern.
  • system()-based chains were tested but did not yield a usable shell or command output.

Tool Quirks

  • Docker was installed but the daemon was unavailable, so local Linux execution/debugging was not used.
  • Local pwntools/gdb/checksec were unavailable; the solve used static ELF parsing, objdump, nm, Python sockets, and remote validation.
  • The vulnerable printf("Enjoy your %s") prints a few non-null ROP bytes before the GOT leak, so leak parsing must skip the printed payload prefix.

Evidence Paths

  • analysis/function-disasm.txt
  • analysis/elf-mitigations.txt
  • analysis/gadgets.txt
  • analysis/libc-symbols-expanded.txt
  • analysis/multileak-check.txt
  • analysis/execve-shell-debug-rerun.txt
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Hypothesis Board

Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Use fill() stack overflow for ret2libc: leak puts@got with puts@plt, return to main, compute libc base, then call system('/bin/sh')fill reads 0x400 bytes into a 0x20 stack buffer; NX enabled, no canary, no PIE, partial RELRO; provided libc has puts/system/binsh offsetsBuild local payload with 40-byte offset and one remote leak stage; parse leaked puts pointer and confirm it maps to provided libc alignmentMediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Pwn
  • Challenge: Restaurant
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extract the HTB archive and identify the target ELF plus provided libc.
  2. Use static disassembly to audit the menu handlers.
  3. Identify fill() as the vulnerable path: a large read() into a small stack buffer.
  4. Derive saved-RIP overwrite offset from the stack frame.
  5. Check mitigations: NX on, no canary, no PIE, partial RELRO.
  6. Build stage one ROP to leak puts@got with puts@plt, then return to main.
  7. Compute libc base from the leaked puts address and validate the remote libc against the provided libc using additional GOT leaks.
  8. Test system() and close it as unreliable for this deployment.
  9. Build final stage with libc gadgets to call execve('/bin/sh', 0, 0).
  10. Send shell commands to read the flag and capture through the harness.

Reusable Lessons

  • Direct execve('/bin/sh', 0, 0) can be more reliable than system('/bin/sh') when system() returns without useful command output.
  • Verify remote libc with more than one symbol when a ret2libc chain behaves unexpectedly.
  • Receiver idle windows matter for shell-based pwn exploits; do not close the socket immediately after the vulnerable program emits its final line.
  • Sanitized transcripts should redact flag-shaped output outside loot/.

Dead Ends

  • RAG/private memory did not contain a matching Restaurant solve pattern.
  • system()-based chains were tested but did not yield a usable shell or command output.

Tool Quirks

  • Docker was installed but the daemon was unavailable, so local Linux execution/debugging was not used.
  • Local pwntools/gdb/checksec were unavailable; the solve used static ELF parsing, objdump, nm, Python sockets, and remote validation.
  • The vulnerable printf("Enjoy your %s") prints a few non-null ROP bytes before the GOT leak, so leak parsing must skip the printed payload prefix.

Evidence Paths

  • analysis/function-disasm.txt
  • analysis/elf-mitigations.txt
  • analysis/gadgets.txt
  • analysis/libc-symbols-expanded.txt
  • analysis/multileak-check.txt
  • analysis/execve-shell-debug-rerun.txt
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: Restaurant
  • Category: Pwn
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T11:05:40Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c733b-e972-4dfc-9e2c-c943c63fe653.zip883854<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T11:05:40Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T11:06:02Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T11:07:52Zhypothesis recordedhypothesis-board.mdUse fill() stack overflow for ret2libc: leak puts@got with puts@plt, return to main, compute libc base, then call system('/bin/sh')MediumBuild local payload with 40-byte offset and one remote leak stage; parse leaked puts pointer and confirm it maps to provided libc alignment
2026-06-10T11:08:11Zresearch recordanalysis/research/research-records.mdResearch tagged MISSINGMediumValidate against current evidence
2026-06-10T11:08:47Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T11:08:48Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T110848910767Z-88239e3d.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T11:09:23Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T11:09:35Zevaluatoranalysis/evaluator-20260610T110935705108Z-c2edc4ea.mdProceedHighRun gate before exploit, then execute solve/solve.py through challenge_exec.
2026-06-10T11: <REDACTED>
2026-06-10T11: <REDACTED>
2026-06-10T11:27:11Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The archive extracts to restaurant and a matching libc.so.6.
  • restaurant is an x86-64 Linux ELF, dynamically linked, not stripped, non-PIE.
  • Static mitigation check: NX enabled, no stack canary, partial RELRO, PIE disabled.
  • fill() allocates a 0x20 stack buffer and calls read(0, buffer, 0x400), giving a direct saved-RIP overwrite.
  • Saved RIP offset is 0x20 + 0x8 = 40 bytes.
  • Useful binary addresses:

- pop rdi; ret: 0x4010a3

- plain ret: 0x40063e

- puts@plt: 0x400650

- puts@got: 0x601fa8

- main: 0x400f68

  • Provided libc offsets:

- puts: 0x80aa0

- /bin/sh: 0x1b3e1a

- execve: 0xe4c00

- pop rsi; ret: libc offset 0x23eea

- pop rdx; ret: libc offset 0x130546

  • Multi-symbol leak testing confirmed the remote libc matches the provided libc.
  • system()-based chains returned without useful shell output, so the final solver uses a direct execve('/bin/sh', 0, 0) chain.
  • Final exploit: <REDACTED>, overflow fill(), leak puts@got via puts@plt, return to main, compute libc base, overflow again into execve('/bin/sh'), then read flag.txt through the spawned shell.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Technical analogy

How to remember this solve

Think of the challenge as a small system with one rule that matters more than the rest. The solve is finding that rule, validating it, and using it carefully enough to reach the final proof.

For Restaurant, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.