Try โ€‚โ€‰HackMD

easy pseudo random - zer0pts ctf 2021

challenge

The following code and the outputs was given. It seems that the flag is encrypted with random numbers. We can get the upper k bits of two consecutive values, named w0 and w1. We can get the flag when the prediction of pseudo random numbers can be achieved using these.

# task.sage from Crypto.Util.number import* from flag import flag nbits = 256 p = random_prime(1 << nbits) Fp = Zmod(p) P.<v> = PolynomialRing(Fp) b = randrange(p) d = 2 F = v^2 + b v0 = randrange(p) v1 = F(v0) k = ceil(nbits * (d / (d + 1))) w0 = (v0 >> (nbits - k)) w1 = (v1 >> (nbits - k)) # encrypt m = bytes_to_long(flag) v = v1 for i in range(5): v = F(v) m ^^= int(v) print(f"p = {p}") print(f"b = {b}") print(f"m = {m}") print(f"w0 = {w0}") print(f"w1 = {w1}")

solution

Let

F(v)=v2+b over
Fp
and
n
be the nbits. Then
v0
and
v1
are given by the following equations.
v0=2nโˆ’kw0+xv1=2nโˆ’kw1+y

Here
x
and
y
are unknown quantities. If they are found, we can expect the pseudo random sequences. Expanding
v1
, we get further
v1=F(v0)=(2nโˆ’kw0+x)2+bใ€€

Thus, we get the following equation
f(x,y)=2nโˆ’kw1+yโˆ’((2nโˆ’kw0+x)2+b)=y+c2x2+c1x+c0=0

where
ci (i=0,...,2)
are suitable constants (They are explicitly depend on know parameters).
For easy understanding, the equation is written
y+c2x2+c1x+c0โ‰ก0modp
as a congruence in integers.

Now,

k=โŒˆndd+1โŒ‰ where d=degโกf, and therefore we can believe that required roots are found by Coppersmith's method.

# solve.sage from Crypto.Util.number import * from small_roots import small_roots nbits = 256 with open("output.txt") as f: while True: buf = f.readline() if not buf: break exec(buf) Fp = Zmod(p) P.<v> = PolynomialRing(Fp) F = v^2 + b Pf.<x, y> = PolynomialRing(Fp) d = 2 k = ceil(nbits * (d / (d + 1))) delta = float(1 / (d + 1)) bounds = (floor(p^delta), floor(p^delta)) f = (y + w1 * pow(2, nbits - k)) - ((x + w0 * pow(2, nbits - k))^2 + b) # multivariate coppersmith res = small_roots(f, bounds) print(res[0]) x0 = res[0][0] x1 = res[0][1] v0 = x0 + w0 * pow(2, nbits - k) v1 = x1 + w1 * pow(2, nbits - k) print(v0, v1) v = v1 for i in range(5): v = F(v) m ^^= int(v) print(long_to_bytes(m))

Here is the flag zer0pts{is_blum_blum_shub_safe?}