This is a writeup for a difficult crypto challenge from KalmarCTF 2026 called RBG+++. Basically, it is an advanced version of lance-hard? from last year’s KalmarCTF, so make sure you have read Neobeo’s writeup for lance-hard? before starting this one.
Description
RBG sometimes stands for Random Bit Generator. I didn’t really like a challenge that was required to guess Dual_EC_DRBG.
This is a revenge challenge of RBG from Daily Alpacahack.
chall.py
from Crypto.Util.number import *
PBITS, NDAT = 137, 137
with open("flag.txt", "rb") as f: m = int.from_bytes(f.read())
N = getPrime(PBITS) * getPrime(PBITS)e = getRandomRange(731, N)print(f"{N = }")
lcg = lambda s: (s * 3 + 1337) % N
for i in range(NDAT): print(pow(m, e:=lcg(e), N) + pow(m, e:=lcg(e), N))
# Internal audit e = getRandomRange(731, N) print(f"[DEBUG] {e = }")Analysis
The challenge first gives us a modulus which is a product of two 137-bit primes. After that, it generates 136 pairs of , where and .
It is obvious that the modulus is not large enough, and we can easily factor it to get the two primes. The main problem is that we can’t directly find the roots of even if we know the factorization of because is too large.
Let’s can first try to simplify the equation. Since is almost the triple of , we can set . If , then we get
Although is still unknown, it is a constant across all pairs. If we can find another relation between these equations, we can cancel out some variables and get a “low” degree univariate equation.
But we don’t want to be the coefficient of because it is annoying to handle a non-monic polynomial. So Let’s first do some arrangements. Let , we have
which is equivalent to
Now we can set and , then it becomes . We can solve the equations seperately on and to reduce the degree complexity.
Similar to lance-hard?, the first step is to find small coefficients relations between ‘s.
Finding small relations
Since , we can use LLL to find with small ‘s. Thus we get
In addition, we can also add to the LLL because is also a power of . Then the equation becomes
We can heuristically guess the degree of the final polynomial is proportional to . After some experiments, the best parameters are and there’s exactly 4 positive ‘s and 4 negative ‘s, which balances the degree on both sides.
Reduce to univariate polynomial
Now we have 8 equations of and one equation of the products of and .
A straightforward way is to use resultant to eliminate and get a univariate polynomial in . However, the computation of resultant is very expensive, and the degree of final polynomial will explode very fast. By our previous heuristic guess, the degree will be about , which is impossible to compute. So we need to be more careful about how to eliminate variables.
Remember that we have 4 positive ‘s and 4 negative ‘s, we can rewrite the product relation as
For each part, we first reduce it into a bivariate polynomial of and , then use a resultant to eliminate and get the final result.
Algebraic Number Tricks
As we already know that , we can think of it as an “algebraic number over ”1
A famous result about algebraic numbers is that the sum, product of algebraic numbers is still an algebraic number. Similarly, we can do the same thing in our case, which means is also an algebraic number over .
Prop. For a degree algebraic number over , is also a degree algebraic number.
Let be the companion matrix of , then is the eigenvalue of . Thus is the eigenvalue of , whose characteristic polynomial is degree and has as a root.
Prop. For two algebraic numbers and over , is also an algebraic number with degree .
Let the roots of be and the roots of be .2 Then the polynomial has as a root and the degree is .
Note that the coefficients of are still in the field because they are symmetric polynomials of ‘s and ‘s, thus it can be computed by the elementary symmetric polynomials, which is exactly the coefficients of and .
Now I’ll introduce an efficient way to compute the coefficients of .
Def. The power sums are defined as and .
Clearly,
On the other hand, we can quickly convert elementary symmetric polynomials from and to power sums using Newton’s identities. Therefore, we can compute the coefficients of in time. Since we only consider , this is much more efficient than any other fancy methods.
Exact degree prediction
Now we have a way to compute the polynomial of without computing any resultant. The final polynomial of and is a degree 81 polynomial for with thousands degree for , which is still impossible to compute resultant.
Again, we can use the trick in lance-hard? to map to by substituting with some random value, and interpolate later.
Notice that all of these “algebraic numbers tricks” will not break the coefficients of polynomial, i.e. the coefficients are still polynomials of . Thus, the only thing we need to do is to approximate the degree of the final polynomial.
Since the analysis is very hard, I just did some experiments to find the pattern of degree growth. The result shows that the degree of the final polynomial is about . Now we can use it to choose the best LLL candidates and predict the degree of final polynomial, which is about .
Solve the polynomial
Fast Lagrange interpolation
Since we can free choose the evaluation points, we can use consecutive xs for interpolation. We can easily compute it in time.3
Finding roots of univariate polynomial
In general, the best method to find roots is first compute and then factor the result. However, the degree of our polynomial is about , which is too large for NTL to run FFT, so it failed.
Thus we have to compute two final polynomials and run gcd on them. Thanks to NTL and half-gcd and FFT(?), this time it doesn’t raise error and finished in about 20 minutes.
After getting the gcd, I find that it contains a large factor of , so it could be faster if we remove the factors of before computing gcd and interpolation.
The rest is simple as we have got .
Conclusion
My final solution is written in sage mixed with pyx4. The overall running time is
- LLL: single thread, ~1 hour. This is not optimized at all.
- Compute many ‘s: multi-thread, ~2 hours per polynomial. We need 4 polynomials in total.
- Interpolation: single thread, ~50 minutes per polynomial.
- GCD: single thread, ~20 minutes.
So the total running time is about 10 hours, which is quite long but still acceptable for a hard crypto challenge. It is still possible to optimize the code further, but I will leave it for future work.
Footnotes
Please be aware that the real definition of algebraic number is the root of a non-zero polynomial with coefficients over integers. But here we use it in finite field and the coefficients are polynomials of . ↩
No need to worry if the roots exist in the field, these are just symbols over some unknown extension field. ↩
Thanks Neobeo for the implementation in lance-hard?, and check grhkm’s blog for more details about the algorithm. ↩
I was surprised that sage supports
load("file.pyx"), which will automatically compile the pyx file and load it. ↩

