Challenge / Quantum

QLotto

QLotto is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

EasyPublished 2025-05-21Sanitized local writeup

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.

QLotto sanitized attack graph

Walkthrough flow

01

Circuit constraints

02

State manipulation

03

Measurement strategy

04

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.

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.

  • 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.py
  • analysis/source-audit.md
  • analysis/local-validation/negative-index-validation.txt
  • analysis/remote/remote-session.txt
  • solve/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:

text
H:-1;Z:-1;S:-2;S:-1;RZZ:-90,-2,-1;H:-1

Local validation in analysis/local-validation/negative-index-validation.txt proved four things:

  1. generate_circuit() accepts the candidate unchanged.
  2. validate_entropy() still returns true, so the q0 fairness gate is satisfied.
  3. measure_all() returns two-bit memory strings that are anti-correlated on every shot.
  4. Feeding those same memory strings into extract_numbers() shows that the printed testing_numbers map back to the hidden lotto_numbers with:
python
lotto = ((22 - t) % 42) + 1

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

bash
cd <local workspace>
python3 Quantum/QLotto/solve/solve.py

The solver:

  1. Connects to the TCP service.
  2. Sends the validated negative-index circuit.
  3. Parses [Dealer] Your draws are: [...].
  4. Derives the six hidden numbers with ((22 - t) % 42) + 1.
  5. Sends the guesses back to the service.
  6. Writes the returned flag candidate to loot/flag-candidate.txt.
  7. 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 0 or >= n.
  • For measure_all() plus memory=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

FileSizeSHA256TypeNotes
files/a12c7333-7db1-4dfc-9a1a-61b70ff1e2d3.zip2155<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/quantum_qlotto/server.py6382<hash redacted>Python script text executable, Unicode text, UTF-8 text, with CRLF line terminators

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-07T19:32:55Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-07T19:33:04Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T19:33:15Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T19:34:50Zhypothesis recordedhypothesis-board.mdExploit 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.HighInstall/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:16Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T19:35:16Zlocal memory searchanalysis/research/local-memory-search-20260607T193516529131Z-44c23af5.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-07T19:35:16Zresearch taskanalysis/research/task-20260607T193516530323Z-a85854da.mdResearch task created for advisory investigationMediumRecord research output
2026-06-07T19:35:16Zcheckpoint recordedanalysis/checkpoint-hypothesis_ready-20260607T193516531678Z-18854255.mdCheckpoint for <secret redacted>HighUse checkpoint to drive next decision
2026-06-07T19:35:37ZRAG queryanalysis/rag/rag-query-20260607T193526422196Z-70be8426.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-07T19:36:06Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-07T19:36:06ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-07T19:36:06Zevaluatoranalysis/evaluator-20260607T193606901079Z-8c17b897.mdValidate firstHighValidate Qiskit negative-index and bit-order behavior; if confirmed, record Proceed and run the minimal solve.
2026-06-07T19:41:15Zevaluatoranalysis/evaluator-20260607T194115022179Z-0dece534.mdProceedHighrun solve/solve.py once against the live target and capture the resulting jackpot flag from loot/flag-candidate.txt
2026-06-07T19:41:33Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-07T19:42:14ZRAG recordanalysis/rag-records.mdRetrieved memory tagged PARTIALMediumValidate or reject with live evidence
2026-06-07T19:42:38Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Local Qiskit validation in analysis/local-validation/negative-index-validation.txt confirmed that generate_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:-1 preserves 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). Under measure_all(), the simulator returned q1q0, so printed testing_numbers come from q1 and hidden lotto_numbers come from q0.
  • The proven mapping is lotto = ((22 - t) % 42) + 1 for each printed testing number t.
  • Reproducible remote solve is implemented in solve/solve.py, and the sanitized live transcript is stored in analysis/remote/remote-session.txt.

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

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Exploit 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.HighActive

Closed Branches

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

FileSizeSHA256TypeNotes
files/a12c7333-7db1-4dfc-9a1a-61b70ff1e2d3.zip2155<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/quantum_qlotto/server.py6382<hash redacted>Python script text executable, Unicode text, UTF-8 text, with CRLF line terminators

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-07T19:32:55Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-07T19:33:04Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T19:33:15Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T19:34:50Zhypothesis recordedhypothesis-board.mdExploit 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.HighInstall/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:16Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T19:35:16Zlocal memory searchanalysis/research/local-memory-search-20260607T193516529131Z-44c23af5.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-07T19:35:16Zresearch taskanalysis/research/task-20260607T193516530323Z-a85854da.mdResearch task created for advisory investigationMediumRecord research output
2026-06-07T19:35:16Zcheckpoint recordedanalysis/checkpoint-hypothesis_ready-20260607T193516531678Z-18854255.mdCheckpoint for <secret redacted>HighUse checkpoint to drive next decision
2026-06-07T19:35:37ZRAG queryanalysis/rag/rag-query-20260607T193526422196Z-70be8426.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-07T19:36:06Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-07T19:36:06ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-07T19:36:06Zevaluatoranalysis/evaluator-20260607T193606901079Z-8c17b897.mdValidate firstHighValidate 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:14ZRAG recordanalysis/rag-records.mdRetrieved memory tagged PARTIALMediumValidate or reject with live evidence
2026-06-07T19:42:38Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Local Qiskit validation in analysis/local-validation/negative-index-validation.txt confirmed that generate_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:-1 preserves 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). Under measure_all(), the simulator returned q1q0, so printed testing_numbers come from q1 and hidden lotto_numbers come from q0.
  • The proven mapping is lotto = ((22 - t) % 42) + 1 for each printed testing number t.
  • Reproducible remote solve is implemented in solve/solve.py, and the sanitized live transcript is stored in analysis/remote/remote-session.txt.

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