본문 바로가기

2025 - 2

1주차 워게임 [EZ-Des] 문제풀이

문제 링크 : https://dreamhack.io/wargame/challenges/2176

 

EZ-Des

DES 근데 이제 뭔가 많은

dreamhack.io

 

문제 설명

DES 근데 이제 뭔가 많은


 

write - up

 

문제에서는 keys.txt 와 flag.txt 라는 텍스트 파일을 준다. keys는 16진수 키 후보들 리스트 같아보이고, flag 는 DH{???} 형태를 가진 400바이트가 답이라는 힌트를 준다. 추가로 des.py 파일도 주는데, 다음과 같다.

from flask import Flask, send_file, make_response, render_template_string
from Crypto.Cipher import DES
import random
import time
import os

app = Flask(__name__)

FLAG_FILE = 'flag.txt'
KEY_FILE = 'keys.txt'
CIPHER_FILE = 'ciphertext.txt'
BLOCK_SIZE = 8
NUM_BLOCKS = 50


def read_flag(filename=FLAG_FILE, target_len=400):
    with open(filename, 'rb') as f:
        flag = f.read()
    if len(flag) < target_len:
        flag += b'A' * (target_len - len(flag))
    return flag[:target_len]


def read_keys(filename=KEY_FILE):
    with open(filename, 'r') as f:
        lines = f.readlines()
    keys = [bytes.fromhex(line.strip()) for line in lines if line.strip()]
    assert len(keys) == NUM_BLOCKS and all(len(k) == 8 for k in keys)
    return keys


def split_blocks(msg, block_size=BLOCK_SIZE):
    return [msg[i:i+block_size] for i in range(0, len(msg), block_size)]


def triple_des_ede(block, key):
    c1 = DES.new(key, DES.MODE_ECB).encrypt(block)
    c2 = DES.new(key, DES.MODE_ECB).decrypt(c1)
    c3 = DES.new(key, DES.MODE_ECB).encrypt(c2)
    return c3


def encrypt_flag(plaintext, keys, seed):
    idxs = list(range(NUM_BLOCKS))
    random.seed(seed)
    random.shuffle(idxs)
    blocks = split_blocks(plaintext, BLOCK_SIZE)
    ciphertext_blocks = []
    for i, block in enumerate(blocks):
        key = keys[idxs[i]]
        ct = triple_des_ede(block, key)
        ciphertext_blocks.append(ct)
    return b''.join(ciphertext_blocks)


@app.route('/')
def home():
    return render_template_string('''
        <h2>다운로드</h2>
        <form action="/download">
            <button type="submit">ciphertext.txt 다운로드</button>
        </form>
        <form action="/keys">
            <button type="submit">keys.txt 다운로드</button>
        </form>
    ''')


@app.route('/download')
def download_ciphertext():
    pt = read_flag()
    keys = read_keys()
    seed = int(time.time())
    ct = encrypt_flag(pt, keys, seed)
    tmpfile = 'ciphertext.txt'
    with open(tmpfile, 'wb') as f:
        f.write(ct)
    os.utime(tmpfile, (seed, seed))
    resp = make_response(send_file(tmpfile, as_attachment=True))
    resp.headers['X-Used-Seed'] = str(seed)
    return resp


@app.route('/keys')
def download_keys():
    return send_file(KEY_FILE, as_attachment=True)


if __name__ == '__main__':
    app.run(host="0.0.0.0")

 

추가로 문제 사이트에선 VM을 가동시킬 수 있었고, 가동시킨 VM의 서버주소로 들어가면 ciphertext.txt 파일을 받을 수 있었다. ciphertext 파일을 notepad로 열었을때 꺠져있는것으로보아 암호화된 flag일 것이라 생각했고, keys 파일이랑 ciphrttext파일 을 엮어서 복호화를 진행시켜야한다는 것까지는 이해를 했으나 코드를 구현할 능력이 없기에... GPT 선생님께 여쭤본 결과 다음과 같은 코드를 내주셨다.

# solve.py

import os
import random
from Crypto.Cipher import DES

BLOCK_SIZE = 8
NUM_BLOCKS = 50

def split_blocks(msg, block_size=BLOCK_SIZE):
    return [msg[i:i+block_size] for i in range(0, len(msg), block_size)]

def triple_des_ede_decrypt(block, key):
    c1 = DES.new(key, DES.MODE_ECB).decrypt(block)
    c2 = DES.new(key, DES.MODE_ECB).encrypt(c1)
    c3 = DES.new(key, DES.MODE_ECB).decrypt(c2)
    return c3

def try_seed(seed, ciphertext, keys):
    idxs = list(range(NUM_BLOCKS))
    random.seed(seed)
    random.shuffle(idxs)

    cipher_blocks = split_blocks(ciphertext)
    plain_blocks = [b""] * NUM_BLOCKS
    for i, block in enumerate(cipher_blocks):
        key = keys[idxs[i]]
        plain_blocks[i] = triple_des_ede_decrypt(block, key)

    plaintext = b"".join(plain_blocks)

    start = plaintext.find(b"DH{")
    end = plaintext.find(b"}", start)
    if start != -1 and end != -1:
        return plaintext[start:end+1]
    return None

def main():
    ciphertext = open("ciphertext.txt", "rb").read()
    keys = [bytes.fromhex(line.strip()) for line in open("keys.txt") if line.strip()]

    base_seed = int(os.path.getmtime("ciphertext.txt"))
    print("[*] Base seed:", base_seed)

    for delta in range(-3, 4):
        seed = base_seed + delta
        flag = try_seed(seed, ciphertext, keys)
        if flag:
            print("[+] Found FLAG with seed", seed, ":", flag.decode(errors="ignore"))
            break
    else:
        print("[-] FLAG not found in nearby seeds")

if __name__ == "__main__":
    main()

 

해당 코드를 작동시키면 다음과 같이 나오고 정답이 맞았다. (2시간 걸림...)

[*] Base seed: /*seed 자리*/
[+] Found FLAG with seed /*seed 자리*/ : DH{/*진짜 답을 써놓긴 애매해서 주석처리 이 자리에 flag 나왔음.*/}