Rating: 5.0

babycrypto1

Source code

#!/usr/bin/env python
from base64 import b64decode
from base64 import b64encode
import socket
import multiprocessing

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import hashlib
import sys

class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, data):
        iv = get_random_bytes(AES.block_size)
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def encrypt_iv(self, data, iv):
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def decrypt(self, data):
        raw = b64decode(data)
        self.cipher = AES.new(self.key, AES.MODE_CBC, raw[:AES.block_size])
        return unpad(self.cipher.decrypt(raw[AES.block_size:]), AES.block_size)

flag = open("flag", "rb").read().strip()

COMMAND = [b'test',b'show']

def run_server(client, aes_key, token):
    client.send(b'test Command: ' + AESCipher(aes_key).encrypt(token+COMMAND[0]) + b'\n')
    client.send(b'**Cipher oracle**\n')
    client.send(b'IV...: ')
    iv = b64decode(client.recv(1024).decode().strip())
    client.send(b'Message...: ')
    msg = b64decode(client.recv(1024).decode().strip())
    client.send(b'Ciphertext:' + AESCipher(aes_key).encrypt_iv(msg,iv) + b'\n\n')
    while(True):
        client.send(b'Enter your command: ')
        tt = client.recv(1024).strip()
        tt2 = AESCipher(aes_key).decrypt(tt)
        client.send(tt2 + b'\n')
        if tt2 == token+COMMAND[1]:
            client.send(b'The flag is: ' + flag)
            client.close()
            break

if __name__ == '__main__':
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', 16001))
    server.listen(1)

    while True:
        client, address = server.accept()

        aes_key = get_random_bytes(AES.block_size)
        token = b64encode(get_random_bytes(AES.block_size*10))[:AES.block_size*10]

        process = multiprocessing.Process(target=run_server, args=(client, aes_key, token))
        process.daemon = True
        process.start()

Solution

Reading the source code, we can see that the idea is to replace the last block of encryption with show instead of test. In addition, the server allows us to encrypt a string with a given IV.

AESDecryption

When you look at the following diagram of AES_CBC decryption, you see that the last block is basically decrypted and then XORed with the previous block. So if we encrypt the string show with the previous block as the IV we would get the correct final block. Then we just need to replace that in the string that we were given at the start from the server and we can go ahead and send that to the serevr.

Solution Code

from base64 import b64decode
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad

from pwn import *

import hashlib
import sys

r = remote("35.200.115.41", 16001, level='debug')

line = str(r.recvline().replace(b'\n', b'')).split(": ")
http://)
r.recv()

testcommand = line[1]

iv = b64encode(b64decode(testcommand)[:16])

token = b64encode(b64decode(testcommand)[16:AES.block_size*10+16])
tokenlast = b64decode(token)[-16:]
r.sendline(b64encode(tokenlast))

r.sendline(b64encode(b'show'))
line = str(r.recvline().replace(b'\n', b'')).split(":")

encryptedshow = line[2]

payload = b64encode(b64decode(iv)+b64decode(token)+b64decode(encryptedshow)[16:])
r.recvuntil(b'Enter your command: ')
r.sendline(testcommand)
r.recvuntil(b'Enter your command: ')
r.sendline(payload)
r.recv()
r.recv()

Rabbit Holes

  • One thing that I kept doing was XORing show with the IV and the lastblock that have it work and send the IV at the start of the string sent by the server as the encryption IV but this didn't work because show was XORed with the lastblock before encryption
  • I also tried padding show to make it have the right length but that didn't work because the data was additionally padded
Original writeup (https://susanou.github.io/Writeups/posts/LINECTF/).