Click to expand challenge code
| |
The other notorious challenge from this ctf that managed to stay unsolved for the entire competition. It’s a shame, the decrypted image is really nice, I wanted you to see it π
Analysis
Functionality
You’re given 2 images alongside the source code:
serjy_out.png: The plain image xored with a random keystream derived from two intertwinedLCGsserjy_corrupt.png: A small part of the plain image
The keystream is pseudorandomly generated using 2 LCGs Linear congruential generators (lets’s call them $L_1$ and $L_2$ for convenience) in the following manner:
- Both LCGs are initialized with a
48 bitrandom initial seed. - The output keystream bytes are exclusively derived from $L_1$
- Each output byte advances the $L_1$ state by $1$
- Every $16$ bytes, $L_2$ advances in state by $1$ and $L_1$’s state is “refreshed” as follows:
$$
state_1 = state_1 \oplus \left( state_2 \gg 40 \right)
$$
AKA: $state_1$ is set to $state_1$
XOR$state_2$’s most significant byte
This keeps on going until we encrypt the entire image
Known Plaintext Attack
We know that LCGs are a deprecated cryptographic primitive that have been proven broken time and time again, as with just a few outputs we’re capable of reconstructing the entire state of the generator (more about the technical details here)
To do so, you need enough to know some parts of the keystream otherwise you’ll be driving blindly. That is why I gave you serjy_corrupt.png, letting you perform some sort of known plaintext attack. Xoring it with the encrypted image will grant you some bytes of the keystream, but only some of them!
Truncated LCG Attack
the problem with the aforementioned method, is that you need the full states of the LCG generator (pretty similar to mt19937 for the jigsaw challenge)… or do you? *Vsauce theme *
Turns out that, even with a truncated output, you can recover the entire state of a congruential generator! We can use Z3 like the jigsaw task, but it’ll take a LOOOOT longer, so why don’t we use something else?
Notice the first word of the acronym LCG? Linear? Interesting… What can we use to model linear systems? THAT’s RIGHT! FUCKING ALGEBRAAAAAA
Since our LCGs are defined by the recurrence $x_{i+1} \equiv a \cdot x_i + c \pmod m$. We are given the truncated outputs $y_i$, which are the $s$ most significant bits of the $k$-bit state $x_i$. This means $x_i = y_i \cdot 2^{k-s} + \delta_i$, where $\delta_i$ is the unknown lower part, $0 \le \delta_i < 2^{k-s}$.
Substituting this into the LCG recurrence gives:
$$ y_{i+1} \cdot 2^{k-s} + \delta_{i+1} \equiv a \cdot (y_i \cdot 2^{k-s} + \delta_i) + c \pmod m $$Rearranging for the unknown $\delta_i$ terms:
$$ \delta_{i+1} - a \cdot \delta_i \equiv a \cdot y_i \cdot 2^{k-s} + c - y_{i+1} \cdot 2^{k-s} \pmod m $$Let $z_i = a \cdot y_i \cdot 2^{k-s} + c - y_{i+1} \cdot 2^{k-s}$. The $z_i$ values are known. We now have a system of linear congruences for the small unknown values $\delta_i$:
$$ \delta_1 - a \cdot \delta_0 \equiv z_0 \pmod m \\ \delta_2 - a \cdot \delta_1 \equiv z_1 \pmod m \\ \vdots \\ \delta_n - a \cdot \delta_{n-1} \equiv z\_{n-1} \pmod m $$Does this look familiar? if you said that it resembles the Hidden Number Problem then you’re a fucking nerd, ngl twinπ but you’re right!
We can solve this by constructing a lattice as follows:
$$ B = \begin{pmatrix} m & 0 & 0 & \cdots & 0 \\ a^1 & -1 & 0 & \cdots & 0 \\ a^2 & 0 & -1 & \cdots & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a^{n-1} & 0 & 0 & \cdots & -1 \end{pmatrix} $$with $a$ and $m$ being the parameters of our LCG. With this matrix in place, we call for $LLL$ to reduce the lattice, making the calculations more managable, then we can solve our system of equations (which is modeled as a lattice in this case) to recover the $\delta_i$ values, letting us recover the full state of the LCG in a pretty elegant way!
Dual LCG
You might have noticed that we only talked about a single truncated LCG, but we have two of them, and they’re intertwined! Guess what, we know $L_1$ is partially derived from $L_2$, meaning that, if we recovered enough concrete states of $L_1$ we’re gonna get enough MSBs of $L_2$ to, you guessed it, perform ANOTHER Truncated LCG attack!
This might sound a bit convoluted, but think of it like how the earth spins $365$ times around itself, and once around the sun in a single year; now think of the earth as $L_1$ and the sun as $L_2$.
Sounds good xd? Hopefully the script clears things up
Solution
Plan
- Recover the $L_1$ keysteam bytes with the “known plaintext attack”
- Reconstruct each $L_1$ state
- Derive the $L_2$ state MSBs associated with each $L_1$
- Reconstruct the $L_2$ state
- Use the same keystream derivation backwards to reconstruct the entire keystream
XORthe encrypted image with the keystream
Note: You might notice that sometimes when you decrypt, the output will still look gibberish. That’s why you should always debug your code step by step to get a clear idea of what’s going on.
I purposely left the debugging logs in the solver so you can understand it aswell!
Solver
Click to expand solver code
| |
Here’s the decrypted picture if you’re wondering!

