PicoCTF-18 (SpyFi, 300 pt)
This challenge encrypts a secret with AES-ECB, and that choice alone is the whole vulnerability. ECB encrypts each 16-byte block independently, so identical plaintext blocks always produce identical ciphertext blocks. If you can control part of the plaintext and the secret sits in a predictable spot, you can recover it one byte at a time without ever touching the key.
If ECB is new to you, read this first: AES-ECB
AES-ECB encryption and decryption flow
![]()
![]()
The challenge source
#!/usr/bin/python2 -u
from Crypto.Cipher import AES
agent_code = """flag"""
def pad(message):
if len(message) % 16 != 0:
message = message + '0'*(16 - len(message)%16 ) #block-size = 16
return message
def encrypt(key, plain):
cipher = AES.new( key.decode('hex'), AES.MODE_ECB )
return cipher.encrypt(plain).encode('hex')
welcome = "Welcome, Agent 006!"
print welcome
sitrep = raw_input("Please enter your situation report: ")
message = '''Agent,
Greetings. My situation report is as follows:
{0} #here is our input message
My agent identifying code is: {1}. #flag
Down with the Soviets,
006'''.format(sitrep,agent_code)
message = pad(message)
print encrypt( """key""", message )
Our input gets dropped into a fixed template, and the flag is appended after it. The idea is to pad our input so the flag lands on a known block boundary, then brute-force each byte. Let’s start with this input:
AAAAAAAAAAA + BBBBBBBBBBBBBBBB + CCCCCCCCCCCCCCCC <-- 'A'x11 (offset) + 'B'x16 + 'C'x16
Assume the flag is picoCTF{ABCDEFG} for illustration.
Split the message into 16-byte blocks
'Agent,\nGreetings' <--- 16 (Block 1)
'. My situation r' <--- 16 (Block 2)
'eport is as foll' <--- 16 (Block 3)
'ows:\nAAAAAAAAAAA' <--- 16 (Block 4)
'BBBBBBBBBBBBBBBB' <--- 16 (Block 5)
'CCCCCCCCCCCCCCCC' <--- 16 (Block 6)
'\nMy agent identi' <--- 16 (Block 7)
'fying code is: ' <--- 16 (Block 8) <---known Block
'picoCTF{ABCDEFG}' <--- 16 (Block 9) <---unknown Block
'.Down with the S' <--- 16 (Block 10) <---known Block
Now drop one ‘C’ from the input. Everything after it shifts left by one byte, pulling the first flag byte into a block we control the alignment of:
'Agent,\nGreetings' <--- 16 (Block 1)
'. My situation r' <--- 16 (Block 2)
'eport is as foll' <--- 16 (Block 3)
'ows:\nAAAAAAAAAAA' <--- 16 (Block 4)
'BBBBBBBBBBBBBBBB' <--- 16 (Block 5)
'CCCCCCCCCCCCCCC\n' <--- 16 (Block 6)
'My agent identif' <--- 16 (Block 7)
'ying code is: p' <--- 16 (Block 8) <---here is our one byte flag but we dont know what char is it.
'icoCTF{ABCDEFG}.' <--- 16 (Block 9) <---unknown block
'Down with the So' <--- 16 (Block 10)<---known Block
Block 8 now ends with the first byte of the flag, but we don’t yet know what that byte is. To find it, we plant a guess block: put 'ying code is: ' followed by a candidate character into an earlier block, and cycle that character through the printable range.
'Agent,\nGreetings' <--- 16 (Block 1)
'. My situation r' <--- 16 (Block 2)
'eport is as foll' <--- 16 (Block 3)
'ows:\nAAAAAAAAAAA' <--- 16 (Block 4)
'ying code is: %s' <--- 16 (Block 5) <--- replace %s to char in range(32,128)
'CCCCCCCCCCCCCCC\n' <--- 16 (Block 6)
'My agent identif' <--- 16 (Block 7)
'ying code is: p' <--- 16 (Block 8) <--- Now we know block 8 has our one byte flag
'icoCTF{ABCDEFG}.' <--- 16 (Block 9) <--- unknown block
'Down with the So' <--- 16 (Block 10) <---known Block
Compare the ciphertext of block 5 (our guess) against block 8 (the real flag byte). When they match, the character we guessed is the first byte of the flag. No key required. Now repeat with one fewer ‘C’ to line up the next byte.
After the first loop we know the flag starts with: “p”
For the second byte, shift the alignment again and feed the known “p” plus a new guess:
'Agent,\nGreetings' <--- 16 (Block 1)
'. My situation r' <--- 16 (Block 2)
'eport is as foll' <--- 16 (Block 3)
'ows:\nAAAAAAAAAAA' <--- 16 (Block 4)
'ing code is: p%s' <--- 16 (Block 5) <--- replace %s to char in range(32,128)
'CCCCCCCCCCCCCC\nM' <--- 16 (Block 6)
'y agent identify' <--- 16 (Block 7)
'ing code is: pi' <--- 16 (Block 8) <--- Now we know block 8 has our two byte flag in which one we know i.e "p" and other we have to find out in second loop.
'coCTF{ABCDEFG}.D' <--- 16 (Block 9) <--- unknown block
'own with the Sov' <--- 16 (Block 10) <--- known Block
Same comparison: when block 5 and block 8 encrypt to the same value, the guessed character is the second byte of the flag.
After the second loop we know the flag starts with: “pi”
Keep sliding the window one byte at a time until the whole flag falls out.
My script for this challenge
#!/usr/bin/python2 -u
from pwn import *
flag = "picoCTF{"
for j in range(1,14):
print(".................................................",j)
p = remote('2018shell1.picoctf.com', 37131)
p.recvuntil('Please enter your situation report: ')
my_msg = "A"*11+"B"*(25-j)
p.sendline(my_msg)
enc_msg = p.recv(1024).decode('hex')
p.close()
for i in range(32,128):
q = remote('2018shell1.picoctf.com', 37131)
q.recvuntil('Please enter your situation report: ')
msg = "A"*11+"B"*(14-j) + flag + chr(i)
q.sendline(msg)
y = q.recv(1024).decode('hex')
q.close()
if y[80:96] == enc_msg[128:144]:
flag += chr(i)
break
print(flag)
#picoCTF{@g3nt6_1$_th3_c00l3$t_2432504}