ReplaceMe
ReplaceMe is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
ReplaceMe attack path
ReplaceMe 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.
Walkthrough flow
Binary triage
Memory primitive
Control-flow hijack
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.
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/ReplaceMe/writeup.md
- htb-challenge/Pwn/ReplaceMe/notes.md
- htb-challenge/Pwn/ReplaceMe/memory-summary.md
- htb-challenge/Pwn/ReplaceMe/hypothesis-board.md
Technical Walkthrough
Writeup
Challenge
- Name: ReplaceMe
- Category: Pwn
- Difficulty: Medium
- Mode: hybrid
Summary
ReplaceMe is a fork-per-connection Linux pwn challenge around a sed-like s/old/new/ replacement utility. The binary reads an input string and a replacement expression into adjacent global buffers, then expands the result into a fixed stack buffer without checking the combined output length.
The exploit uses that unchecked expansion to overwrite the saved return address. Because the program prints the expanded stack buffer before returning, the first stage performs a one-byte saved-RIP overwrite back to main and leaks the high bytes of the PIE return address in the same process. A second stage uses a pop rdi; ret gadget to leak puts from the GOT and loop again. The final stage calls system("/bin/sh") from the provided libc and reads flag.txt.
Artifact Inventory
Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.
files/extracted/replaceme: 64-bit PIE ELF, dynamically linked, not stripped.files/extracted/libc.so.6: matching libc used for ret2libc offsets.files/extracted/Dockerfile: service runs the binary throughsocat ... EXEC:./replaceme.- Remote instance:
<TARGET>:30149.
Analysis
Validated local evidence:
analysis/source-audit.mdrecords the unchecked stack-copy primitive indo_replacement.analysis/objdump-replaceme.txtshowsinputat0x4040,replacementat0x40c0, the local output area beginning atrbp-0xc0, andleave; retat the end ofdo_replacement.analysis/remote/partial-return-onebyte-probe.binproves that changing only the saved return address low byte loops execution back tomain.analysis/remote/partial-return-leak-probe.binproves the same loop can leak PIE address bytes before returning.analysis/remote/libc-leak-probe.binproves a ROP stage can leak libc throughputs(puts@got)and loop again.
Important implementation details:
- A two-byte partial overwrite failed because PIE base randomization affects the second byte. The reliable loop changes only the low byte from the return site at
main+0x70back tomain. - The overflow crosses stack locals used after the
memcpy. The payload sets the overwrittentail_lenlocal to-1so the post-overflow tail copy is skipped. - The final chain must call
system("/bin/sh")without an extra alignmentret; the extraretvariant crashed, while the no-ret variant produced shell output.
Solve
Run:
python3 solve/solve.py --host <TARGET> --port 30149The solver:
- Leaks PIE and loops to
main. - Leaks libc through
puts@gotand loops tomain. - Calls
system("/bin/sh"), sendscat flag.txt, and writes the flag candidate toloot/flag-candidate.txt. - Writes only a redacted transcript to
analysis/remote/solve-transcript-redacted.bin.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- For PIE binaries, a one-byte partial overwrite is often more reliable than two bytes when the target and original return site are on the same page.
- Print-before-return stack overflows can provide an information leak even when the primary bug is a write primitive.
- When an overflow corrupts locals that are still used, the exploit must neutralize or preserve those locals before focusing on the saved return address.
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: ReplaceMe
- Category: Pwn
- Difficulty: Medium
- Mode: hybrid
- Remote instance: none
- Start time: 2026-06-11T23:14:10Z
- 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c73a5-b9fe-4e10-8b39-f9480f64a9b3.zip | 863099 | <hash redacted> | Zip archive data, at least v2.0 to extract, compression method=deflate | zip entries: 5 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-11T23:14:10Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-11T23:14:11Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-11T23:18:34Z | hypothesis recorded | hypothesis-board.md | Stack overflow in do_replacement from expansion of s/old/new/ into fixed stack output buffer | High | Use a bounded socket probe that overwrites only the low two bytes of the saved return address to return to main and observe a second prompt |
| 2026-06-11T23:18:34Z | hypothesis recorded | hypothesis-board.md | find/strlen out-of-bounds read can extend copied tail across adjacent globals | Medium | Probe controlled output length changes using shorter input versus full input |
| 2026-06-11T23:19:02Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-11T23:19:02Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T231902490353Z-fa3ef7db.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T23:19:04Z | local memory search | analysis/research/local-memory-search-20260611T231904085750Z-d0c25ce0.md | Found 5 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-11T23:19:26Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-11T23:19:43Z | RAG query | analysis/rag/rag-query-20260611T231926170450Z-7d77b349.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-11T23:19:53Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-11T23:20:10Z | instrumentation plan | analysis/instrumentation-plan.md | Validate and exploit the unchecked replacement expansion without blind remote attempts | High | Stop after two failed primitive probes without a new fact, or immediately if the service stops responding, the partial return does not produce a second prompt, or the exploit requires unvalidated brute force. |
| 2026-06-11T23:20:10Z | RAG record | analysis/rag-records.md | Retrieved memory tagged PARTIAL | Medium | Validate or reject with live evidence |
| 2026-06-11T23:20:21Z | evaluator | analysis/evaluator-20260611T232021059853Z-afb03626.md | Proceed | High | Run challenge_exec-wrapped Python probe against <TARGET>:30149 and save transcript in analysis/remote/. |
| 2026-06-11T23:35:48Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-11T23:36:42Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
do_replacementexpandss/old/new/into a stack output area without a bounds check.- PIE/NX/full RELRO are enabled, but no stack canary was observed.
- The reliable control primitive is a one-byte saved-RIP overwrite back to
main, not a two-byte overwrite. - Payloads must neutralize overwritten stack locals, especially
tail_len, before the function reachesfputsandleave; ret. - Final solve chain: PIE leak -> libc leak through
puts@got->system("/bin/sh")without an extra alignmentret-> readflag.txt. - Raw flag is stored only in
loot/flag.txt.
RAG / Advisory Memory
RAG output is advisory only. Record evaluated retrievals with:
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: ReplaceMe
- 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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Stack overflow in do_replacement from expansion of s/old/new/ into fixed stack output buffer | objdump shows do_replacement allocates 0xc0 bytes, zeroes an 0x80 output area at rbp-0xc0, then memcpy copies prefix + replacement + tail without checking output length before fputs and return | Need live signal that the overflow can redirect control under PIE/ASLR | Use a bounded socket probe that overwrites only the low two bytes of the saved return address to return to main and observe a second prompt | High | Active |
| 2 | find/strlen out-of-bounds read can extend copied tail across adjacent globals | input and replacement are adjacent globals at 0x4040 and 0x40c0; reads do not append NUL; find scans fixed 0x80 windows without length-plus-needle bounds; strlen(match+old_len) can consume adjacent bytes until a NUL | May be useful for sizing/leaks but not sufficient alone for flag capture | Probe controlled output length changes using shorter input versus full input | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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 ReplaceMe, 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.