Challenge / Crypto

RSAisEasy

RSAisEasy 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-15Sanitized local writeup

Scenario

RSAisEasy attack path

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

RSAisEasy sanitized attack graph

Walkthrough flow

01

Read the provided RSA generation script and identify...

02

Observe that the output leaks (n1 * E) + n2.

03

Compute gcd(n1, leak % n1) to recover the shared...

04

Compute p = n1 / q and decrypt the first ciphertext.

05

Divide the leak by q to get p * E + z.

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/RSAisEasy/writeup.md
  • htb-challenge/Crypto/RSAisEasy/notes.md
  • htb-challenge/Crypto/RSAisEasy/memory-summary.md
  • htb-challenge/Crypto/RSAisEasy/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__RSAisEasy__memory-summary.md.11b2b707af.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__RSAisEasy__notes.md.fddba09778.md

Technical Walkthrough

Writeup

Challenge

  • Name: RSAisEasy
  • Category: Crypto
  • Difficulty: Easy
  • Mode: file

Summary

The provided script uses two RSA moduli that share a prime: n1 = pq and n2 = qz. It does not print n2 directly, but it prints (n1 * E) + n2. Reducing this leak modulo n1 leaves n2 mod n1, which still shares the factor q with n1. From there, n1 can be factored, the first ciphertext can be decrypted, and the second modulus can be reconstructed enough to decrypt the second ciphertext.

Artifact Inventory

Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.

  • files/a12c734a-6ff7-4e31-8204-6d990803d383.zip: HTB ZIP archive.
  • analysis/extracted/RSAisEasy.py: RSA parameter generation script.
  • analysis/extracted/output.txt: public values n1, c1, c2, and (n1 * E) + n2.
  • solve/solve.py: reproducible offline solver.

Analysis

  1. The script generates three 512-bit primes p, q, and z.
  2. It builds n1 = pq and n2 = qz, so both moduli share q.
  3. Instead of printing n2, it prints leak = (n1 * E) + n2.
  4. leak % n1 removes the unknown multiple of n1 and leaves n2 mod n1.
  5. Since n2 is a multiple of q, gcd(n1, leak % n1) recovers q.
  6. p = n1 / q, so c1 decrypts normally with phi(n1) = (p-1)(q-1).
  7. Dividing the leak by q gives p * E + z. The remainder modulo p gives z, or z - p if z > p, so the solver tests remainder and remainder + p.
  8. Once z is recovered, n2 = q*z and c2 decrypts normally.

Solve

Run:

bash
python3 Crypto/RSAisEasy/solve/solve.py

The script parses output.txt, recovers q with gcd, reconstructs z, decrypts both RSA ciphertexts, joins both plaintext fragments, and prints the flag for harness capture.

Flag

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

Lessons

  • Shared-prime RSA failures are immediately exploitable with gcd.
  • Hiding one modulus as (known_modulus * random) + hidden_modulus does not protect the shared factor because modular reduction removes the random multiple.
  • When a hidden prime is added to a known-prime multiple, simple division/remainder reasoning can recover the missing prime with only one small candidate adjustment.

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: RSAisEasy
  • Category: Crypto
  • Difficulty: Easy
  • Mode: file
  • Remote instance: none
  • Start time: 2026-06-10T10:33: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/a12c734a-6ff7-4e31-8204-6d990803d383.zip1385<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T10:33:14Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T10:33:14Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T10:34:16Zhypothesis recordedhypothesis-board.mdRecover the shared RSA prime q from gcd(n1, ((n1 * E) + n2) mod n1), then factor n1 and reconstruct enough of n2 to decrypt both ciphertexts.MediumParse output.txt, compute q with gcd, derive p, test z candidates from dividing leak/q by p, and decrypt c1/c2 with RSA private exponents.
2026-06-10T10:34:16Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-10T10:34:52Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T10:34:52Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T103452480975Z-80251ba6.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T10:35:54Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • RSAisEasy.py generates n1 = pq and n2 = qz, so the moduli share prime q.
  • output.txt leaks (n1 * E) + n2 instead of n2 directly.
  • gcd(n1, leak % n1) recovers the shared prime q.
  • p = n1 / q decrypts c1.
  • leak / q = p * E + z; testing the remainder modulo p and remainder + p recovers z.
  • n2 = q*z decrypts c2.
  • solve/solve.py reproduces the full offline solve and the harness captured the flag into loot/flag.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: Crypto
  • Challenge: RSAisEasy
  • 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. Read the provided RSA generation script and identify that two moduli share a prime: n1 = pq, n2 = qz.
  2. Observe that the output leaks (n1 * E) + n2.
  3. Compute gcd(n1, leak % n1) to recover the shared prime q.
  4. Compute p = n1 / q and decrypt the first ciphertext.
  5. Divide the leak by q to get p * E + z.
  6. Recover z by testing the quotient remainder modulo p and remainder + p.
  7. Reconstruct n2 = q*z and decrypt the second ciphertext.
  8. Join both plaintext fragments.

