Honor Among Thieves
Honor Among Thieves is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Honor Among Thieves attack path
Honor Among Thieves 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 and Rivals.sol; identify that...
Confirm Rivals.talk(bytes32) derives a candidate...
Use JSON-RPC to inspect target storage and chain...
Scan prior calls to talk(bytes32) and inspect...
Replay the successful historical calldata from the...
Source coverage
High source coverage
Status: complete. This article is generated from 6 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/Honor-Among-Thieves/writeup.md
- htb-challenge/Blockchain/Honor-Among-Thieves/notes.md
- htb-challenge/Blockchain/Honor-Among-Thieves/memory-summary.md
- htb-challenge/Blockchain/Honor-Among-Thieves/hypothesis-board.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Blockchain__Honor-Among-Thieves__memory-summary.md.8ce49d79aa.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Blockchain__Honor-Among-Thieves__notes.md.d02e4b247a.md
Technical Walkthrough
Writeup
Challenge
- Name: Honor-Among-Thieves
- Category: Blockchain
- Difficulty: Easy
- Mode: hybrid
Summary
The contract pair exposes a simple win condition: Setup.isSolved(address) returns true only when Rivals.solver() equals the player address. The target contract already had a rival address recorded as solver, so the solve was to inspect the public chain history, identify the rival transaction that emitted Voice(5), and replay that successful talk(bytes32) calldata from the player account.
Artifact Inventory
- Original archive:
files/a12c737b-c8ce-4489-ad54-aaab1fed14b9.zip - Extracted contracts:
- files/extracted/blockchain_honor_among_thieves/Setup.sol
- files/extracted/blockchain_honor_among_thieves/Rivals.sol
- Remote surface:
- /connection_info for player and contract metadata
- /rpc for JSON-RPC
- /flag for completion after isSolved
Analysis
Setup.sol deploys Rivals and checks whether TARGET.solver() == _player. Rivals.sol stores encryptedFlag and hashedFlag privately, but those values are still accessible through chain storage. The relevant mutating function is:
function talk(bytes32 _key) external {
bytes32 _flag = _key ^ encryptedFlag;
if (keccak256(abi.encode(_flag)) == hashedFlag) {
solver = msg.sender;
emit Voice(5);
} else {
emit Voice(block.timestamp % 5);
}
}The local source audit is recorded in analysis/source-audit.md. Advisory memory did not return a useful challenge-specific match, so the decision was based on local source and live RPC evidence only; see analysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txt and analysis/rag-records.md.
The chain contained historical rival calls to talk(bytes32). Receipt analysis found exactly one call with indexed Voice(5), proving that call used the winning key. The redacted evidence is in analysis/winning-rival-transaction-summary.json, and the raw replay calldata is stored under loot/winning-talk-calldata.txt.
Solve
The reproducible solver is solve/solve.py.
It performs the following steps:
- Fetch
/connection_infoand store raw connection material underloot/, with a redacted analysis copy. - Read the current
solverfrom target storage slot 2. - Scan target
talk(bytes32)transactions and identify the receipt that emitted indexedVoice(5). - Replay the winning calldata from the player address through
eth_sendTransaction. - Confirm storage slot 2 changed to the player address.
- Request
/flagand write the response toloot/flag-candidate.txt.
The successful run is summarized in analysis/solve-run-summary.json. The replay transaction changed solver_after to the player address and /flag returned HTTP 200.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- Solidity
privatestorage is not secret on-chain. - Indexed event arguments are useful for reconstructing which historical transaction reached a success branch.
- For challenge chains with an already successful rival transaction, replaying public calldata can be enough when the success path sets
msg.senderas the solver. - Keep
/connection_infoprivate keys and raw flags inloot/, with only redacted copies inanalysis/.
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: Honor-Among-Thieves
- Category: Blockchain
- Difficulty: Easy
- Mode: hybrid
- Remote instance: <TARGET>:31885
- Start time: 2026-06-09T10:26:14Z
- 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/a12c737b-c8ce-4489-ad54-aaab1fed14b9.zip | 1279 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-09T10:26:14Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-09T10:26:14Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-09T10:26:14Z | hypothesis recorded | hypothesis-board.md | Audit provided smart contract/source, identify win condition and exploit class, then interact with remote blockchain instance to satisfy isSolved/capture flag. | Medium | Extract source, inspect contracts/deployment metadata, identify RPC/ticket protocol from remote, and reproduce any contract interaction locally if tooling is available. |
| 2026-06-09T10:27:39Z | checkpoint recorded | analysis/checkpoint-analysis-20260609T102739737761Z-d6e0b639.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-09T10:33:58Z | RAG query | analysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-09T10:34:24Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-09T10:34:32Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-09T10:36:45Z | receipt scan | analysis/winning-rival-transaction-summary.json | One prior rival talk(bytes32) transaction emitted indexed Voice(5), proving the winning calldata; raw replay calldata stored in loot/winning-talk-calldata.txt | High | Record local fallback/evaluator, run exploit gate, replay calldata from player |
| 2026-06-09T10:37:54Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-09T10:38:17Z | evaluator | analysis/evaluator-20260609T103817895868Z-ece92690.md | Proceed | High | Run exploit gate, replay winning talk(bytes32) calldata from player via JSON-RPC, poll receipt, then request /flag. |
| 2026-06-09T10:43:35Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-09T10:49:22Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-09T10:49:42Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- The remote exposes HTTP endpoints including
/connection_info,/rpc, and/flag. /connection_infoprovides the player address, target contract, setup contract, and a private key; raw connection material is stored only underloot/.Setup.isSolved(address)depends onTARGET.solver()matching the player address.Rivals.talk(bytes32)is the only mutating target function needed for the win condition.- The on-chain transaction history contains rival calls to
talk(bytes32). Receipt logs identify exactly one successful call via indexedVoice(5). - Replaying the successful public calldata from the player account is the cheapest validated solve path.
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: Honor-Among-Thieves
- Difficulty: Easy
- 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.solandRivals.sol; identify thatSetup.isSolved(player)depends onRivals.solver()matching the player. - Confirm
Rivals.talk(bytes32)derives a candidate flag by XORing the supplied key withencryptedFlag, hashes it, and setssolver = msg.senderonly on success. - Use JSON-RPC to inspect target storage and chain history. Private Solidity storage is readable, and historical transaction calldata/logs are public.
- Scan prior calls to
talk(bytes32)and inspect receipts for indexedVoice(5), which marks a successful key. - Replay the successful historical calldata from the player account, changing
solverto the player. - Request the challenge flag after the win condition is satisfied; store raw flag in
loot/only.
Reusable Lessons
- Blockchain challenge
privatevariables are not confidential; useeth_getStorageAtwhen contract logic depends on private state. - Indexed event arguments can act as an oracle for success/failure branch reconstruction.
- If a challenge includes rival/on-chain attempts, historical calldata may contain the exact winning input.
- Raw challenge credentials from
/connection_infobelong inloot/; redacted copies belong inanalysis/.
Dead Ends
- Shared CTF LightRAG did not contain a useful Honor Among Thieves-specific match; local source and live RPC evidence drove the solve.
Tool Quirks
- Local web3/ethers/solc/foundry tooling was not needed. Standard Python plus
requestswas enough for JSON-RPC. - Re-scanning every block can be slow as the local challenge chain grows. Cache the winning calldata under
loot/after the first receipt proof.
Evidence Paths
analysis/source-audit.mdanalysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txtanalysis/rag-records.mdanalysis/winning-rival-transaction-summary.jsonanalysis/rival-talk-receipts-summary.jsonanalysis/solve-run-summary.jsonsolve/solve.pyloot/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 | Audit provided smart contract/source, identify win condition and exploit class, then interact with remote blockchain instance to satisfy isSolved/capture flag. | Challenge is Blockchain with a downloadable ZIP and remote host:port; scenario refers to obtaining a second key/treasure. | Extract source, inspect contracts/deployment metadata, identify RPC/ticket protocol from remote, and reproduce any contract interaction locally if tooling is available. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|
Memory Summary
approval_required: true
Sanitized Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Blockchain
- Challenge: Honor-Among-Thieves
- Difficulty: Easy
- 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.solandRivals.sol; identify thatSetup.isSolved(player)depends onRivals.solver()matching the player. - Confirm
Rivals.talk(bytes32)derives a candidate flag by XORing the supplied key withencryptedFlag, hashes it, and sets `solver = <REDACTED> - Use JSON-RPC to inspect target storage and chain history. Private Solidity storage is readable, and historical transaction calldata/logs are public.
- Scan prior calls to
talk(bytes32)and inspect receipts for indexedVoice(5), which marks a successful key. - Replay the successful historical calldata from the player account, changing
solverto the player. - Request the challenge flag after the win condition is satisfied; store raw flag in
loot/only.
Reusable Lessons
- Blockchain challenge
privatevariables are not confidential; useeth_getStorageAtwhen contract logic depends on private state. - Indexed event arguments can act as an oracle for success/failure branch reconstruction.
- If a challenge includes rival/on-chain attempts, historical calldata may contain the exact winning input.
- Raw challenge credentials from
/connection_infobelong inloot/; redacted copies belong inanalysis/.
Dead Ends
- Shared CTF LightRAG did not contain a useful Honor Among Thieves-specific match; local source and live RPC evidence drove the solve.
Tool Quirks
- Local web3/ethers/solc/foundry tooling was not needed. Standard Python plus
requestswas enough for JSON-RPC. - Re-scanning every block can be slow as the local challenge chain grows. Cache the winning calldata under
loot/after the first receipt proof.
Evidence Paths
analysis/source-audit.mdanalysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txtanalysis/rag-records.mdanalysis/winning-rival-transaction-summary.jsonanalysis/rival-talk-receipts-summary.jsonanalysis/solve-run-summary.jsonsolve/solve.pyloot/flag.txt
Ingestion Decision
- Proposed for LightRAG: yes
- Requires user approval before ingestion: yes
Notes
Notes
Scope
- Challenge: Honor-Among-Thieves
- Category: Blockchain
- Difficulty: Easy
- Mode: hybrid
- Remote instance: <TARGET>:31885
- Start time: 2026-06-09T10:26:14Z
- 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/a12c737b-c8ce-4489-ad54-aaab1fed14b9.zip | 1279 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-09T10:26:14Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-09T10:26:14Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-09T10: <REDACTED>, identify win condition and exploit class, then interact with remote blockchain instance to satisfy isSolved/capture flag. | Medium | Extract source, inspect contracts/deployment metadata, identify RPC/ticket protocol from remote, and reproduce any contract interaction locally if tooling is available. | |||
| 2026-06-09T10:27:39Z | checkpoint recorded | analysis/checkpoint-analysis-20260609T102739737761Z-d6e0b639.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-09T10:33:58Z | RAG query | analysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-09T10:34:24Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-09T10:34:32Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-09T10:36:45Z | receipt scan | analysis/winning-rival-transaction-summary.json | One prior rival talk(bytes32) transaction emitted indexed Voice(5), proving the winning calldata; raw replay calldata stored in loot/winning-talk-calldata.txt | High | Record local fallback/evaluator, run exploit gate, replay calldata from player |
| 2026-06-09T10:37:54Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-09T10: <REDACTED>, replay winning talk(bytes32) calldata from player via JSON-RPC, poll receipt, then request /flag. | |||||
| 2026-06-09T10: <REDACTED> | |||||
| 2026-06-09T10: <REDACTED> | |||||
| 2026-06-09T10:49:42Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- The remote exposes HTTP endpoints including
/connection_info,/rpc, and/flag. /connection_infoprovides the player address, target contract, setup contract, and a private key; raw connection material is stored only underloot/.Setup.isSolved(address)depends onTARGET.solver()matching the player address.Rivals.talk(bytes32)is the only mutating target function needed for the win condition.- The on-chain transaction history contains rival calls to
talk(bytes32). Receipt logs identify exactly one successful call via indexedVoice(5). - Replaying the successful public calldata from the player account is the cheapest validated solve path.
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.
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 Honor Among Thieves, 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.