Click to expand challenge code
| |
Ahh yes, the infamous Jigsaw puzzle than many players spent hours bashing their head against xd. This is one of the two challenges I made that remained unsolved for the entire competition.
So as promised, here’s a writeup 😉
I also wanted to follow a musical theme across all challenges (this one about radiohead, the ZKP one about Green Day, Toxicity about System of a Down…), I hope you appreciated it 😁
Analysis
Functionality
Before trying anything, we gotta understand what we’re working with, aswell as our limitations:
- We have a centralized “dealer” (which you can think of as a single
prngstate) responsible for generating all of our random numbers - The first random outputs are used to generate the
AESkey and iv used to encrypt the flag - We get access to an oracle that “encrypts” our input using a random keystream provided by the
dealer - There is a limit on the length of our input (7200 bytes)
- The “encryption” is done an unconventional method, juggling
ANDandOR
Lossy Encryption
The encryption method is pretty unconventional, so let’s break it down in detail:
- After each input, a random
methodsinteger is generated - for each block of 4 bytes, the
ithbit ofmethodswill determine which operation we’re gonna perform between a random32 bit integerand the plaintext block- if the bit is
0, we perform anANDoperation - if it’s
1, we perform anOR
- if the bit is
- We concatenate the resulting ciphertext blocks and return them to the player
What you’ll notice it that we didn’t use a classic XOR operation, otherwise the challenge would be trivial!
Contrary to a XOR operation, & and | are not bijective! (aka, we lose information after the operation), meaning, given $x$ and $y$ with $x = y$ & $c$ or $x = y \vert c$ for some unknown $c$, it is mathematically impossible to pinpoint $c$!
Unless….
The Split
Here’s the neat thing, we know that $c$&$y$ gives us no information regarding $c$, but what if $y=1$? Suddenly, $x=1$&$c = c$!
Same thing goes for OR: $x=0 \vert c = c $
How could this be helpful though? Think about it for a second: We don’t know which operation is performed on each plaintext block, but think about what happens if we encrypt 0xffff0000!! Let’s consider the random number for each block $x_i$:
- If the operation is
AND: We get $$ \text{0x????0000} = \left( \left( x_i \gg 16 \right) \mathbin{\&} \text{0xFFFF} \right) \ll 16 $$ which essentially translates to The 16 Most Significant Bits of $x_i$, shifted to the left by 16 bits - If the operation is
OR: We get $$ \text{0xFFFF????} = \left(x_i\mathbin{\&} \text{0xFFFF} \right) \vert \text{0xFFFF0000} $$ AKA The 16 Least Significant Bits of $x_i$
This ultimately gives us half of each randomly generated number by the dealer, aswell as methods since we now know the exact order of the methods used!
Here’s the second challenge though: How do you recover a Mersenne Twister RNG state given partial information about the outputs?
Here, we call for symbolic modeling of the problem, aka Z3
Symbolic Solution
If you’re not familiar with symbolic solvers like z3 and or-tools, basically they’re what we call SAT solvers used to model a set of equations and try their best to find a solution.
There are plenty of mersenne solvers online, but most of them try algebraic solutions that require the full prng output in sequential order for them to work. We don’t have that privilege here so we resort to SAT solvers that help us recover the missing half of each entry.
Note: There are methods to solve this using linear algebra over $GF(2)$, but they are waaaay too complicated, and are outside the scope of this writeup.
There is this repo that has a great implementation for symbolic mersenne solver that works even with missing bits or even entirely skipped states!. It’s very heavy duty though, so it works best the more information you give it.
Luckily for us, we have plenty!
It should be noted that the symbolic solver will give us the state of the
rngat the end of its executon. We still need to walk back in states to reach the one used to generate the AES key!
Solution
Plan
- Get as many encrypted blocks as possible, $900$ in our case, but even $624$ would be enough, but will take a lot longer to solve.
- Recover the state at the end
- Walk back in states with a simulated
MTimplementation - Regenerate the key (use the
ivto verify that it’s correct), and decrypt the flag!
Solver
Click to expand solver code
| |
FL1TZ{atl3Ast_g1mme_th3_S33D_bef0R3_y0u_run_4Way_fr0M_m3:(}
