This challenge was a standard Hidden Number Problem dressed up in fantasy cosplay. The objective was clear: uncover a hidden value $\alpha$ using Babai's CVP algorithm
Challenge Overview
The challenge spins up a remote service that hands out:
p: A 256-bit prime modulus.n: The bit length ofp, which is 256.k: The number of bits to be leaked, calculated asint(n**0.5) + n.bit_length() + 1, which comes out to 26.d: The maximum number of queries we can make,2 * int(n**0.5) + 3, giving us 35 attempts.
Then we have the entry point: Query MSB oracle. For each query, the server:
- Generates a random $t$.
- Computes $x = (\alpha * t)\mod p$, where $\alpha$ is the secret we’re after.
- Returns $t$ and a value $z$, which is an approximation of $x$.
- Finally, an AES-CBC–encrypted flag, with the key derived as $\text{SHA256}(\alpha)$.
All we have to do is to recover the secret $\alpha$ from the oracle leaks, derive the AES key, and decrypt the flag. Easy, right?
Attack Vector ;)
1. Collecting the Noisy Leaks
We scripted a quick loop to:
Send option
1repeatedly until we exhausted the max queries $d$.Retrieve $t_i$ and $z_i$ ans store them in arrays for later.
Tip: Whenever you’re given the source code of a challenge, ALWAYS run it locally before interacting with the remote. This lets you add logging and monitor exactly what’s going on so the challenge doesn’t feel as black-boxy
2. Modeling as a Hidden Number Problem
This is textbook Howgrave-Graham–Naehrig territory. We have:
$$ \alpha \cdot t_i \equiv z_i \pm \varepsilon_i \pmod p, $$where each error $\varepsilon_i$ is bounded by $p / 2^{k+1}$. The goal: recover $\alpha$ from these noisy modular equations.
For those of you who’re unfamiliar with HNP, it is an application of lattice reduction, where the goal is to construct a lattice and reduce it (via LLL or BKZ) so that one of the short vectors reveals a relatively small value that we’re looking for, in this case, it’s $\alpha$
3. Lattice Construction & Babai’s Closest Vector
Let’s build our lattice. For $d$ samples $(t_i, z_i)$, we can construct the following basis matrix $M$ of dimension $(d+1)\times(d+1)$:
$$ M=\begin{bmatrix} p & 0 & \cdots & 0 & 0 \\ 0 & p & \cdots & 0 & 0 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & p & 0 \\ t_1 & t_2 & \cdots & t_d & C \end{bmatrix} $$Concretely:
| |
Then we feed $M$ to LLL (with $\delta=0.75$, default value) and apply Babai’s nearest plane to find the shortest vector closest to $[z_1, \dots, z_d, 0]$. We let the algorithm do its magic, and we’ll find that the last coordinate of that vector (scaled back by $2^{k+1}$) yields $\alpha$.
4. Decrypting the Flag
With $\alpha$ in hand, derive the AES key:
| |
Then unwrap the CBC cipher to retrieve the flag:
| |
L3AK{hnp_BBB_cvp_4_the_w1n}
Full Solver Snippet
Click to expand solver code
| |
Ignore the timing analysis. I initially thought that we had to perform some kind of timing attack due to the number of iterations used to derive $z$
1 2 3 4 5time.sleep(0.05) for _ in range(1000): z = random.randrange(1, self.p) if abs(x - z) < threshold: return zTurns out it was a red herring
