def encrypt(m):
state = [int.from_bytes(m[i:i+4]) for i in range(0, len(m), 4)]
assert len(state) == N
def xor_shift():
nonlocal state
t = state[0] ^ ((state[0] << 11) & WORD_MASK)
for i in range(N-1):
state[i] = state[i+1]
state[-1] = (state[-1] ^ (state[-1] >> 19)) ^ (t ^ (t >> 8))
for _ in range(M):
xor_shift()
return state
I couldn't solve this during the CTF, but found the concept interesting.
Encryption
The xor_shift() takes an input state and shifts the state upwards by one and adds a new state. We can express the state shift in the form of matrices.
Let Un and Ln be the left and right shift matrices respectively such that
# copied from https://s3v3ru5.github.io/notes/TWCTFxorandshift
si << a = (Un**a) * tovec(si) # n = si.bit_length()
si >> a = (Ln**a) * tovec(si)
If we use GF(2) as our base ring, then we can define xor as addition.
ti ^ ri = tovec(ti) + tovec(ri)
ti << a = (Un ** a) * tovec(ti)
ti >> a = (Ln ** a) * tovec(ti)
We then compare this with the xor_shift function to check if it works
size=32
Un = gen_left_shift_mat(size)
Ln = gen_right_shift_mat(size)
I = Matrix.identity(GF(2),32)
N = Matrix(GF(2),32)
A = Un^11+I
Y = Ln^19+I
Z = Ln^8 + I
def gmm(dimension):
t1 = [N,N,A]
t1+=[N for i in range(dimension-3)]
block = [[N for _ in range(dimension)] for _ in range(dimension-2)]
for i in range(6):
block[i][2+i] = I
t2 = [Z]+[N for i in range(dimension-2)]+[Y]
block = [t1]+block+[t2]
return block_matrix(block)
b=gmm(8)
b1=b^5*ints_2_state(s1,32)
a1=xor_shift(state,5)
a2=state_2_ints(b1,32)
a1 ==a2[1:]
#True
A variant of this method was most likely used by the authors to encrypt
Decryption
We use the same idea, except we construct a different matrix to switch the input and output state. To make it easier, we multiply by another matrix first to make it easier to deal with the last element
def gmm3(dimension):
block = [[N for _ in range(dimension)] for _ in range(dimension-1)]
for i in range(6):
block[i][i] = I
t2 = [[N for i in range(dimension-2)] + [-Y,I]]
block+=t2
return block_matrix(block)
def gmm2(dimension):
block = [[N for _ in range(dimension)] for _ in range(dimension-1)]
for i in range(6):
block[i][i] = I
t2 = [[N for i in range(dimension-2)] + [N,A^-1*Z^-1]]
t2 = t2 + block
return block_matrix(t2)
Now that we have everything that we need, we can finally decrypt
p1 = gmm2(7)
h1 = gmm3(7)
b2= (p1*h1)^(17005450388330379)*ints_2_state(state,32)
x1=state_2_ints(b2,32)
t1=b''
for i in x1:
t1+= long_to_bytes(i)
print(t1)
#FLAG{m47r1x_!n_8inary_w0rld}
This challenge uses xor shifts to encrypt the flag. Before proceeding with the writeup, I highly recommend reading , as I use the code as well as the concepts explained there.