La revanche de Sauron - FCSC 2024

󰃭 2025-08-12

At quick glance

In this challenge we have a pretty single encryption scheme and very few relations to work with, smells like lattice to me…

Analysis

There are only two blocks so let’s put it into a system:

$$b_1 \texttt{iv}_1 + k_1 s = c_1$$

$$b_2 \texttt{iv}_2 + k_2 s = c_2$$

Here lattice will surely work because of the imbalance in term of coefficient sizes :

  • $b_1, b_2$ are 256 bits
  • $s$ is 1024 bits
  • $\texttt{iv}_1, \texttt{iv}_2$ are 1024 as well
  • $k_1, k_2$ are 1024 bits

So the blocks are way smaller, let’s build a null combinaison and encourage LLL/BKZ to go towards it with scalling:

$$k_2 b_1 \texttt{iv}_1 + k_1 k_2 s = k_2 c_1$$

$$k_1 b_2 \texttt{iv}_2 + k_1 k_2 s = k_1 c_2$$

So

$$k_2 b_1 \texttt{iv}_1 - k_1 b_2 \texttt{iv}_2 - k_2 c_1 + k_1 c_2 = 0$$

The reason we want to eliminate $s$ is is because we want linear combinaisons of the unknowns and $k_1 s$ or $k_2 s$ prevent that.

LLL BKZ time

So from the equation above the coefficients we want in front of $c_1$ and $c_2$ should be 256 bits smaller than the ones in from of $\texttt{iv}_1$ and $\texttt{iv}_2$, so with the right scalling (scalling heavily on the null equation ofc) we can use the lattice :

$$ \begin{bmatrix} 2^{1024}c_1 & 2^{1024}c_2 & 2^{1024}\texttt{iv}_1 & 2^{1024}\texttt{iv}_2 \\ 2^{256} & 0 & 0 & 0 \\ 0 & 2^{256} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

The code

That is pretty much it (we also have to deal with the sign but that’s no big deal)

from pwn import remote, process
from Crypto.Util.number import *
from sage.all import *
from gmo.all import *
import json

f = json.loads(open("out.txt", "r").read())
bs = 1024 // 32

iv1 = f[0]["iv"]
c1 = f[0]["c"]

iv2 = f[1]["iv"]
c2 = f[1]["c"]

lattice = [[c1, c2, iv1, iv2],
           [1,  0,  0,   0],
           [0,  1,  0,   0],
           [0,  0,  1,   0],
           [0,  0,  0,   1]]


lat = matrix(ZZ, lattice).transpose()
scale = diagonal_matrix([2**1024, 2**256, 2**256, 1, 1])

r = (lat * scale).BKZ() / scale
flag = b""
for v in r:
    if v[0] == 0:
        print(v)
        k2, k1, k2b1, k1b2 = list(map(int, v[1:]))
        if k2b1 % k2 == 0 and k1b2 % k1 == 0:
            b2 = k1b2 // k1
            b1 = k2b1 // k2
            if b1 < 0:
                b1 *= -1
                b2 *= -1
            print(int.to_bytes(b1, bs,"big") + int.to_bytes(b2, bs,"big"))

And the flag: FCSC{8fd540e4620d3b873be4dcc074e3fb84f528a5800ffbb31dd158a8b7d5}