HKCert 2025 crypto/Bivariate copper Writeup
0x00 Attachment
chall.py
|
|
0x01 Basic Information
From chall.py (as implied by your parameters), the server generates:
-
A large RSA modulus:
N = p*q -
qis small (25 bits) (this is crucial) -
A prime
pis large (≈ 1024 bits) -
Some secret “flag integer”
m -
Random offsets
r1, r2(known) -
A secret multiplier
k(known) -
Two “leaks”
leak1, leak2derived from valuest1, t2:t1 = k * inverse(m + r1, p) mod pt2 = k * inverse(m + r2, p) mod pleak1 = t1 >> 244leak2 = t2 >> 244
So you do not see full t1, t2; you only see their high bits.
That missing part becomes our “small unknown”.
0x02 Factor RSA and decrypt the built-in hint
Because q is only 25 bits, factoring N is trivial by trial division / small prime search.
Once you factor:
p = N // q- compute
φ(N) = (p-1)(q-1) d = e^{-1} mod φ(N)- decrypt:
msg = c^d mod N
You already confirmed the plaintext is:
Hurry up and go smelt copper!
That’s a direct hint: use Coppersmith (lattice small root).
0x03 Convert leaks into “known high bits + small unknown”
You know:
leak1 = t1 >> 244leak2 = t2 >> 244
Let shift = 244 and define:
a1 = leak1 * 2^shifta2 = leak2 * 2^shift
Then write the unknown low bits as:
t1 = a1 + xt2 = a2 + y
where the bounds are:
0 ≤ x < 2^shift0 ≤ y < 2^shift
So x and y are small compared to p (since p ~ 2^1024 and 2^244 is much smaller).
This is exactly the “small root” situation.
0x04 Eliminate the flag m and obtain a bivariate modular equation
We start from the definitions:
$$ [ t_1 \equiv k(m+r_1)^{-1} \pmod p ] [ t_2 \equiv k(m+r_2)^{-1} \pmod p ] $$Rearrange both:
$$ [ (m+r_1)t_1 \equiv k \pmod p ] [ (m+r_2)t_2 \equiv k \pmod p ] $$
Now eliminate m. From the first:
Plug into the second:
$$ [ (kt_1^{-1} - r_1 + r_2)t_2 \equiv k \pmod p ] $$
Multiply by t1:
Bring all to one side:
$$ [ (r_2-r_1)t_1t_2 + k(t_2 - t_1) \equiv 0 \pmod p ] $$Now substitute t1 = a1 + x, t2 = a2 + y:
This is a bivariate polynomial congruence with a small root (x,y).
0x05 Simplify the polynomial structure (bilinear form)
Expand F(x,y) and collect terms in (x,y):
It becomes:
$$ [ f(x,y) = Axy + B_x x + B_y y + C \equiv 0 \pmod p ] $$Where:
- $$ (A = (r_2-r_1)) $$
- $$ (B_x = A a_2 - k) $$
- $$ (B_y = A a_1 + k) $$
- $$ (C = A a_1 a_2 + k(a_2-a_1)) $$
(all taken mod p)
So it’s bilinear (degree 2 but only via x*y), which is friendly for lattice methods.
0x06 Bivariate Coppersmith idea (high-level)
We want to find small integers (x,y) such that:
f(x,y) ≡ 0 (mod p)|x| < X = 2^244|y| < Y = 2^244
Coppersmith-style approach:
-
Build many polynomials that vanish at the root modulo a high power of
p, e.g. terms like:- $$ (x^i y^j f(x,y)^k p^{m-k}) $$
-
Scale monomials by
X^a Y^bso evaluation at the root stays small. -
Put coefficient vectors into a lattice.
-
Apply LLL to get short vectors → polynomials that are actually zero over integers at the root (not just mod p).
-
Use elimination (e.g. resultant) to solve for
xandy.
This is often attributed to variants of Howgrave-Graham criteria and Jochemsz–May constructions for multivariate cases.
0x07 Solve for the flag once (x,y) is found
After recovering x:
t1 = a1 + x- from $$((m+r_1)t_1 \equiv k \pmod p)$$: $$ [ m \equiv k t_1^{-1} - r_1 \pmod p ] $$
Since the flag integer is small (a typical CTF string), the residue corresponds directly to the plaintext bytes.
0x08 Reference implementation (Sage)
This is a complete “drop-in” solver pattern:
- Factors
N - Builds bilinear
f(x,y) - Constructs a lattice (Jochemsz–May style)
- Uses LLL
- Extracts candidate polynomials and eliminates via resultants
- Verifies
(x,y)by checkingf(x,y) ≡ 0 (mod p) - Recovers
mand prints bytes
Run with:
sage -python coppersolve.py
|
|
|
|