Challenge / Crypto

Rhome

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

EasyPublished 2024-05-12Sanitized local writeup

Scenario

Rhome attack path

Rhome 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 Crypto evidence, validation, and reusable operator lessons.

Rhome sanitized attack graph

Walkthrough flow

01

Audit server.py.

02

Identify Diffie-Hellman parameters generated as p =...

03

Identify g = h^(2*r) mod p, constraining public keys...

04

Query the remote service for p, g, A, B, and...

05

Factor (p - 1) / 2 with Pollard Rho to recover 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.

  • Crypto/Rhome/writeup.md
  • htb-challenge/Crypto/Rhome/notes.md
  • htb-challenge/Crypto/Rhome/memory-summary.md
  • htb-challenge/Crypto/Rhome/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__Rhome__memory-summary.md.7fe27cfb46.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__Rhome__notes.md.72511f680b.md

Technical Walkthrough

Writeup

Challenge

  • Name: Rhome
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid

Summary

The service implements Diffie-Hellman, but it deliberately places the generator in a tiny subgroup. The modulus is generated as p = 2qr + 1 with q only 42 bits, and g = h^(2*r) mod p, so the public keys A and B live in the subgroup of order q. Factoring p-1 recovers q, and baby-step/giant-step recovers the exponent modulo q, which is enough to derive the shared secret and decrypt the AES-ECB flag ciphertext.

Artifact Inventory

  • Original archive: files/a12c7355-a35e-4808-a203-839a95532bec.zip
  • Extracted source: files/extracted/crypto_rhome/server.py
  • Remote service: <TARGET>:32375
  • Key evidence:

- analysis/source-audit.md

- analysis/solve-run-summary.json

- analysis/remote-transcript-redacted.txt

Analysis

server.py creates parameters as:

python
self.q = getPrime(42)
self.p = (2 * self.q * self.r) + 1
self.g = pow(self.h, 2 * self.r, self.p)

Since p - 1 = 2qr, exponentiating h by 2*r removes the large cofactor and leaves g in a subgroup whose order divides the 42-bit prime q. The service exposes:

text
p
g
A = g^a mod p
B = g^b mod p

It encrypts the flag with:

python
key = sha256(long_to_bytes(ss)).digest()[:16]
AES.new(key, <secret redacted>)

The attack is:

  1. Factor (p - 1) / 2 to recover the small q.
  2. Solve A = g^a mod p in the order-q subgroup with baby-step/giant-step.
  3. Compute ss = B^a mod p.
  4. Derive the AES key and decrypt the ciphertext.

RAG did not return useful Rhome-specific guidance, so the solve used the current source and remote transcript as evidence; see analysis/rag-records.md.

Solve

The reproducible solver is solve/solve.py.

It performs:

  1. Connects to the remote service and requests parameters plus encrypted flag.
  2. Uses Pollard Rho to recover the 42-bit subgroup factor q from p-1.
  3. Uses baby-step/giant-step to recover a mod q.
  4. Computes the shared secret as B^a mod p.
  5. Decrypts the AES-ECB ciphertext.
  6. Writes the raw flag candidate to loot/flag-candidate.txt.

The successful run is summarized in analysis/solve-run-summary.json. The harness captured the result into loot/flag.txt.

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • A large prime modulus does not help if the generator is constrained to a small subgroup.
  • In Diffie-Hellman, validate the generator order and public-key subgroup membership.
  • A 42-bit discrete log is practical with baby-step/giant-step.
  • Keep raw decrypted flags in loot/; store only summaries and hashes 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: Rhome
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: <TARGET>:32375
  • Start time: 2026-06-09T11:25:38Z
  • 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/a12c7355-a35e-4808-a203-839a95532bec.zip1044<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/crypto_rhome/server.py1753<hash redacted>Python script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T11:25:38Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T11:25:51Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T11:26:33Zhypothesis recordedhypothesis-board.mdRecover the Diffie-Hellman shared secret by exploiting the deliberately small 42-bit subgroup order, then decrypt the AES-ECB ciphertext returned by the remote flag option.MediumFactor p-1 to recover the 42-bit q, solve discrete log A = g^a in subgroup order q with BSGS, derive ss = B^a mod p, then decrypt one remote ciphertext.
