pwnfreehard

Forks and Knives

hackthebox

Task: a forked Linux TCP service with PIE, NX, canary, Full RELRO, an off-by-one admin bypass, a reservation format string, and a staged stack overflow. Solution: use the NUL overwrite to reach manager functions, leak libc and stack via fprintf, brute-force the shared canary across forked children, recover the randomized flag filename with getdents64, then finish with an ORW ROP chain.

$ ls tags/ techniques/
off_by_one_admin_bypassformat_string_libc_leakfork_canary_bruteforcestack_overflow_roporw_ropgetdents64_filename_recovery

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Forks and Knives — HackTheBox

Description

Welcome to the Forks & Knives restaurant!

This was a forked TCP pwn service, not a web task. The binary exposed a restaurant menu over the network and kept per-client state such as the user name, reservations, and order data.

Remote target: 154.57.164.78:30845.

Files:

  • server
  • libc.so.6
  • Dockerfile

Recon

The first useful strings immediately showed the challenge shape:

  • Welcome to the Forks & Knives restaurant!
  • Can I have your name please?
  • Reserve a table
  • Place an order
  • Login as manager
  • View reservations
  • Clear reservations

That made it clear this was a classic menu-driven Linux service. There was no HTTP parsing anywhere, and the target itself behaved like a forked TCP daemon: every incoming connection got a fresh child process.

checksec on the binary:

ProtectionStatus
PIEEnabled
NXEnabled
CanaryEnabled
RELROFull

So the exploitation plan had to avoid GOT overwrites and needed either a canary leak or a reliable canary bypass. Because the service forks, the stack canary is inherited by children, which turned brute force into a viable primitive.

Reversing Findings

Global state

The early input handler stores the customer name in a global buffer:

  • name at 0x4030
  • adjacent admin flag at 0x4040

The name logic reads up to 16 bytes and then writes a trailing \0 at name[len].

That is safe for lengths 0..15, but exactly 16 bytes makes the terminator land one byte past the buffer, on top of the neighboring auth state.

Bug 1 — off-by-one NUL admin bypass

...

$ grep --similar

Similar writeups