Challenge / Pwn

Arms Roped

Arms Roped 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-12Sanitized local writeup

Scenario

Arms Roped attack path

Arms Roped 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.

Arms Roped sanitized attack graph

Walkthrough flow

01

ARM PIE binary has a stack overflow from...

02

The vulnerable function echoes non-exit input with...

03

Non-exit iterations can be used as a byte oracle to...

04

PIE base comes from the saved return address. Libc...

05

Final payload uses an ARM/Thumb-aware libc gadget to...

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/Arms-Roped/writeup.md
  • htb-challenge/Pwn/Arms-Roped/notes.md
  • htb-challenge/Pwn/Arms-Roped/memory-summary.md
  • htb-challenge/Pwn/Arms-Roped/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Arms-Roped
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid

Summary

Arms-Roped is an ARM 32-bit PIE pwn challenge served through a patched QEMU user-mode runner. The binary has a stack overflow in string_storer: it reads a newline-delimited string with scanf("%m[^\n]%n"), copies the recorded length into a 32-byte stack buffer, then echoes the buffer with puts unless the first four bytes are quit.

The key observation is that the canary check happens only on the quit exit path. Non-quit lines can corrupt the current stack frame, leak bytes through puts(buffer), and then loop back for another line. The final payload restores the leaked canary before returning into a ROP chain.

Artifact Inventory

  • files/extracted/pwn_arms_roped/arms_roped: ARM 32-bit PIE executable.
  • files/extracted/pwn_arms_roped/libc.so.6: matching ARM libc used by the Docker image.
  • files/extracted/pwn_arms_roped/Dockerfile: QEMU+socat service wrapper.
  • files/extracted/pwn_arms_roped/patch.diff: QEMU ASLR simulation patch.
  • Full inventory: analysis/artifact-inventory.json.

Analysis

The direct disassembly in analysis/string_storer.disasm.txt shows:

  • local buffer at r11 - 0x30
  • canary at r11 - 0x10
  • saved return address at offset 0x30 from the buffer
  • puts(buffer) on every non-quit iteration
  • canary validation only after the quit comparison succeeds

The remote baseline in analysis/remote-baseline-transcript.txt confirmed that ordinary non-quit lines are echoed and quit exits cleanly.

The exploit model is documented in analysis/exploit-analysis.md:

  1. Leak canary bytes by overwriting through the prior byte and reading the next byte printed by puts.
  2. Leak the saved return address byte-by-byte, deriving the PIE base from the return site at PIE + 0x948.
  3. Use the ARM CSU sequence at PIE + 0x9ec and PIE + 0x9cc to call puts(puts@GOT + i) byte-by-byte.
  4. Normalize the leaked libc function pointer by clearing the Thumb/interworking bit before computing libc base.
  5. Use a Thumb pop {r0, r1, pc} gadget from libc to call system("/bin/sh").

The RAG query was recorded but treated as generic only; it did not drive the solve. See analysis/rag-records.md.

Solve

Run the solver against the remote instance:

bash
python3 solve/solve.py <TARGET> 31484 --output loot/flag-candidate.txt

The successful harness-wrapped execution is recorded in telemetry.jsonl. The solver is bounded, retries connections when newline-delimited payload bytes would break scanf, and writes the remote command output to loot/flag-candidate.txt.

Flag

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

Lessons

  • A stack canary does not stop exploitation if the program loops after an overflow and exposes a pre-check output primitive.
  • Byte-by-byte puts leaks are more reliable than long string leaks when null bytes can truncate output.
  • ARM/Thumb interworking bits matter when computing libc bases and final branch targets.
  • For newline-delimited binary payloads, retry when any required packed value contains 0x0a.

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: Arms-Roped
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: <TARGET>:31484
  • Start time: 2026-06-13T10:06:22Z
  • 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/a12c736f-b2f3-452f-a3a3-c3d5ed6b5958.zip586016<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 6 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-13T10:06:22Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-13T10:06:22Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-13T10:06:48Zhypothesis recordedhypothesis-board.mdAnalyze the provided ARM binary, patch, libc, and Docker setup to build a local exploit, then send the same payload to the remote service.MediumInspect patch/source hints, binary architecture/protections, and run local service/Docker if available to reproduce the crash and shell path.
