Challenge / Blockchain

Locked And Loaded

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

MediumPublished 2024-01-24Sanitized local writeup

Scenario

Locked And Loaded attack path

Locked And Loaded 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 Blockchain evidence, validation, and reusable operator lessons.

Locked And Loaded sanitized attack graph

Walkthrough flow

01

Audit Setup.sol: solved condition is target contract...

02

Audit Lockers.sol: private users mapping and private...

03

Decode private storage through JSON-RPC:

04

Recover the existing Mythic item and its owner...

05

Validate sellItem(name, credential) with eth_call...

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.

  • Blockchain/Locked-and-Loaded/writeup.md
  • htb-challenge/Blockchain/Locked-and-Loaded/notes.md
  • htb-challenge/Blockchain/Locked-and-Loaded/memory-summary.md
  • htb-challenge/Blockchain/Locked-and-Loaded/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Locked-and-Loaded
  • Category: Blockchain
  • Difficulty: Medium
  • Mode: hybrid

Summary

Locked-and-Loaded was solved by combining two Solidity issues. First, the contract stored item metadata and locker <password redacted> in private state, which is still readable from chain storage. Second, sellItem() sends ETH before deleting the sold item, allowing a reentrant sale of the same Mythic item.

The target started with 2 ether. A single existing Mythic item paid 1 ether, so the solve required transferring that Mythic item into an attacker-controlled locker and reentering sellItem() once to drain both payouts before deletion.

Artifact Inventory

  • files/extracted/blockchain_locked_and_loaded/Setup.sol: defines the solved condition as address(TARGET).balance == 0.
  • files/extracted/blockchain_locked_and_loaded/Lockers.sol: stores users/items in private state and contains the vulnerable sellItem() flow.
  • solve/solve.py: reproducible read-only inspection and final exploit runner.
  • solve/ReenterLocker.sol: minimal attacker contract used for the reentrant second sale.
  • analysis/storage-inspection-redacted.json: live read-only storage validation without raw <password redacted>.
  • analysis/solve-run-summary.json: final transaction summary and solved-state proof.

Analysis

Setup.isSolved() only checks the target balance. Lockers receives 2 ether during setup, and price[Rarity.Mythic] is 1 ether.

The source audit in analysis/source-audit.md identified the storage layout: users at slot 0, usernameToWallet at slot 1, price at slot 2, and items at slot 3. The solver decoded the private items array and the string-keyed users mapping from JSON-RPC storage. The read-only probe found 10 items, recovered 9 owner <password redacted>, and validated one Mythic sale through eth_call.

Only one Mythic item existed, so a simple sale would leave 1 ether in the target. The final path used the external call-before-delete bug in sellItem(). The attacker contract registered its own locker, the Mythic item was transferred to that locker using the recovered original owner password, and the attacker contract reentered sellItem() from its receive hook while the item still existed.

Solve

Run the read-only validation first:

bash
python3 Blockchain/Locked-and-Loaded/solve/solve.py \
  --base-url http://<TARGET>:32111 \
  --workspace Blockchain/Locked-and-Loaded \
  --readonly

After the harness Proceed evaluator, run the final exploit through the wrapper:

bash
python3 scripts/challenge_exec.py Blockchain/Locked-and-Loaded \
  --phase <secret redacted> \
  --output analysis/solve-run-summary.json \
  -- python3 Blockchain/Locked-and-Loaded/solve/solve.py \
    --base-url http://<TARGET>:32111 \
    --workspace Blockchain/Locked-and-Loaded \
    --execute

The final run deployed ReenterLocker, registered an attacker locker, transferred the Mythic item, performed the reentrant sale, reduced the target balance to zero, and wrote the flag candidate to loot/flag-candidate.txt. The harness then captured the flag into loot/flag.txt.

Flag

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

Lessons

  • Solidity private is not secrecy. <password redacted>, item names, and ownership stored on-chain must be considered public.
  • When a payout happens before state deletion, reentrancy can reuse stale state even if the apparent payout value is capped.
  • For blockchain challenges, a read-only storage decoder plus eth_call validation is a strong gate before sending live transactions.

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: Locked-and-Loaded
  • Category: Blockchain
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-12T23:09:27Z
  • 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/a12c7345-e73a-4183-a679-e5d2c7c4f452.zip2047<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON
