Pydome
Pydome is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Pydome attack path
Pydome 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 Misc evidence, validation, and reusable operator lessons.
Walkthrough flow
Artifact review
Hypothesis
Validated solve path
Proof captured
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.
- Misc/Pydome/writeup.md
- htb-challenge/Misc/Pydome/notes.md
- htb-challenge/Misc/Pydome/memory-summary.md
- htb-challenge/Misc/Pydome/hypothesis-board.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Pydome__memory-summary.md.fb5f489735.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Pydome__notes.md.5ce3fa2bb8.md
Technical Walkthrough
Writeup
Challenge
- Name: Pydome
- Category: Misc
- Difficulty: Medium
- Mode: hybrid
Summary
Pydome is a Python input-filter challenge. The provided source hides the real ANACONDA environment seed, so direct seed recovery from the archive is not possible. The solve uses two service behaviors instead: SHA mismatch output leaks story.txt, and the non-integer conversion branch leaks deterministic random.randrange(100) outputs. A second leak plan with an initial pad-only attempt reveals the first hidden random value, allowing the first numeric-only secret and SHA target to be reconstructed.
Artifact Inventory
files/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip
- Contains only misc_pydome/server.py.
- The archive does not include story.txt, flag.txt, Docker metadata, or the ANACONDA environment value.
- Remote instance:
<TARGET>:32587. - Main evidence:
- analysis/extracted/misc_pydome/server.py
- analysis/remote/story.txt
- analysis/remote/random-leak-n100-repeat-indexes.json
- analysis/remote/prng-plan-20260610T235310Z.json
- analysis/remote/observed-prng-solve.json
Analysis
Source review showed four important facts:
- The service requires exactly 100 comma-separated tokens.
- Valid numeric tokens are used as indexes into
story.txt; invalid numeric tokens can pad the 100-token length. - On SHA mismatch, the service prints the generated
user_input, which turnsstory.txtinto a remote oracle. - The suffix check uses
zip(forest, user_input[0x40:]), so an empty suffix passes once the SHA target is correct.
The hidden SHA target is generated from Python random seeded with ','.join(os.getenv("ANACONDA").split("A")). The seed is not present in the provided archive, so broad seed guessing was closed after source/story guesses, system dictionary, and rockyou candidates did not match the live randrange(100) fingerprint.
The decisive path was to exploit observable random behavior. A leak100 attempt with 100 single-character non-integer tokens prints 100 chosen indexes from random.randrange(100). A separate plan beginning with pad,leak100,leak100,... produced a later printed window that aligned with the first leak at offset 50. The alignment equation identifies the first hidden random value as 31. For a numeric-only first solve attempt, that value is the secret length, and the first 31 observed leak values are the secret bytes. Hashing those bytes gives the required SHA target.
Solve
Reproducible scripts:
solve/prng_oracle.pycollects structured random leak windows.solve/solve_observed_prng.pyinfers the first hidden secret length from the leak alignment, builds the SHA target from observed PRNG values, encodes the SHA hex digest using indexes from the leakedstory.txt, and submits the final numeric-only payload.
Final run:
cd <local workspace>
python3 scripts/challenge_exec.py Misc/Pydome -- python3 Misc/Pydome/solve/solve_observed_prng.py
python3 scripts/challenge_harness.py capture-flag Misc/Pydome --from loot/flag-candidate.txtThe solve transcript is saved at analysis/remote/observed-prng-solve.txt; raw flag material is stored only in loot/.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- Do not keep guessing a hidden environment seed when the challenge exposes deterministic runtime behavior.
- A failed comparison that prints attacker-controlled reconstructed data can become a file oracle.
- Truncated PRNG leaks do not always require full MT19937 recovery. Here, one alignment between two controlled leak plans was enough to infer the first hidden length and reconstruct the needed first-attempt secret.
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: Pydome
- Category: Misc
- Difficulty: Medium
- Mode: hybrid
- Remote instance: <TARGET>:32587
- Start time: 2026-06-10T22:40:26Z
- 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/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip | 1682 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-10T22:40:26Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-10T22:40:48Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-10T22:42:51Z | hypothesis recorded | hypothesis-board.md | Use the failed SHA comparison as a story.txt oracle, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check. | Medium | Leak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed. |
| 2026-06-10T22:42:51Z | checkpoint recorded | analysis/checkpoint-triage-20260610T224251744282Z-37116995.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-10T22:43:04Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-10T22:43:05Z | local memory search | analysis/research/local-memory-search-20260610T224305894888Z-72381fc8.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-10T22:43:47Z | RAG query | analysis/rag/rag-query-20260610T224315062546Z-ff87ac1d.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-10T22:44:14Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-10T22:44:25Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-10T22:44:26Z | evaluator | analysis/evaluator-20260610T224426102030Z-2e678cd5.md | Validate first | High | gate_before_remote_oracle_probe |
| 2026-06-10T22:45:10Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-10T22:45:19Z | evaluator | analysis/evaluator-20260610T224519341832Z-d0504787.md | Proceed | High | gate_before_remote_probe_then_run_solver |
| 2026-06-10T22:52:41Z | branch closed | hypothesis-board.md | All tested empty-secret random-shift and story/source-derived seed hash candidates returned SHA mismatch or crashed before hash; no flag returned. | High | Rerank hypotheses |
| 2026-06-10T23:00:02Z | evaluator | analysis/evaluator-20260610T230002507106Z-da861f2a.md | Proceed | High | run_bounded_state_search_solver |
| 2026-06-10T23:14:56Z | branch closed | hypothesis-board.md | Exact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates. | High | Rerank hypotheses |
| 2026-06-10T23:14:56Z | branch closed | hypothesis-board.md | SHA256(empty) failed at attempts 1-5 with pad-only prep; broader live state searches were too slow/noisy and produced no Level 4 or flag hit in saved transcripts. | High | Rerank hypotheses |
| 2026-06-10T23:15:08Z | checkpoint recorded | analysis/checkpoint-analysis-20260610T231508873810Z-7d352e2b.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-10T23:52:09Z | research task | analysis/research/task-20260610T235209831479Z-80843d5b.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-10T23:52:09Z | checkpoint recorded | analysis/checkpoint-analysis-20260610T235209871385Z-8a681c90.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-10T23:52:48Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-10T23:52:58Z | hypothesis recorded | hypothesis-board.md | Use the non-integer print path as a runtime PRNG oracle, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash. | Medium | Analyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts. |
| 2026-06-10T23:54:40Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-10T23:55:42Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- Extracted source is a single Python service:
analysis/extracted/misc_pydome/server.py. - The service requires exactly 100 comma-separated tokens.
- Valid numeric tokens index into remote
story.txt; invalid large numeric tokens pad the token count without contributing output. - On SHA mismatch, the service prints the generated
user_input, which makesstory.txtremotely leakable. The leaked story is saved atanalysis/remote/story.txt. - The leaked story is 1001 characters and contains all lowercase hexadecimal characters, so any SHA256 hex digest can be encoded as story indexes once the target digest is known.
- The forest check is weak:
all(zip(forest, user_input[0x40:]))means an empty suffix passes if the SHA target is correct. - The SHA target is generated from Python
randomseeded with','.join(os.getenv("ANACONDA").split("A")); theANACONDAvalue is not present in the provided files. - Live PRNG fingerprinting works.
analysis/remote/random-leak-n100.txtcaptures 100randrange(100)outputs from the non-integer print path. - Closed branches: obvious story/source seed guesses, system dictionary, rockyou, SHA256(empty) at attempts 1-5, and broad/noisy empty-state searches. See
hypothesis-board.mdandanalysis/remote/*summary.json. - Solved path: use
solve/prng_oracle.pyto collect observable PRNG windows, align thepad,leak100,...window against the first leak, infer the first hidden random value as the numeric-only secret length, hash the first observed leak values for that length, and submit the digest withsolve/solve_observed_prng.py. - Completion evidence:
analysis/remote/observed-prng-solve.jsonshowsflag_captured: true; the raw flag is stored only inloot/flag.txt.
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: Misc
- Challenge: Pydome
- 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.
Reusable Lessons
-
Dead Ends
-
Tool Quirks
-
Evidence Paths
-
Ingestion Decision
- Proposed for LightRAG: yes/no
- 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 | Use the failed SHA comparison as a story.txt oracle, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check. | server.py builds user_input from story_data indexes, prints user_input on SHA mismatch, pads ignored numeric indexes, and uses all(zip(forest, suffix)) instead of enforcing full forest length. | Leak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed. | Medium | Active | |
| 2 | Use the non-integer print path as a runtime PRNG oracle, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash. | analysis/remote/random-leak-n100-repeat-indexes.json proves deterministic first-window leakage; analysis/remote/prng-plan-20260610T235011-reparsed.json captures later same-connection windows; public hint research points toward observable behavior rather than guessing ANACONDA. | Analyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|---|---|---|---|
| Known empty-secret and story/source seed guesses | analysis/remote/attempt-summary.json | All tested empty-secret random-shift and story/source-derived seed hash candidates returned SHA mismatch or crashed before hash; no flag returned. | Only revisit if a new clue identifies the ANACONDA seed value or a reliable way to force zero-length secret. | |
| Dictionary/story seed recovery | analysis/remote/random-leak-n100.txt | Exact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates. | Only revisit with a narrower seed hint, Dockerfile/env leak, or a stronger method to recover Python random seed/state from truncated outputs. | |
| Empty-secret PRNG state search | analysis/remote/empty-attempt-position-summary.json | SHA256(empty) failed at attempts 1-5 with pad-only prep; broader live state searches were too slow/noisy and produced no Level 4 or flag hit in saved transcripts. | Revisit only with a local model of the exact PRNG seed or a more efficient state advancement strategy that avoids large non-integer print output. |
Memory Summary
approval_required: true
Sanitized Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Misc
- Challenge: Pydome
- 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.
Reusable Lessons
-
Dead Ends
-
Tool Quirks
-
Evidence Paths
-
Ingestion Decision
- Proposed for LightRAG: yes/no
- Requires user approval before ingestion: yes
Notes
Notes
Scope
- Challenge: Pydome
- Category: Misc
- Difficulty: Medium
- Mode: hybrid
- Remote instance: <TARGET>:32587
- Start time: 2026-06-10T22:40:26Z
- 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/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip | 1682 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-10T22:40:26Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-10T22:40:48Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-10T22: <REDACTED>, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check. | Medium | Leak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed. | |||
| 2026-06-10T22:42:51Z | checkpoint recorded | analysis/checkpoint-triage-20260610T224251744282Z-37116995.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-10T22:43:04Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-10T22:43:05Z | local memory search | analysis/research/local-memory-search-20260610T224305894888Z-72381fc8.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-10T22:43:47Z | RAG query | analysis/rag/rag-query-20260610T224315062546Z-ff87ac1d.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-10T22:44:14Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-10T22:44:25Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-10T22:44:26Z | evaluator | analysis/evaluator-20260610T224426102030Z-2e678cd5.md | Validate first | High | gate_before_remote_oracle_probe |
| 2026-06-10T22:45:10Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-10T22:45:19Z | evaluator | analysis/evaluator-20260610T224519341832Z-d0504787.md | Proceed | High | gate_before_remote_probe_then_run_solver |
| 2026-06-10T22: <REDACTED> | |||||
| 2026-06-10T23:00:02Z | evaluator | analysis/evaluator-20260610T230002507106Z-da861f2a.md | Proceed | High | run_bounded_state_search_solver |
| 2026-06-10T23:14:56Z | branch closed | hypothesis-board.md | Exact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates. | High | Rerank hypotheses |
| 2026-06-10T23: <REDACTED> | |||||
| 2026-06-10T23:15:08Z | checkpoint recorded | analysis/checkpoint-analysis-20260610T231508873810Z-7d352e2b.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-10T23:52:09Z | research task | analysis/research/task-20260610T235209831479Z-80843d5b.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-10T23:52:09Z | checkpoint recorded | analysis/checkpoint-analysis-20260610T235209871385Z-8a681c90.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-10T23:52:48Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-10T23: <REDACTED>, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash. | Medium | Analyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts. | |||
| 2026-06-10T23: <REDACTED> | |||||
| 2026-06-10T23:55:42Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- Extracted source is a single Python service:
analysis/extracted/misc_pydome/server.py. - The service requires exactly 100 comma-separated tokens.
- Valid numeric tokens index into remote
story.txt; invalid large numeric tokens pad the token count without contributing output. - On SHA mismatch, the service prints the generated
user_input, which makesstory.txtremotely leakable. The leaked story is saved atanalysis/remote/story.txt. - The leaked story is 1001 characters and contains all lowercase hexadecimal characters, so any SHA256 hex digest can be encoded as story indexes once the target digest is known.
- The forest check is weak:
all(zip(forest, user_input[0x40:]))means an empty suffix passes if the SHA target is correct. - The SHA target is generated from Python
randomseeded with','.join(os.getenv("ANACONDA").split("A")); theANACONDAvalue is not present in the provided files. - Live PRNG fingerprinting works.
analysis/remote/random-leak-n100.txtcaptures 100randrange(100)outputs from the non-integer print path. - Closed branches: obvious story/source seed guesses, system dictionary, rockyou, SHA256(empty) at attempts 1-5, and broad/noisy empty-state searches. See
hypothesis-board.mdandanalysis/remote/*summary.json. - Solved path: <REDACTED>, align the
pad,leak100,...window against the first leak, infer the first hidden random value as the numeric-only secret length, hash the first observed leak values for that length, and submit the digest withsolve/solve_observed_prng.py. - Completion evidence: <REDACTED>
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 challenge like a timed puzzle booth. If the task is too fast or repetitive for a person, the intended move is usually to write a small helper that performs the simple action perfectly.
For Pydome, 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.