YALM
YALM is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
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.
Walkthrough flow
Source audit showed textbook RSA with e = 3, hidden...
The tester endpoint leaked a less-than oracle for N:...
Recover the exact modulus by binary-searching that...
Direct integer cube root failed because the...
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.
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:
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:
m = known_prefix * 256^flag_length + xand 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:
python3 Crypto/YALM/solve/recover_public.py \
--host <TARGET> \
--port 31578 \
--output Crypto/YALM/analysis/recovered-public-values.jsonThis recovers the public ciphertext and exact hidden modulus.
python3 Crypto/YALM/solve/solve.py \
--workspace <local workspace> \
--host <TARGET> \
--port 31578solve.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 = 3textbook 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c7341-ab2d-42a2-a0c4-540b0a4f2a03.zip | 897 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
files/extracted/crypto_yalm/server.py | 1134 | <hash redacted> | Python script text executable, ASCII text |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-13T00:45:28Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-13T00:45:28Z | artifact inventory | analysis/artifact-inventory.json | 2 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-13T00:46:06Z | hypothesis recorded | hypothesis-board.md | Recover 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. | Medium | 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. |
| 2026-06-13T00:46:06Z | checkpoint recorded | analysis/checkpoint-triage-20260613T004606500017Z-a1bf4374.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-13T00:46:36Z | RAG query | analysis/rag/rag-query-20260613T004615008249Z-034a9743.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-13T00:46:46Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-13T00:47:15Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-13T00:47:15Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-13T00:47:15Z | instrumentation plan | analysis/instrumentation-plan.md | Recover hidden RSA modulus N from the live service and decrypt the option-1 secret. | High | If 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:08Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-13T00:49:08Z | evaluator | analysis/evaluator-20260613T004908553200Z-b9330951.md | Proceed | High | Run solver against <TARGET>:31578 and capture loot/flag-candidate.txt if exact cube root and boundary validation pass. |
| 2026-06-13T01:38:50Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-13T01:40:17Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
files/extracted/crypto_yalm/server.pyuses textbook RSA withe = 3and a hidden modulusN.- Option 1 returns the secret ciphertext
c = m^3 mod Nfor a known-prefix message containing the flag. - Option 2 leaks a comparison oracle:
Thanks for the message!means the submitted integer is belowN, whileToo many messages!means it is at leastN. - The planned solve is to recover
Nwith doubling plus binary search, validate the boundary against the live oracle, then try exact integer cube-root recovery of the unpaddede=3ciphertext. - 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/; publicNand ciphertext are stored inanalysis/recovered-public-values.json.
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: 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.
- Source audit showed textbook RSA with
e = 3, hiddenN, and a known-prefix secret message. - The tester endpoint leaked a less-than oracle for
N: one response for submitted integers belowN, another response for integers at leastN. - Recover the exact modulus by binary-searching that oracle, using the ciphertext as a lower bound.
- Direct integer cube root failed because the ciphertext was reduced modulo
N. - Use known-prefix Coppersmith/LLL over
(prefix * 256^L + x)^3 - c mod Nand scan plausible flag lengths. - 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
Neven 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
sympyLLL overflowed on 2048-bit synthetic Coppersmith lattices.fpylllworked 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.pyanalysis/source-audit.mdanalysis/ciphertext-cuberoot-probe.txtanalysis/recovered-public-values.jsonanalysis/coppersmith-live-run.txtsolve/recover_public.pysolve/solve.pyloot/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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Recover 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. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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.