Challenge / Crypto

YALM

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

MediumPublished 2024-06-12Sanitized local writeup

Scenario

YALM attack path

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

YALM sanitized attack graph

Walkthrough flow

01

Source audit showed textbook RSA with e = 3, hidden...

02

The tester endpoint leaked a less-than oracle for N:...

03

Recover the exact modulus by binary-searching that...

04

Direct integer cube root failed because the...

05

Use known-prefix Coppersmith/LLL over (prefix * 256^L...

Source coverage

High source coverage

Status: complete. This article is generated from 4 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/YALM/writeup.md
  • htb-challenge/Crypto/YALM/notes.md
  • htb-challenge/Crypto/YALM/memory-summary.md
  • htb-challenge/Crypto/YALM/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: YALM
  • Category: Crypto
  • Difficulty: Medium
  • Mode: hybrid

Summary

YALM is a textbook RSA challenge with e = 3, a hidden modulus, and a source-provided comparison oracle. The server does not reveal N, but option 2 leaks whether a submitted integer is below N. That leak is enough to recover the exact modulus with binary search. Once N is known, the secret ciphertext is vulnerable to a low-exponent known-prefix Coppersmith attack.

Artifact Inventory

Reference: analysis/artifact-inventory.json.

  • files/a12c7341-ab2d-42a2-a0c4-540b0a4f2a03.zip: original HTB archive.
  • files/extracted/crypto_yalm/server.py: source for the RSA service.
  • Remote service: <TARGET>:31578.

Analysis

server.py defines:

python
self.e = 3
self.n = N
c = pow(m, self.e, self.n)

The secret plaintext has a long fixed prefix and the flag appended. Option 1 returns the ciphertext. Option 2 accepts a hex integer and repeatedly divides it by N; the response changes when the submitted integer is at least N. This creates a less-than oracle:

  • success response: submitted integer < N
  • failure response: submitted integer >= N

The exact modulus was recovered by using the ciphertext as a lower bound and binary-searching the oracle boundary. The resulting public values are stored in analysis/recovered-public-values.json.

The ciphertext was not an exact integer cube, so direct iroot(c, 3) was not enough. With N recovered, the solver models the plaintext as:

text
m = known_prefix * 256^flag_length + x

and solves (known_prefix * 256^flag_length + x)^3 - c == 0 mod N for small x using Coppersmith/LLL. The recovered suffix was validated against the HTB flag wrapper and captured through the harness.

Solve

The workspace contains two reproducible scripts:

bash
python3 Crypto/YALM/solve/recover_public.py \
  --host <TARGET> \
  --port 31578 \
  --output Crypto/YALM/analysis/recovered-public-values.json

This recovers the public ciphertext and exact hidden modulus.

bash
python3 Crypto/YALM/solve/solve.py \
  --workspace <local workspace> \
  --host <TARGET> \
  --port 31578

solve.py can run the full chain. During this run, the modulus recovery was separated into recover_public.py because the remote oracle was latency-bound from the local network path. The final local Coppersmith phase used the recovered public values and wrote loot/flag-candidate.txt, which the harness captured into loot/flag.txt.

Flag

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

Lessons

  • A non-output encryption tester can still leak a modulus if the control flow changes at m >= N.
  • For e = 3 textbook RSA, known-prefix Coppersmith is the right fallback when direct cube root fails.
  • When an oracle is latency-bound, split public-value recovery from local cryptanalysis so the expensive remote phase is isolated and resumable.

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: YALM
  • Category: Crypto
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: <TARGET>:31578
  • Start time: 2026-06-13T00:45:28Z
  • 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/a12c7341-ab2d-42a2-a0c4-540b0a4f2a03.zip897<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/crypto_yalm/server.py1134<hash redacted>Python script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-13T00:45:28Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-13T00:45:28Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-13T00:46:06Zhypothesis recordedhypothesis-board.mdRecover the hidden RSA modulus N with the option-2 less-than-N oracle, then decrypt the option-1 e=3 ciphertext by integer cube root if m^3 < N.MediumBuild a local simulator and a remote probe that classifies small and huge plaintext values, then binary search N and test exact integer cube root of the secret ciphertext.
2026-06-13T00:46:06Zcheckpoint recordedanalysis/checkpoint-triage-20260613T004606500017Z-a1bf4374.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-13T00:46:36ZRAG queryanalysis/rag/rag-query-20260613T004615008249Z-034a9743.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-13T00:46:46Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-13T00:47:15ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-13T00:47:15Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T00:47:15Zinstrumentation plananalysis/instrumentation-plan.mdRecover hidden RSA modulus N from the live service and decrypt the option-1 secret.HighIf exact cube root fails after N recovery, stop direct attack and pivot to a known-prefix low-exponent method rather than blind guessing.
2026-06-13T00:49:08Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-13T00:49:08Zevaluatoranalysis/evaluator-20260613T004908553200Z-b9330951.mdProceedHighRun solver against <TARGET>:31578 and capture loot/flag-candidate.txt if exact cube root and boundary validation pass.
2026-06-13T01:38:50Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-13T01:40:17Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • files/extracted/crypto_yalm/server.py uses textbook RSA with e = 3 and a hidden modulus N.
  • Option 1 returns the secret ciphertext c = m^3 mod N for a known-prefix message containing the flag.
  • Option 2 leaks a comparison oracle: Thanks for the message! means the submitted integer is below N, while Too many messages! means it is at least N.
  • The planned solve is to recover N with doubling plus binary search, validate the boundary against the live oracle, then try exact integer cube-root recovery of the unpadded e=3 ciphertext.
  • Direct cube root was not exact; the final solve used the recovered modulus with a known-prefix Coppersmith/LLL attack.
  • Raw flag material is stored only under loot/; public N and ciphertext are stored in analysis/recovered-public-values.json.

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: YALM
  • 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. Source audit showed textbook RSA with e = 3, hidden N, and a known-prefix secret message.
  2. The tester endpoint leaked a less-than oracle for N: one response for submitted integers below N, another response for integers at least N.
  3. Recover the exact modulus by binary-searching that oracle, using the ciphertext as a lower bound.
  4. Direct integer cube root failed because the ciphertext was reduced modulo N.
  5. Use known-prefix Coppersmith/LLL over (prefix * 256^L + x)^3 - c mod N and scan plausible flag lengths.
  6. Validate the recovered suffix with the HTB flag wrapper and capture it through the harness.

Reusable Lessons

  • Hidden RSA modulus challenges may expose enough control-flow leakage to recover N even when no encryption output is returned.
  • For low exponent RSA with a long known prefix and short unknown suffix, Coppersmith is the correct fallback after exact cube root fails.
  • Keep public-value recovery resumable because oracle latency can dominate the solve.

Dead Ends

  • Direct cube root of the ciphertext was not exact.
  • Parallel oracle search did not improve wall-clock time from the local network path; the service appeared serialized or throttled.
  • Older Pwnbox SSH endpoints from prior work were not reachable, so public-value recovery had to run locally.

Tool Quirks

  • sympy LLL overflowed on 2048-bit synthetic Coppersmith lattices.
  • fpylll worked and was installed under the challenge-local .deps/ directory.
  • The modulus oracle required around two thousand comparisons from the ciphertext lower bound.

Evidence Paths

  • files/extracted/crypto_yalm/server.py
  • analysis/source-audit.md
  • analysis/ciphertext-cuberoot-probe.txt
  • analysis/recovered-public-values.json
  • analysis/coppersmith-live-run.txt
  • solve/recover_public.py
  • 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 hidden RSA modulus N with the option-2 less-than-N oracle, then decrypt the option-1 e=3 ciphertext by integer cube root if m^3 < N.server.py test_encryption loops over base-N limbs but returns no ciphertext; len(cs)>1 only when submitted integer m >= N. get_secret uses e=3 and no padding.Build a local simulator and a remote probe that classifies small and huge plaintext values, then binary search N and test exact integer cube root of the secret ciphertext.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

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