2026-06-13T10:06:48Zresearch taskanalysis/research/task-20260613T100648528187Z-9fae4574.mdResearch task created for advisory investigationMediumRecord research output
2026-06-13T10:08:28Zinstrumentation plananalysis/instrumentation-plan.mdBuild a reproducible exploit for the ARM stack overflow that bypasses canary/ASLR and gets a shell on the remote service.HighStop after two remote crash/failure attempts without new leak/layout evidence; record failure and reassess canary or ASLR assumptions before more attempts.
2026-06-13T10:08:28Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-13T10:08:28Zcheckpoint recordedanalysis/checkpoint-analysis-20260613T100828500808Z-6e4c5ed7.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-13T10:11:21Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-13T10:11:39ZRAG queryanalysis/rag/rag-query-20260613T101121206417Z-1ab6a2d4.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-13T10:12:15Zlocal memory searchanalysis/research/local-memory-search-20260613T101215384252Z-d1ddc39f.mdFound 5 safe prior-note result(s)MediumRecord useful result or skip
2026-06-13T10:15:07ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-13T10:15:41Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-13T10:15:41Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T10:19:14Zevaluatoranalysis/evaluator-20260613T101914838491Z-fdc49dbc.mdProceedHighGate before exploit, then execute solve/solve.py with host/port and capture the flag candidate if produced.
2026-06-13T10:19:14Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T10:24:26Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-13T10:25:33Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Remote endpoint: <TARGET>:31484.
  • string_storer has a stack overflow from scanf("%m[^\n]%n") plus unchecked memcpy into a 32-byte stack buffer.
  • Non-quit input is echoed through puts(buffer) before the canary check, enabling byte-by-byte leaks.
  • Final solve leaks canary, PIE, and libc, then uses ARM/Thumb-aware ret2libc to execute system("/bin/sh").
  • Raw flag is stored only at loot/flag.txt.

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: Arms-Roped
  • 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. ARM PIE binary has a stack overflow from newline-delimited dynamic scanf input copied into a fixed stack buffer.
  2. The vulnerable function echoes non-exit input with puts(buffer) and only checks the canary on the quit exit path.
  3. Non-exit iterations can be used as a byte oracle to leak the canary and saved return address, then repair the canary in a final exit payload.
  4. PIE base comes from the saved return address. Libc base comes from CSU-mediated byte leaks of a resolved GOT function pointer.
  5. Final payload uses an ARM/Thumb-aware libc gadget to call system("/bin/sh") and read the flag.

Reusable Lessons

  • In looping stack-overflow programs, check whether the canary is validated only at function exit; pre-exit output can become a byte leak.
  • Use byte-wise leaks when puts truncation on null bytes would make a single long leak unreliable.
  • Clear ARM/Thumb interworking bit from leaked libc function pointers before base calculation.
  • Account for newline-sensitive parsers when packing binary ROP payloads.

Dead Ends

  • Local QEMU/Docker reproduction was unavailable because Docker daemon/QEMU tooling was missing locally. The solve proceeded with static analysis plus bounded remote validation.
  • RAG returned generic/unrelated ARM challenge material and was not treated as evidence.

Tool Quirks

  • macOS host lacked readelf, ARM-aware objdump variants, QEMU ARM, gdb, and pwntools. Install-request artifacts were recorded instead of installing silently.
  • The stock objdump mixed ARM/Thumb decoding in libc, so the final script uses known offsets plus raw-byte validation and Thumb-bit normalization.

Evidence Paths

  • analysis/string_storer.disasm.txt
  • analysis/exploit-analysis.md
  • analysis/remote-baseline-transcript.txt
  • analysis/libc-thumb-pop-candidates.txt
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes, sanitized technique only
  • 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
1Analyze the provided ARM binary, patch, libc, and Docker setup to build a local exploit, then send the same payload to the remote service.Archive contains arms_roped, libc.so.6, Dockerfile, and patch.diff; scenario asks for shell on a remote TCP service.Inspect patch/source hints, binary architecture/protections, and run local service/Docker if available to reproduce the crash and shell path.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 Arms Roped, 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.