Reusable Lessons

  • Shared-prime RSA challenges should start with gcd checks across any visible or partially visible modulus relationship.
  • A masked value of the form (known_modulus * random) + hidden_modulus still leaks the hidden modulus modulo the known one.
  • When primes have similar bit lengths, quotient/remainder relationships often leave only one or two candidates to test.

Dead Ends

  • No external research, Sage, Sympy, or CTF RSA tooling was required.
  • Brute force was unnecessary; the leak is algebraic.

Tool Quirks

  • PyCryptodome was available and sufficient for inverse, isPrime, and long_to_bytes.
  • Local sympy and sage were unavailable, but not needed.

Evidence Paths

  • analysis/extracted/RSAisEasy.py
  • analysis/extracted/output.txt
  • analysis/checkpoint-analysis-20260610T103452480975Z-80251ba6.md
  • 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 shared RSA prime q from gcd(n1, ((n1 * E) + n2) mod n1), then factor n1 and reconstruct enough of n2 to decrypt both ciphertexts.RSAisEasy.py creates n1=pq and n2=qz, then prints (n1 * E) + n2; output.txt provides n1, c1, c2, and the combined leak.Parse output.txt, compute q with gcd, derive p, test z candidates from dividing leak/q by p, and decrypt c1/c2 with RSA private exponents.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Crypto
  • Challenge: RSAisEasy
  • 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. Read the provided RSA generation script and identify that two moduli share a prime: n1 = pq, n2 = qz.
  2. Observe that the output leaks (n1 * E) + n2.
  3. Compute gcd(n1, leak % n1) to recover the shared prime q.
  4. Compute p = n1 / q and decrypt the first ciphertext.
  5. Divide the leak by q to get p * E + z.
  6. Recover z by testing the quotient remainder modulo p and remainder + p.
  7. Reconstruct n2 = q*z and decrypt the second ciphertext.
  8. Join both plaintext fragments.

Reusable Lessons

  • Shared-prime RSA challenges should start with gcd checks across any visible or partially visible modulus relationship.
  • A masked value of the form (known_modulus * random) + hidden_modulus still leaks the hidden modulus modulo the known one.
  • When primes have similar bit lengths, quotient/remainder relationships often leave only one or two candidates to test.

Dead Ends

  • No external research, Sage, Sympy, or CTF RSA tooling was required.
  • Brute force was unnecessary; the leak is algebraic.

Tool Quirks

  • PyCryptodome was available and sufficient for inverse, isPrime, and long_to_bytes.
  • Local sympy and sage were unavailable, but not needed.

Evidence Paths

  • analysis/extracted/RSAisEasy.py
  • analysis/extracted/output.txt
  • analysis/checkpoint-analysis-20260610T103452480975Z-80251ba6.md
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: RSAisEasy
  • Category: Crypto
  • Difficulty: Easy
  • Mode: file
  • Remote instance: none
  • Start time: 2026-06-10T10:33: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/a12c734a-6ff7-4e31-8204-6d990803d383.zip1385<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T10:33:14Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T10:33:14Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T10:34:16Zhypothesis recordedhypothesis-board.mdRecover the shared RSA prime q from gcd(n1, ((n1 * E) + n2) mod n1), then factor n1 and reconstruct enough of n2 to decrypt both ciphertexts.MediumParse output.txt, compute q with gcd, derive p, test z candidates from dividing leak/q by p, and decrypt c1/c2 with RSA private exponents.
2026-06-10T10:34:16Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-10T10: <REDACTED>
2026-06-10T10:34:52Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T103452480975Z-80251ba6.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T10:35:54Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • RSAisEasy.py generates n1 = pq and n2 = qz, so the moduli share prime q.
  • output.txt leaks (n1 * E) + n2 instead of n2 directly.
  • gcd(n1, leak % n1) recovers the shared prime q.
  • p = n1 / q decrypts c1.
  • leak / q = p * E + z; testing the remainder modulo p and remainder + p recovers z.
  • n2 = q*z decrypts c2.
  • solve/solve.py reproduces the full offline solve and the harness captured the flag into loot/flag.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 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 RSAisEasy, 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.