QLotto
QLotto is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
QLotto attack path
QLotto 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 Quantum evidence, validation, and reusable operator lessons.
Walkthrough flow
Circuit constraints
State manipulation
Measurement strategy
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.
- Quantum/QLotto/writeup.md
- htb-challenge/Quantum/QLotto/notes.md
- htb-challenge/Quantum/QLotto/memory-summary.md
- htb-challenge/Quantum/QLotto/hypothesis-board.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Quantum__QLotto__memory-summary.md.832a34c79f.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Quantum__QLotto__notes.md.992e3e784d.md
Technical Walkthrough
Writeup
Challenge
- Name: QLotto
- Category: Quantum
- Difficulty: Easy
- Mode: hybrid
Summary
This challenge is solved from server.py. The bug is that the input filter blocks qubit index 0 but does not block negative indices, so -2 and -1 reach Qiskit unchanged. A locally validated circuit uses that gap to act on q0 and q1, keeps q0 fair for the entropy gate, and forces the printed testing bits to be the per-shot complement of the hidden lotto bits. Once that relationship is proven, each printed number t maps to the hidden winning number with ((22 - t) % 42) + 1.
Artifact Inventory
Relevant artifacts:
files/extracted/quantum_qlotto/server.pyanalysis/source-audit.mdanalysis/local-validation/negative-index-validation.txtanalysis/remote/remote-session.txtsolve/solve.py
Analysis
server.py builds a 2-qubit QuantumCircuit, applies H(0), accepts user gate instructions, validates entropy by measuring q0 over 100,000 shots, then measures both qubits over 36 shots with memory=True.
The key parser weakness is in generate_circuit():
- it rejects any parameter exactly equal to
0 - it rejects any qubit index
>= circuit.num_qubits - it does not reject negative indices
That means -2 and -1 are forwarded into Qiskit gate calls. The candidate circuit is:
H:-1;Z:-1;S:-2;S:-1;RZZ:-90,-2,-1;H:-1Local validation in analysis/local-validation/negative-index-validation.txt proved four things:
generate_circuit()accepts the candidate unchanged.validate_entropy()still returns true, so the q0 fairness gate is satisfied.measure_all()returns two-bit memory strings that are anti-correlated on every shot.- Feeding those same memory strings into
extract_numbers()shows that the printedtesting_numbersmap back to the hiddenlotto_numberswith:
lotto = ((22 - t) % 42) + 1This works because extract_numbers() builds the printed number from the first bit of each two-bit string and the hidden number from the second bit. With anti-correlation, the underlying six-bit values are complements before the % 42 + 1 reduction.
Solve
Run:
cd <local workspace>
python3 Quantum/QLotto/solve/solve.pyThe solver:
- Connects to the TCP service.
- Sends the validated negative-index circuit.
- Parses
[Dealer] Your draws are: [...]. - Derives the six hidden numbers with
((22 - t) % 42) + 1. - Sends the guesses back to the service.
- Writes the returned flag candidate to
loot/flag-candidate.txt. - Saves a sanitized transcript to
analysis/remote/remote-session.txt.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- In Qiskit-backed challenge services, negative indices are worth checking whenever validation only blocks
0or>= n. - For
measure_all()plusmemory=True, proving string bit order once can turn a fuzzy hypothesis into a direct solve.
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: QLotto
- Category: Quantum
- Difficulty: Easy
- Mode: hybrid
- Remote instance: <TARGET>:31927
- Start time: 2026-06-07T19:32:55Z
- 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/a12c7333-7db1-4dfc-9a1a-61b70ff1e2d3.zip | 2155 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
files/extracted/quantum_qlotto/server.py | 6382 | <hash redacted> | Python script text executable, Unicode text, UTF-8 text, with CRLF line terminators |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-07T19:32:55Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-07T19:33:04Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T19:33:15Z | artifact inventory | analysis/artifact-inventory.json | 2 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T19:34:50Z | hypothesis recorded | hypothesis-board.md | Exploit negative qubit indices to bypass the server's no-qubit-0 rule. Use -2 for q0 and -1 for q1, synthesize an anti-correlating circuit so the printed testing numbers reveal the hidden lotto numbers by complement mapping, then submit the derived six-number guess. | High | Install/use Qiskit locally or make one safe remote probe with the candidate negative-index instructions to confirm the service accepts them and prints testing numbers rather than rejecting the move. |
| 2026-06-07T19:35:16Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-07T19:35:16Z | local memory search | analysis/research/local-memory-search-20260607T193516529131Z-44c23af5.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-07T19:35:16Z | research task | analysis/research/task-20260607T193516530323Z-a85854da.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-07T19:35:16Z | checkpoint recorded | analysis/checkpoint-hypothesis_ready-20260607T193516531678Z-18854255.md | Checkpoint for <secret redacted> | High | Use checkpoint to drive next decision |
| 2026-06-07T19:35:37Z | RAG query | analysis/rag/rag-query-20260607T193526422196Z-70be8426.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-07T19:36:06Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-07T19:36:06Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-07T19:36:06Z | evaluator | analysis/evaluator-20260607T193606901079Z-8c17b897.md | Validate first | High | Validate Qiskit negative-index and bit-order behavior; if confirmed, record Proceed and run the minimal solve. |
| 2026-06-07T19:41:15Z | evaluator | analysis/evaluator-20260607T194115022179Z-0dece534.md | Proceed | High | run solve/solve.py once against the live target and capture the resulting jackpot flag from loot/flag-candidate.txt |
| 2026-06-07T19:41:33Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-07T19:42:14Z | RAG record | analysis/rag-records.md | Retrieved memory tagged PARTIAL | Medium | Validate or reject with live evidence |
| 2026-06-07T19:42:38Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- Local Qiskit validation in
analysis/local-validation/negative-index-validation.txtconfirmed thatgenerate_circuit()accepts the candidate negative-index circuit and does not reject-2/-1. - The candidate circuit
H:-1;Z:-1;S:-2;S:-1;RZZ:-90,-2,-1;H:-1preserves fair entropy on q0 while producing per-shot anti-correlation in the measured two-bit memory strings. extract_numbers()interprets each two-bit memory string left-to-right as(testing_bit, lotto_bit). Undermeasure_all(), the simulator returnedq1q0, so printedtesting_numberscome from q1 and hiddenlotto_numberscome from q0.- The proven mapping is
lotto = ((22 - t) % 42) + 1for each printed testing numbert. - Reproducible remote solve is implemented in
solve/solve.py, and the sanitized live transcript is stored inanalysis/remote/remote-session.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: Quantum
- Challenge: QLotto
- 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.
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 | Exploit negative qubit indices to bypass the server's no-qubit-0 rule. Use -2 for q0 and -1 for q1, synthesize an anti-correlating circuit so the printed testing numbers reveal the hidden lotto numbers by complement mapping, then submit the derived six-number guess. | server.py forbids p == 0 and n >= circuit.num_qubits, but does not reject negative qubit indices. With two qubits, Python/Qiskit-style indexing may map -2 to q0 and -1 to q1. The game prints testing_numbers derived from one measured bit and asks for lotto_numbers derived from the other bit. | Install/use Qiskit locally or make one safe remote probe with the candidate negative-index instructions to confirm the service accepts them and prints testing numbers rather than rejecting the move. | High | 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: Quantum
- Challenge: QLotto
- 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.
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: QLotto
- Category: Quantum
- Difficulty: Easy
- Mode: hybrid
- Remote instance: <TARGET>:31927
- Start time: 2026-06-07T19:32:55Z
- 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/a12c7333-7db1-4dfc-9a1a-61b70ff1e2d3.zip | 2155 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
files/extracted/quantum_qlotto/server.py | 6382 | <hash redacted> | Python script text executable, Unicode text, UTF-8 text, with CRLF line terminators |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-07T19:32:55Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-07T19:33:04Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T19:33:15Z | artifact inventory | analysis/artifact-inventory.json | 2 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T19:34:50Z | hypothesis recorded | hypothesis-board.md | Exploit negative qubit indices to bypass the server's no-qubit-0 rule. Use -2 for q0 and -1 for q1, synthesize an anti-correlating circuit so the printed testing numbers reveal the hidden lotto numbers by complement mapping, then submit the derived six-number guess. | High | Install/use Qiskit locally or make one safe remote probe with the candidate negative-index instructions to confirm the service accepts them and prints testing numbers rather than rejecting the move. |
| 2026-06-07T19:35:16Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-07T19:35:16Z | local memory search | analysis/research/local-memory-search-20260607T193516529131Z-44c23af5.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-07T19:35:16Z | research task | analysis/research/task-20260607T193516530323Z-a85854da.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-07T19:35:16Z | checkpoint recorded | analysis/checkpoint-hypothesis_ready-20260607T193516531678Z-18854255.md | Checkpoint for <secret redacted> | High | Use checkpoint to drive next decision |
| 2026-06-07T19:35:37Z | RAG query | analysis/rag/rag-query-20260607T193526422196Z-70be8426.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-07T19:36:06Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-07T19:36:06Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-07T19:36:06Z | evaluator | analysis/evaluator-20260607T193606901079Z-8c17b897.md | Validate first | High | Validate Qiskit negative-index and bit-order behavior; if confirmed, record Proceed and run the minimal solve. |
| 2026-06-07T19: <REDACTED> | |||||
| 2026-06-07T19: <REDACTED> | |||||
| 2026-06-07T19:42:14Z | RAG record | analysis/rag-records.md | Retrieved memory tagged PARTIAL | Medium | Validate or reject with live evidence |
| 2026-06-07T19:42:38Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- Local Qiskit validation in
analysis/local-validation/negative-index-validation.txtconfirmed thatgenerate_circuit()accepts the candidate negative-index circuit and does not reject-2/-1. - The candidate circuit
H:-1;Z:-1;S:-2;S:-1;RZZ:-90,-2,-1;H:-1preserves fair entropy on q0 while producing per-shot anti-correlation in the measured two-bit memory strings. extract_numbers()interprets each two-bit memory string left-to-right as(testing_bit, lotto_bit). Undermeasure_all(), the simulator returnedq1q0, so printedtesting_numberscome from q1 and hiddenlotto_numberscome from q0.- The proven mapping is
lotto = ((22 - t) % 42) + 1for each printed testing numbert. - Reproducible remote solve is implemented in
solve/solve.py, and the sanitized live transcript is stored inanalysis/remote/remote-session.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.
Technical analogy
How to remember this solve
Think of the quantum circuit like a fragile sequence of switches. The solve is about arranging the switches so the final measurement lands in the one state that unlocks the flag.
For QLotto, 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.