Challenge / Pwn

Forks And Knives

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

MediumPublished 2025-03-21Sanitized local writeup

Scenario

Forks And Knives attack path

Forks And Knives 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.

Forks And Knives sanitized attack graph

Walkthrough flow

01

Binary triage

02

Memory primitive

03

Control-flow hijack

04

Proof captured

Source coverage

High source coverage

Status: complete. This article is generated from 4 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/Forks-and-Knives/writeup.md
  • htb-challenge/Pwn/Forks-and-Knives/notes.md
  • htb-challenge/Pwn/Forks-and-Knives/memory-summary.md
  • htb-challenge/Pwn/Forks-and-Knives/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Forks-and-Knives
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid

Summary

Forks-and-Knives is a fork-per-client menu service with three bugs that chain together:

  1. A 16-byte name input clears the adjacent manager flag with the automatic NUL terminator.
  2. The reservation path passes a short user-controlled string directly to fprintf, creating a file-backed format-string leak that can be viewed through the manager interface.
  3. The order "add more" flow performs a second read at buffer + first_read_count; with a first read of 0x100, the second read crosses the stack canary and saved return address.

The final exploit leaks libc, brute-forces the fork-stable stack canary, then uses ret2libc to attach a shell to the socket and read the flag.

Artifact Inventory

  • files/extracted/pwn_forks_and_knives/challenge/server: stripped PIE x86-64 service.
  • files/extracted/pwn_forks_and_knives/challenge/libc.so.6: provided libc used for offsets.
  • files/extracted/pwn_forks_and_knives/Dockerfile: Ubuntu 22.04 service image exposing the binary on TCP 1337.

Analysis

Relevant evidence:

  • analysis/local-disassembly-notes.md records the manager flag, format string, and overflow locations from disassembly.
  • analysis/remote-readonly-leak-table-v2.txt records compact remote leaks.
  • analysis/remote-libc-offset-byte-match.txt and analysis/remote-libc-releak-after-canary.txt show why the libc leak offset is 0x11491b.
  • analysis/remote-canary-oracle-sanity-v3.txt validates the canary crash oracle.
  • analysis/remote-rop-write-probe-fd4.txt validates libc base, socket fd 4, and ROP delivery before the final shell stage.

The first libc-base attempt used a page-aligned but incorrect offset. Dereferencing %2$s showed the leaked address starts with bytes 48 3d 00, which uniquely matched offset 0x11491b in the provided libc among offsets ending in 0x91b.

Solve

Run:

bash
python3 Pwn/Forks-and-Knives/solve/solve.py --host <TARGET> --port 32185 --output Pwn/Forks-and-Knives/analysis/flag-candidate.txt

The script:

  • leaks libc with the reservation format string;
  • brute-forces the fork-stable canary;
  • sends a final ROP chain using dup2 and execve;
  • extracts an HTB-format flag candidate into analysis/flag-candidate.txt.

Use the harness to store the flag:

bash
python3 scripts/challenge_harness.py capture-flag Pwn/Forks-and-Knives --from analysis/flag-candidate.txt

Flag

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

Lessons

  • Page alignment alone is not enough to identify a libc leak offset; validate with bytes or another independent leak.
  • Forking services make stack canary brute force practical when child crashes do not restart the parent.
  • For socket shells, execve("/bin/sh", NULL, NULL) after dup2 was more reliable than system("/bin/sh").

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: Forks-and-Knives
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: <TARGET>:32185
  • Start time: 2026-06-12T09:15:59Z
  • 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/a12c7337-67cf-4d8f-9f3a-ccb241eacf33.zip954177<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 6 shown in artifact inventory JSON