2026-06-09T11:26:33Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-09T11:26:33Zcheckpoint recordedanalysis/checkpoint-analysis-20260609T112633261307Z-9d7cc7ea.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-09T11:26:51ZRAG queryanalysis/rag/rag-query-20260609T112642524135Z-6c969f14.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-09T11:27:17ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-09T11:27:48Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-09T11:28:54Zevaluatoranalysis/evaluator-20260609T112854618618Z-e9b9a79b.mdProceedHighRun exploit gate and execute solve/solve.py through challenge_exec.
2026-06-09T11:29:23Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-09T11:30:24Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • server.py exposes p, g, A, and B through menu option 1.
  • Parameters are generated as p = 2qr + 1 where q is only a 42-bit prime.
  • g = h^(2*r) mod p, so public keys live in the small subgroup whose order divides q.
  • The shared secret can be recovered by factoring p-1 to recover q, solving A = g^a mod p in that subgroup, then computing B^a mod p.
  • The flag ciphertext is AES-ECB with key SHA256(long_to_bytes(shared_secret))[:16].
  • RAG did not return a useful Rhome-specific match; use source audit and live remote parameters as evidence.

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: Crypto
  • Challenge: Rhome
  • 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 server.py.
  2. Identify Diffie-Hellman parameters generated as p = 2qr + 1 with q only 42 bits.
  3. Identify g = h^(2*r) mod p, constraining public keys to the small subgroup of order q.
  4. Query the remote service for p, g, A, B, and ciphertext.
  5. Factor (p - 1) / 2 with Pollard Rho to recover the 42-bit q.
  6. Use baby-step/giant-step to solve A = g^a mod p modulo q.
  7. Compute B^a mod p, derive SHA256(long_to_bytes(shared_secret))[:16], and decrypt AES-ECB.
  8. Store the raw flag under loot/ only.

Reusable Lessons

  • Small-subgroup Diffie-Hellman can make an otherwise large modulus breakable.
  • If p - 1 has a small factor and g is projected into that subgroup, BSGS over the small order is enough.
  • Pollard Rho plus BSGS is practical for a 42-bit subgroup without Sage/SymPy.
  • Store discrete-log exponents and shared secrets as hashes in analysis artifacts, not raw values.

Dead Ends

  • RAG returned no useful Rhome-specific or matching small-subgroup DH memory. The solve used current source and live remote evidence.

Tool Quirks

  • Local sympy, z3, sage, and pwntools were unavailable. Pure Python, sockets, and PyCryptodome were sufficient.
  • Baby-step/giant-step for a 42-bit subgroup uses a few million table entries but completed quickly enough locally.

Evidence Paths

  • analysis/source-audit.md
  • analysis/checkpoint-analysis-20260609T112633261307Z-9d7cc7ea.md
  • analysis/rag/rag-query-20260609T112642524135Z-6c969f14.txt
  • analysis/rag-records.md
  • analysis/evaluator-20260609T112854618618Z-e9b9a79b.md
  • 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
