CTF/LINE CTF 2021

[Clear] LINE CTF 2021 babycrypto2 Writeup

Vardy 2021. 3. 22. 20:37

주어진 파일을 분석해보자 .

#!/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()

AES_KEY = get_random_bytes(AES.block_size)
TOKEN = b64encode(get_random_bytes(AES.block_size*10-1))
COMMAND = [b'test',b'show']
PREFIX = b'Command: '

def run_server(client):
    client.send(b'test Command: ' + AESCipher(AES_KEY).encrypt(PREFIX+COMMAND[0]+TOKEN) + b'\n')
    print(len(PREFIX+COMMAND[0]))
    print(len(TOKEN))
    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 == PREFIX+COMMAND[1]+TOKEN:
            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', 16002))
    server.listen(1)

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

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

babycrypto1과 달라진 점은, 

평문의 뒷부분이 아닌 앞부분이 다를 때의 암호문을 구해야 한다는 점이다.

 

즉 주어진 test Command 의 첫 번째 블록은 [Command: test???] 의 암호문일 것이다.

(1블록의 길이는 16이므로 뒤에 3바이트는 token의 앞부분일 것이다.)

우리는 첫 번째 블록이 [Command: show???] 일때의 암호문을 구해야 한다.

 

다시 CBC 암호화 방식을 보자. 첫 번째 암호 블록은

임의의 IV XOR Plaintext의 첫 블록 연산 후 암호화가 진행된다.

 

우선 test command를 그대로 전송해 ???값을 알아 낼 수 있다.

 

from pwn import *
import base64

p=remote("35.200.39.68", 16002)

p.recvuntil("test Command: ")
test = p.recvuntil(b"\nEnter your command: ", drop=True)
print(test)
#print(test[-32:-16])
iv = list(base64.b64decode(test)[:16])
chiper = base64.b64decode(test)[16:]

for i in range(16):
  iv[i] = 0

iv=bytes(iv)

medium = base64.b64encode(iv+chiper)
#print(final)
p.send(test)
p.recvuntil("test")
temp_3byte = p.recv(3)
print(b"temp_3byte: " + temp_3byte)

p.recvuntil(b"Enter your command: ")
p.send(medium)
temp_16byte=p.recv(16)

target = b"Command: show"+temp_3byte
print(target)

last_iv=list(target[i]^temp_16byte[i] for i in range(16))
print(last_iv)

final = base64.b64encode(bytes(last_iv)+chiper)

p.recvuntil("Enter your command: ")
p.send(final)
p.interactive()

 

또한, 암/복호화 과정에서 첫 번째 블록은 IV와 XOR 연산이 이루어지기 때문에 IV를 0*16으로 조작 한 값에 원하는 평문을 XOR시켜 연산해주면 원하는 평문일 때의 암호문을 얻을 수 있다.

 

[iv] XOR [Cipher_aftet_aes] = [test] 

( [iv] XOR [Cipher_aftet_aes] XOR [test] )  XOR [test] XOR [show] 

[test] XOR [test] XOR [show] = [show]

 

< exploit code >

from pwn import *
import base64

p=remote("35.200.39.68", 16002)

p.recvuntil("test Command: ")
test = p.recvuntil(b"\nEnter your command: ", drop=True)
print(test)
#print(test[-32:-16])
iv = list(base64.b64decode(test)[:16])
chiper = base64.b64decode(test)[16:]

for i in range(16):
  iv[i] = 0

iv=bytes(iv)

medium = base64.b64encode(iv+chiper)
#print(final)
p.send(test)
p.recvuntil("test")
temp_3byte = p.recv(3)
print(b"temp_3byte: " + temp_3byte)

p.recvuntil(b"Enter your command: ")
p.send(medium)
temp_16byte=p.recv(16)
#print(b"temp_16byte: "t + temp_16byte)

target = b"Command: show"+temp_3byte
print(target)

last_iv=list(target[i]^temp_16byte[i] for i in range(16))
print(last_iv)

final = base64.b64encode(bytes(last_iv)+chiper)

p.recvuntil("Enter your command: ")
p.send(final)
p.interactive()

 

FLAG = LINECTF{echidna_kawaii_and_crypto_is_difficult}

반응형