Challenge / Blockchain

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

EasyPublished 2024-01-21Sanitized local writeup

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.

Honor Among Thieves sanitized attack graph

Walkthrough flow

01

Audit Setup.sol and Rivals.sol; identify that...

02

Confirm Rivals.talk(bytes32) derives a candidate...

03

Use JSON-RPC to inspect target storage and chain...

04

Scan prior calls to talk(bytes32) and inspect...

05

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.

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/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:

solidity
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:

  1. Fetch /connection_info and store raw connection material under loot/, with a redacted analysis copy.
  2. Read the current solver from target storage slot 2.
  3. Scan target talk(bytes32) transactions and identify the receipt that emitted indexed Voice(5).
  4. Replay the winning calldata from the player address through eth_sendTransaction.
  5. Confirm storage slot 2 changed to the player address.
  6. Request /flag and write the response to loot/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 private storage 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.sender as the solver.
  • Keep /connection_info private keys and raw flags in loot/, with only redacted copies in analysis/.

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

FileSizeSHA256TypeNotes
files/a12c737b-c8ce-4489-ad54-aaab1fed14b9.zip1279<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T10:26:14Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T10:26:14Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T10:26:14Zhypothesis recordedhypothesis-board.mdAudit provided smart contract/source, identify win condition and exploit class, then interact with remote blockchain instance to satisfy isSolved/capture flag.MediumExtract 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:39Zcheckpoint recordedanalysis/checkpoint-analysis-20260609T102739737761Z-d6e0b639.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-09T10:33:58ZRAG queryanalysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-09T10:34:24ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-09T10:34:32Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-09T10:36:45Zreceipt scananalysis/winning-rival-transaction-summary.jsonOne prior rival talk(bytes32) transaction emitted indexed Voice(5), proving the winning calldata; raw replay calldata stored in loot/winning-talk-calldata.txtHighRecord local fallback/evaluator, run exploit gate, replay calldata from player
2026-06-09T10:37:54Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-09T10:38:17Zevaluatoranalysis/evaluator-20260609T103817895868Z-ece92690.mdProceedHighRun exploit gate, replay winning talk(bytes32) calldata from player via JSON-RPC, poll receipt, then request /flag.
2026-06-09T10:43:35Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-09T10:49:22Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-09T10:49:42Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The remote exposes HTTP endpoints including /connection_info, /rpc, and /flag.
  • /connection_info provides the player address, target contract, setup contract, and a private key; raw connection material is stored only under loot/.
  • Setup.isSolved(address) depends on TARGET.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 indexed Voice(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:

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: 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.

  1. Audit Setup.sol and Rivals.sol; identify that Setup.isSolved(player) depends on Rivals.solver() matching the player.
  2. Confirm Rivals.talk(bytes32) derives a candidate flag by XORing the supplied key with encryptedFlag, hashes it, and sets solver = msg.sender only on success.
  3. Use JSON-RPC to inspect target storage and chain history. Private Solidity storage is readable, and historical transaction calldata/logs are public.
  4. Scan prior calls to talk(bytes32) and inspect receipts for indexed Voice(5), which marks a successful key.
  5. Replay the successful historical calldata from the player account, changing solver to the player.
  6. Request the challenge flag after the win condition is satisfied; store raw flag in loot/ only.

Reusable Lessons

  • Blockchain challenge private variables are not confidential; use eth_getStorageAt when 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_info belong in loot/; redacted copies belong in analysis/.

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 requests was 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.md
  • analysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txt
  • analysis/rag-records.md
  • analysis/winning-rival-transaction-summary.json
  • analysis/rival-talk-receipts-summary.json
  • analysis/solve-run-summary.json
  • solve/solve.py
  • 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
1Audit 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.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit 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.

  1. Audit Setup.sol and Rivals.sol; identify that Setup.isSolved(player) depends on Rivals.solver() matching the player.
  2. Confirm Rivals.talk(bytes32) derives a candidate flag by XORing the supplied key with encryptedFlag, hashes it, and sets `solver = <REDACTED>
  3. Use JSON-RPC to inspect target storage and chain history. Private Solidity storage is readable, and historical transaction calldata/logs are public.
  4. Scan prior calls to talk(bytes32) and inspect receipts for indexed Voice(5), which marks a successful key.
  5. Replay the successful historical calldata from the player account, changing solver to the player.
  6. Request the challenge flag after the win condition is satisfied; store raw flag in loot/ only.

Reusable Lessons

  • Blockchain challenge private variables are not confidential; use eth_getStorageAt when 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_info belong in loot/; redacted copies belong in analysis/.

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 requests was 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.md
  • analysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txt
  • analysis/rag-records.md
  • analysis/winning-rival-transaction-summary.json
  • analysis/rival-talk-receipts-summary.json
  • analysis/solve-run-summary.json
  • solve/solve.py
  • loot/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

FileSizeSHA256TypeNotes
files/a12c737b-c8ce-4489-ad54-aaab1fed14b9.zip1279<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T10:26:14Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T10:26:14Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T10: <REDACTED>, identify win condition and exploit class, then interact with remote blockchain instance to satisfy isSolved/capture flag.MediumExtract 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:39Zcheckpoint recordedanalysis/checkpoint-analysis-20260609T102739737761Z-d6e0b639.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-09T10:33:58ZRAG queryanalysis/rag/rag-query-20260609T103348633057Z-e0e85c8f.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-09T10:34:24ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-09T10:34:32Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-09T10:36:45Zreceipt scananalysis/winning-rival-transaction-summary.jsonOne prior rival talk(bytes32) transaction emitted indexed Voice(5), proving the winning calldata; raw replay calldata stored in loot/winning-talk-calldata.txtHighRecord local fallback/evaluator, run exploit gate, replay calldata from player
2026-06-09T10:37:54Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate 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:42Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The remote exposes HTTP endpoints including /connection_info, /rpc, and /flag.
  • /connection_info provides the player address, target contract, setup contract, and a private key; raw connection material is stored only under loot/.
  • Setup.isSolved(address) depends on TARGET.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 indexed Voice(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:

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.

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.