Challenge
First, we see how the flag is encrypted
class MyCipher:
def __init__(self, s0, s1):
self.X = s0
self.Y = s1
self.mod = 0xFFFFFFFFFFFFFFFF
self.BLOCK_SIZE = 8
def encrypt(self, pt: bytes):
ct = b''
for i in range(0, len(pt), self.BLOCK_SIZE):
ct += long_to_bytes(self.X)
key = self.get_key_stream()
block = pt[i:i+self.BLOCK_SIZE]
ct += bytes([block[j] ^ key[j] for j in range(len(block))])
return ct
The ciphertext is basically the key xor'ed with the plaintext with some information about one of the parameters of the keystream generation
Let's see how the keystream is being generated
def get_key_stream(self):
s0 = self.X
s1 = self.Y
sum = (s0 + s1) & self.mod
s1 ^= s0
key = []
for _ in range(8):
key.append(sum & 0xFF)
sum >>= 8
self.X = (rotl(s0, 24) ^ s1 ^ (s1 << 16)) & self.mod
self.Y = rotl(s1, 37) & self.mod
return key
If we knew the initial state with which the keystream is initialized, then we can predict any output which comes from it
We know the initial s0 and final s0, so we have to find out the initial value of s1. The final value of s0 is
s0 = rotl(s0,24)^ s1 ^ (s1<<16) & self.mod
Since we know the value of the inital s0, therefore the value of rotl(s0,24) is known to us. We can use z3 to find the value of s1.
from z3 import *
t1=rotl(i0,24)
x0 = BitVec("x0",64)
x1 = BitVec("x1",64)
s = Solver()
s.add(i1==(t1^x1^(x1<<16))&0xFFFFFFFFFFFFFFFF )
s.check()
l1=s.model()
ac = i0^l1
cipher = MyCipher(i0, ac)
for i in b2:
p1+=cipher.encrypt(i)
p1
#FLAG{x013_ro74te_5hif7!!}