ECTF 2016: Counterattack (Crypto, 100 pts)

This dude thinks he's hot at crypto, but he's really not. Don't you SEA[::-1]?!
Hosted at:


The source code reveals a very strange way of using AES encryption. They attempted to use CTR mode, but they only allowed for 16 different blocks to be encrypted and then XOr'd with the plaintext.

def encrypt(self, plain, IV, key):  
    self.count += 1
    CTR = bin((self.count) % 16)[2:].zfill(4)
    feed = IV + CTR
    cipher =, AES.MODE_ECB)
    keystream = cipher.encrypt(feed)
    return self.xor(keystream, plain)

The value for CTR can only be between 0000 and 1111 in binary. This means that cipher only has 16 different values. This means that when the program runs the encrypt code:

blocks = self.makeBlocks(data)  
for block in blocks:  
    outputBuffer.append(self.encrypt(data, IV, key))

The value for cipher will loop around if we input 16 blocks of data. I ran this code to retrieve the encryption for the flag and 16 blocks of \x00 bytes:

for i in range(16):  
    print "Sent " + str(i)

print s.recv(100000)  

I saved the result to a list and ran this code:

printMe = ""

for i in range(16):  
    printMe += chr(ord(result[0][i]) ^ ord(result[16][i]))

for i in range(16):  
    printMe += chr(ord(result[1][i]) ^ ord(result[17][i]))

for i in range(16):  
    printMe += chr(ord(result[2][i]) ^ ord(result[18][i]))

print printMe  

Since the flag was only 3 blocks long, by XOr'ing the first, second, and third blocks with the 17th, 18th, and 19th blocks, you XOr out the keystream variable resulting in this: The flag is ECTF{i_cant_even_ctr_that_stupidity}