files/extracted/pwn_forks_and_knives/Dockerfile330<hash redacted>ASCII text
files/extracted/pwn_forks_and_knives/build-docker.sh132<hash redacted>POSIX shell script text executable, ASCII text
files/extracted/pwn_forks_and_knives/challenge/libc.so.62220400<hash redacted>ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=<hash redacted>, for GNU/Linux 3.2.0, stripped
files/extracted/pwn_forks_and_knives/challenge/server14384<hash redacted>ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=<hash redacted>, for GNU/Linux 3.2.0, stripped

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T09:15:59Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T09:18:00Zartifact inventoryanalysis/artifact-inventory.json5 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T09:18:33Zhypothesis recordedhypothesis-board.mdMenu-driven reservation/order binary likely exposes memory corruption or format-string primitive reachable over TCP; exploit should be built locally against bundled server/libc, then replayed remotely.MediumRun the server locally in Docker, map menu I/O, then fuzz/send edge-case inputs while monitoring crashes and outputs.
2026-06-12T09:18:53Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-12T09:18:53Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T09:19:22Zinstrumentation plananalysis/instrumentation-plan.mdIdentify and validate the local memory-corruption or file/logic primitive in the stripped restaurant server before remote exploitation.HighStop after two failed remote attempts without a new local fact, or if local instrumentation disproves the current primitive.
2026-06-12T09:20:04Zevaluatoranalysis/evaluator-20260612T092004904167Z-656f65d4.mdValidate firstHighRun local Docker probes for manager unlock, format leak positions, and order overflow layout.
2026-06-12T09:37:35Zlocal memory searchanalysis/research/local-memory-search-20260612T093735456559Z-b6ba5c3c.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-12T09:37:54Zcheckpoint recordedanalysis/checkpoint-analysis-20260612T093754481742Z-bb500c63.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-12T09:38:23Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T09:39:13Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T09:39:25Zevaluatoranalysis/evaluator-20260612T093925092974Z-538728e4.mdProceedHighCreate solve.py that leaks bases, brute-forces the canary with the crash oracle, sends dup2/system ret2libc chain, and captures HTB flag.
2026-06-12T10:08:14Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T10:09:15Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-12T10:23:55Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T10:23:55Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • server is a stripped PIE x86-64 menu service with stack canaries and a bundled Ubuntu/glibc-family libc.so.6.
  • The initial name read accepts 16 bytes into the global name buffer and then writes a NUL terminator at name + read_count; exactly 16 bytes clears the adjacent manager-gate flag and unlocks manager-only reservation viewing.
  • The reservation path writes a user-controlled short format token through fprintf(file, format), then manager view reads reservations.txt back over the socket. This gives compact libc/stack/heap leaks.
  • Remote %2$p leaked a libc pointer. %2$s byte matching corrected the offset to 0x11491b; the earlier 0x6691b page-aligned guess was wrong and caused the first ROP crash.
  • The order add-more path reads 0x100 bytes into the order buffer, then reads another 0x100 bytes at buffer + first_read_count. With a first read of 0x100, the second read starts eight bytes before the canary and can overwrite canary, saved RBP, and RIP.
  • The target is fork-per-client, so the canary is stable across child crashes. The solver brute-forces the canary with a crash/no-crash oracle.
  • Final payload uses libc ROP: dup2(client_fd, 0/1/2) followed by execve("/bin/sh", NULL, NULL). A diagnostic write(fd, "/bin/sh", 8) probe validated the ROP base, fd, and delivery before the final shell stage.
  • Correction after submission failure: reading flag* returned an additional HTB-looking thematic decoy/static value. The valid challenge submission value is the explicit flag.txt content, so solve.py now reads only flag.txt and stores the first match.

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: Forks-and-Knives
  • Difficulty: Medium
  • Source workspace: <local workspace>

Validated Solve Chain

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

1.

Reusable Lessons

-

Dead Ends

-

Tool Quirks

-

Evidence Paths

-

Ingestion Decision

  • Proposed for LightRAG: yes/no
  • 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
1Menu-driven reservation/order binary likely exposes memory corruption or format-string primitive reachable over TCP; exploit should be built locally against bundled server/libc, then replayed remotely.Artifacts are a stripped PIE x86_64 server, bundled libc, and Dockerfile exposing TCP 1337 as ctf user; challenge is Medium Pwn hybrid with remote <TARGET>:32185.Need interaction map, mitigation summary, exact primitive, leak strategy, and control-flow target.Run the server locally in Docker, map menu I/O, then fuzz/send edge-case inputs while monitoring crashes and outputs.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

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 Forks And Knives, 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.