ECB Xoracle
Last updated
Last updated
Easy
150
I implemented an additional step in my encryption/decryption service and declare its name as "Xoracle". It should be more secure, right?
34.126.175.135:8009
Notice that each character of the flag XORs every 16 bytes (1 block) of your input before the encryption.
Since the algorithm uses ECB, and we know the first few characters of the flag, we can bruteforce the characters of the flag one by one.
We know the character is correct when the encrypted bytes in a block are the same as the encrypted bytes in the previous block.
Following the path of the flag
, we can see the flow as follows:
len(flag) == 16
result = e.encrypt(data,flag)
ct_1 = self.xor_ecb(data,flag)
blocks = [data[i:i+16] for i in range(0,len(data),16)]
for block,val in zip(blocks,cycle(list(key))):
result += xor(block,chr(val).encode('utf-8'))
def xor(msg:bytes,key:bytes)->bytes:
return ''.join([chr(a^b) for a,b in zip(msg,cycle(key))]).encode('utf-8')
Focusing on the 4th point, we see that each character of the flag is XORed with each block of the input data, where each block is 16 bytes.
Now focusing on the 5th point, we see that each character is provided to a cycle
function, which basically copies that value until it is the same length as as each block (zip(msg,cycle(key))
).
Since we know the flag format is NYP{...}, meaning if we input 'A'*16 + 'B'*16 + 'C'*16, this would be the following result of the XOR:
AAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCC
XOR
XOR
XOR
NNNNNNNNNNNNNNNN
YYYYYYYYYYYYYYYY
PPPPPPPPPPPPPPPP
ECB Encrypt Block
ECB Encrypt Block
ECB Encrypt Block
CT_BLOCK_1
CT_BLOCK_2
CT_BLOCK_3
Now, we can utilize a feature of XOR, where if you XOR a character by itself, it would return 0 (e.g. N^N = 0).
Since xor(('N'*16),('N'*16)) == 0, and xor(('Y'*16),('Y'*16)) == 0, if ECB encryption is performed on both blocks of 0, we would get the same Ciphertext output since it is using ECB Encryption algorithm.
ECB Encryption:
So we can bruteforce each character of the flag in sizes of 16 bytes, and check if that 16 byte CT block output is equal to the one where 0 is encrypted.
For those who are confused, here is an example:
Let's say if we input 'N'*16 for the first block and got the output CT as 'ABCDEFGHIJKLMNOP', then if we give 'N'*16 + 'Y'*16 as the input to encrypt, it should return us 'ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP' (notice first and last 16 bytes are the same).
And so if we provide 'N'*16+'Y'*16+'P'*16+'{'*16+'A'*16, if the CT output is 'ABCDEFGHIJKLMNOP...ABCDEFGHIJKLMNOP', where the last 16 bytes are equal to the first 16 bytes of CT, then we know that the character 'A' is correct and our flag would be NYP{A...}.