# 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.
``` python=
# 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) = v^2 + b$ over $\mathbb{F}_p$ and $n$ be the nbits. Then $v_0$ and $v_1$ are given by the following equations.
$$\begin{eqnarray} v_0 &=& 2^{n-k} w_0 + x \\ v_1 &=& 2^{n-k} w_1 + y \end{eqnarray}$$
Here $x$ and $y$ are unknown quantities. If they are found, we can expect the pseudo random sequences. Expanding $v_1$, we get further
$$\begin{eqnarray} v_1 &=& F(v_0) \\ &=& (2^{n-k}w_0 + x)^2 + b \end{eqnarray}$$
Thus, we get the following equation
$$\begin{eqnarray} f(x, y) &=& 2^{n-k}w_1 + y - ((2^{n-k}w_0 + x)^2 + b) \\ &=& y + c_2x^2 + c_1x + c_0 \\ &=& 0\end{eqnarray}$$
where $c_i \ (i=0,...,2)$ are suitable constants (They are explicitly depend on know parameters).
For easy understanding, the equation is written $y + c_2x^2 + c_1x + c_0 \equiv 0 \mod p$ as a congruence in integers.
Now, $k = \lceil \frac{nd}{d + 1} \rceil \ where\ d = \deg f$, and therefore we can believe that required roots are found by Coppersmith's method.
```python=
# 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?}```