1Recover the Diffie-Hellman shared secret by exploiting the deliberately small 42-bit subgroup order, then decrypt the AES-ECB ciphertext returned by the remote flag option.server.py constructs p = 2qr + 1 with q = getPrime(42), sets g = h^(2*r) mod p, and exposes p,g,A,B plus encrypted FLAG under SHA256(shared_secret)[:16].Factor p-1 to recover the 42-bit q, solve discrete log A = g^a in subgroup order q with BSGS, derive ss = B^a mod p, then decrypt one remote ciphertext.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Crypto
  • Challenge: Rhome
  • 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 server.py.
  2. Identify Diffie-Hellman parameters generated as p = 2qr + 1 with q only 42 bits.
  3. Identify g = h^(2*r) mod p, constraining public keys to the small subgroup of order q.
  4. Query the remote service for p, g, A, B, and ciphertext.
  5. Factor (p - 1) / 2 with Pollard Rho to recover the 42-bit q.
  6. Use baby-step/giant-step to solve A = g^a mod p modulo q.
  7. Compute B^a mod p, derive SHA256(long_to_bytes(shared_secret))[:16], and decrypt AES-ECB.
  8. Store the raw flag under loot/ only.

Reusable Lessons

  • Small-subgroup Diffie-Hellman can make an otherwise large modulus breakable.
  • If p - 1 has a small factor and g is projected into that subgroup, BSGS over the small order is enough.
  • Pollard Rho plus BSGS is practical for a 42-bit subgroup without Sage/SymPy.
  • Store discrete-log exponents and shared secrets as hashes in analysis artifacts, not raw values.

Dead Ends

  • RAG returned no useful Rhome-specific or matching small-subgroup DH memory. The solve used current source and live remote evidence.

Tool Quirks

  • Local sympy, z3, sage, and pwntools were unavailable. Pure Python, sockets, and PyCryptodome were sufficient.
  • Baby-step/giant-step for a 42-bit subgroup uses a few million table entries but completed quickly enough locally.

Evidence Paths

  • analysis/source-audit.md
  • analysis/checkpoint-analysis-20260609T112633261307Z-9d7cc7ea.md
  • analysis/rag/rag-query-20260609T112642524135Z-6c969f14.txt
  • analysis/rag-records.md
  • analysis/evaluator-20260609T112854618618Z-e9b9a79b.md
  • 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: Rhome
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: <TARGET>:32375
  • Start time: 2026-06-09T11:25:38Z
  • 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/a12c7355-a35e-4808-a203-839a95532bec.zip1044<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/crypto_rhome/server.py1753<hash redacted>Python script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T11:25:38Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T11:25:51Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T11: <REDACTED>, then decrypt the AES-ECB ciphertext returned by the remote flag option.MediumFactor p-1 to recover the 42-bit q, solve discrete log A = <REDACTED>, derive ss = <REDACTED>, then decrypt one remote ciphertext.
2026-06-09T11:26:33Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-09T11:26:33Zcheckpoint recordedanalysis/checkpoint-analysis-20260609T112633261307Z-9d7cc7ea.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-09T11:26:51ZRAG queryanalysis/rag/rag-query-20260609T112642524135Z-6c969f14.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-09T11:27:17ZRAG recordanalysis/rag-records.mdRetrieved memory tagged MISSINGMediumValidate or reject with live evidence
2026-06-09T11:27:48Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-09T11:28:54Zevaluatoranalysis/evaluator-20260609T112854618618Z-e9b9a79b.mdProceedHighRun exploit gate and execute solve/solve.py through challenge_exec.
2026-06-09T11: <REDACTED>
2026-06-09T11:30:24Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • server.py exposes p, g, A, and B through menu option 1.
  • Parameters are generated as p = 2qr + 1 where q is only a 42-bit prime.
  • g = h^(2*r) mod p, so public keys live in the small subgroup whose order divides q.
  • The shared secret can be recovered by factoring p-1 to recover q, solving A = <REDACTED>, then computing B^a mod p`.
  • The flag ciphertext is AES-ECB with key `SHA256(long_to_bytes(shared_secret))[: <REDACTED>
  • RAG did not return a useful Rhome-specific match; use source audit and live remote parameters as evidence.

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 challenge like a locked box where the lock is mathematical but slightly flawed. The goal is not to smash the box; it is to notice which part of the lock repeats, leaks, or trusts the wrong assumption.

For Rhome, 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.