files/extracted/blockchain_locked_and_loaded/Lockers.sol3889<hash redacted>ASCII text
files/extracted/blockchain_locked_and_loaded/Setup.sol628<hash redacted>Java source, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T23:09:27Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T23:09:47Zartifact inventoryanalysis/artifact-inventory.json3 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T23:15:35Zhypothesis recordedhypothesis-board.mdRead private Lockers storage to recover item names, owners, and owner <password redacted>; then sell existing Mythic items to drain the 2 ETH target balance.HighRun solve/solve.py --readonly to decode storage and eth_call the Mythic sale route without changing state.
2026-06-12T23:17:03Zinstrumentation plananalysis/instrumentation-plan.mdExploit private Solidity storage plus sellItem reentrancy to drain the 2 ETH Lockers target.HighStop if storage decoding cannot recover the Mythic owner password, eth_call sellItem fails, attacker deployment fails, or post-transaction balance/isSolved does not move as expected.
2026-06-12T23:17:03Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T23:19:00ZRAG queryanalysis/rag/rag-query-20260612T231848279415Z-b45fd0f8.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-12T23:19:26ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-12T23:19:26Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T23:19:26Zcheckpoint recordedanalysis/checkpoint-analysis-20260612T231926628779Z-9c7fc9a8.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-12T23:19:46Zevaluatoranalysis/evaluator-20260612T231946031185Z-4af0ce06.mdProceedHighRun solve/solve.py --execute via challenge_exec; capture flag only if Setup.isSolved is true.
2026-06-12T23:20:48Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T23:21:45Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

-

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: Blockchain
  • Challenge: Locked-and-Loaded
  • 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. Audit Setup.sol: solved condition is target contract balance equal to zero.
  2. Audit Lockers.sol: private users mapping and private items array hold the real authentication and item metadata.
  3. Decode private storage through JSON-RPC:

- users mapping at slot 0

- items dynamic array length at slot 3

- items data at keccak256(uint256(3))

  1. Recover the existing Mythic item and its owner password from storage.
  2. Validate sellItem(name, password) with eth_call before mutation.
  3. Because there is only one Mythic item and the target has 2 ether, use the call-before-delete reentrancy in sellItem().
  4. Deploy an attacker contract, register an attacker-owned locker from that contract, transfer the Mythic item into that locker, and trigger reentrant sellItem() to receive two 1 ETH payouts before deletion.
  5. Confirm target balance is zero, then fetch and harness-capture the flag.

Reusable Lessons

  • Solidity private state is readable from storage and should not be used for <password redacted> or secrets.
  • For mapping(string => string), test string-key slot derivations and validate recovered values with eth_call before sending transactions.
  • Dynamic arrays of structs with string fields can be decoded from storage when the array slot and struct width are known.
  • If a payout happens before deleting or updating the item state, a receiver contract can reenter and reuse the stale item.
  • If a single payout cannot drain the target, check whether the same stale state can be reused before searching for unrelated vulnerabilities.

Dead Ends

  • Creating new Mythic items is blocked by putItem() because it requires rarity < 3.
  • Selling self-created Epic items is too low-value; each payout is only 100 wei.
  • A direct single sale of the recovered Mythic item drains only 1 ETH and does not satisfy the 2 ETH solved condition.

Tool Quirks

  • Python web3 and Foundry were not required. Plain requests JSON-RPC plus PyCryptodome Keccak was enough.
  • npx solc@0.8.13 compiled the small reentrancy helper contract when native solc/solcjs was unavailable.
  • Keep raw recovered <password redacted> and connection info under loot/; use redacted summaries in analysis/.

Evidence Paths

  • analysis/source-audit.md
  • analysis/storage-inspection-redacted.json
  • analysis/readonly-solve-probe.txt
  • solve/solve.py
  • solve/ReenterLocker.sol
  • analysis/solve-run-summary.json
  • 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
1Read private Lockers storage to recover item names, owners, and owner <password redacted>; then sell existing Mythic items to drain the 2 ETH target balance.Lockers stores users mapping and private items array on-chain; Setup is solved only when TARGET balance is zero; sellItem pays 1 ETH per Mythic item and does not require msg.sender ownership.Live storage must contain recoverable Mythic items and matching owner <password redacted>, and eth_call sellItem(name,password) must succeed before mutation.Run solve/solve.py --readonly to decode storage and eth_call the Mythic sale route without changing state.Highactive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Technical analogy

How to remember this solve

Think of the smart contract like a transparent bank ledger with strict but imperfect rules. The trick is to make the rules execute in an order the author did not protect against.

For Locked And Loaded, 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.