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
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.
Walkthrough flow
Audit Setup.sol: solved condition is target contract...
Audit Lockers.sol: private users mapping and private...
Decode private storage through JSON-RPC:
Recover the existing Mythic item and its owner...
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.
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 asaddress(TARGET).balance == 0.files/extracted/blockchain_locked_and_loaded/Lockers.sol: stores users/items in private state and contains the vulnerablesellItem()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:
python3 Blockchain/Locked-and-Loaded/solve/solve.py \
--base-url http://<TARGET>:32111 \
--workspace Blockchain/Locked-and-Loaded \
--readonlyAfter the harness Proceed evaluator, run the final exploit through the wrapper:
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 \
--executeThe 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
privateis 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_callvalidation 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c7345-e73a-4183-a679-e5d2c7c4f452.zip | 2047 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
files/extracted/blockchain_locked_and_loaded/Lockers.sol | 3889 | <hash redacted> | ASCII text | |
files/extracted/blockchain_locked_and_loaded/Setup.sol | 628 | <hash redacted> | Java source, ASCII text |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T23:09:27Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T23:09:47Z | artifact inventory | analysis/artifact-inventory.json | 3 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T23:15:35Z | hypothesis recorded | hypothesis-board.md | Read private Lockers storage to recover item names, owners, and owner <password redacted>; then sell existing Mythic items to drain the 2 ETH target balance. | High | Run solve/solve.py --readonly to decode storage and eth_call the Mythic sale route without changing state. |
| 2026-06-12T23:17:03Z | instrumentation plan | analysis/instrumentation-plan.md | Exploit private Solidity storage plus sellItem reentrancy to drain the 2 ETH Lockers target. | High | Stop 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:03Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T23:19:00Z | RAG query | analysis/rag/rag-query-20260612T231848279415Z-b45fd0f8.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-12T23:19:26Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-12T23:19:26Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-12T23:19:26Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T231926628779Z-9c7fc9a8.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T23:19:46Z | evaluator | analysis/evaluator-20260612T231946031185Z-4af0ce06.md | Proceed | High | Run solve/solve.py --execute via challenge_exec; capture flag only if Setup.isSolved is true. |
| 2026-06-12T23:20: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-12T23:21:45Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
-
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: 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.
- Audit
Setup.sol: solved condition is target contract balance equal to zero. - Audit
Lockers.sol: privateusersmapping and privateitemsarray hold the real authentication and item metadata. - Decode private storage through JSON-RPC:
- users mapping at slot 0
- items dynamic array length at slot 3
- items data at keccak256(uint256(3))
- Recover the existing Mythic item and its owner password from storage.
- Validate
sellItem(name, password)witheth_callbefore mutation. - Because there is only one Mythic item and the target has
2 ether, use the call-before-delete reentrancy insellItem(). - 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. - Confirm target balance is zero, then fetch and harness-capture the flag.
Reusable Lessons
- Solidity
privatestate 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 witheth_callbefore 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 requiresrarity < 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
web3and Foundry were not required. PlainrequestsJSON-RPC plus PyCryptodome Keccak was enough. npx solc@0.8.13compiled the small reentrancy helper contract when nativesolc/solcjswas unavailable.- Keep raw recovered <password redacted> and connection info under
loot/; use redacted summaries inanalysis/.
Evidence Paths
analysis/source-audit.mdanalysis/storage-inspection-redacted.jsonanalysis/readonly-solve-probe.txtsolve/solve.pysolve/ReenterLocker.solanalysis/solve-run-summary.jsonloot/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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Read 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. | High | active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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.