본문 바로가기
카테고리 없음

[Dreamhack] Double DES 문제 풀이

by whiteTommy 2024. 6. 20.

 

 

첨부된 문제 파일을 받아서 열면 다음과 같은 코드가 있다.

#!/usr/bin/env python3
from Crypto.Cipher import DES
import signal
import os

if __name__ == "__main__":
    signal.alarm(15)

    with open("flag", "rb") as f:
        flag = f.read()
    
    key = b'Dream_' + os.urandom(4) + b'Hacker'
    key1 = key[:8]
    key2 = key[8:]
    print("4-byte Brute-forcing is easy. But can you do it in 15 seconds?")
    cipher1 = DES.new(key1, DES.MODE_ECB)
    cipher2 = DES.new(key2, DES.MODE_ECB)
    encrypt = lambda x: cipher2.encrypt(cipher1.encrypt(x))
    decrypt = lambda x: cipher1.decrypt(cipher2.decrypt(x))

    print(f"Hint for you :> {encrypt(b'DreamHack_blocks').hex()}")

    msg = bytes.fromhex(input("Send your encrypted message(hex) > "))
    if decrypt(msg) == b'give_me_the_flag':
        print(flag)
    else:
        print("Nope!")

 

위의 코드를 해석해보자.

 

signal.alarm(15)

15초 내의 시간 제한을 의미한다.

 

key = Dream_ + os.urandom(4) + Hacker 
알려진 6바이트, 랜덤 4바이트, 알려진 6바이트의 키가 생성된다.

 

여기서, os.urandom(4)는 4바이트의 난수로 예측하는 것이 불가능하다.

 

key1 = key[:8] 

key2 = key[8:]

key에서 앞의 8바이트를 key1, 나머지를 key2로 설정한다.

key1은 알려진 6바이트 + 미지의 2바이트, key2는 미지의 2바이트 + 알려진 6바이트이다.

 

cipher1 = DES.new(key1, DES.MODE_ECB)

cipher2 = DES.nes(key2, DES.MODE_ECB)

key1을 이용해서 ECB모드로 DES 암호화를 통해 cipher1을 생성한다.

key2를 이용해서 ECB모드로 DES 암호화를 통해 cipher2를 생성한다.

 

encrypt = lambda x : cipher2.encrypt(cipher1.encrypt(x))

decrypt = lambda x : cipher1.decrypt(cipher2.decrypt(x))

전체 암호화는 입력데이터 x를 cipher1으로 암호화한 후 cipher2를 사용해서 다시 암호화한다.

복호화는 cipher2로 복호화한 후 cipher1을 사용해서 다시 복호화한다.(암호화의 역으로 진행된다)

 

 

문제에서 구하고자 하는 flag 값을 구하기 위해서 코드를 다시 보자.

 

print(f"hint for you :> {encrypt(b'DreamHack_blocks').hex()}")

DreamHack_blocks 를 암호화해서 16진수로 나타낸 값이

2e6b0bddb2971375517807d5ae32bb6e 임을 알 수 있다.

 

msg = bytes.fromhex(input("Send your encrypted message(hex) >"))

bytes.hex()는 16진수 hex 숫자에 \x를 붙여 byte형으로 변경된 값이 msg 변수에 저장

 

if decrypt(msg) == b'give_me_the_flag' :

    print(flag)

else:

    print("Nope!")

이 변수 msg를 복호환 것이 give_me_the_flag와 같으면 flag가 출력된다.

즉, 어떠한 암호문을 복호화해야 give_me_the_flag 라는 평문이 나오는지를 구해야 한다.

다시 말해서, 암/복호화에 사용된 key를 알아야 한다.

 

key1으로 가능한 후보는 2^16 가지, key2로 가능한 후보는 2^16가지이여서 2^32만큼 경우의수가 있을 것 같지만,

 

아래와 같은 meet-in-the-middle attack이 가능하다.

 

DreamHack_blocks를 A, 전체 암호화 결과를 B라고 하면 다음이 성립한다.

cipher2. encrypt(cipher1.encrypt(A)) = B

 

양변에 cipher2.decrypt를 취해보자.

cipher1.encrypt(A) = cipher2.decrypt(B)

 

위의 식을 만족하는 공통된 key1, key2가 올바른 키일 것이라고 추측할 수 있다. 2^16  = 2^17만큼만 연산하면 된다.

 

두 2^16 길이의 배열의 공통원소를 찾기 위해서 아래 코드(util.py)를 작성해보자

from Crypto.Cipher import DES
from pwn import * 

key = b'Dream_' + bytearray(4) + b'Hacker'  # 키 형식 정의: 'Dream_' + 4바이트의 빈 바이트 배열 + 'Hacker'

# key1의 부분을 브루트포싱하여 모든 가능한 값에 대해 암호화된 텍스트를 저장
m = {}
for i in range(0, 256 * 256):
    key1 = key[:6] + int.to_bytes(i, 2, 'big')
    cipher1 = DES.new(key1, DES.MODE_ECB)
    cipher_text = cipher1.encrypt(b'DreamHack_blocks')  # 'DreamHack_blocks' 문자열 암호화
    m[cipher_text] = int.to_bytes(i, 2, 'big')  # {"key1로 암호화된 암호문", "key1의 "}

r = remote('host3.dreamhack.games', 8585)
data = r.recvline()
data = r.recvline()[16: -1]
print(data)

# 16진수로 된 암호화된 값을 바이트 배열로 변환
cipher_text3 = int.to_bytes(int(data, 16), 16, 'big')
ans = 0
# key2를 브루트포싱하여 복호화한 값이 딕셔너리에 있는지 확인.
for i in range(0, 256 * 256):
    key2 = int.to_bytes(i, 2, 'big') + key[10:]
    cipher2 = DES.new(key2, DES.MODE_ECB)
    cipher_text2 = cipher2.decrypt(cipher_text3)
    if cipher_text2 in m:
        ans = m.get(cipher_text2) + int.to_bytes(i, 2, 'big')

# 두 개의 키 부분으로 DES 암호 객체 생성
cipher1 = DES.new(key[:6] + ans[:2], DES.MODE_ECB)
cipher2 = DES.new(ans[2:] + key[10:], DES.MODE_ECB)
# 'give_me_the_flag' 문자열을 이중 DES로 암호화
send_msg = cipher2.encrypt(cipher1.encrypt(b'give_me_the_flag'))

print(send_msg.hex())
r.sendline(send_msg.hex().encode('ascii')) 
r.interactive()

 

2^16가지의 key1으로 DreamHack_blocks 평문을 암호화해서 이에 해당하는 값을 m배열에 (key, value) 쌍으로 저장한다.

 

Host, post로 remote해서 입력을 받아와서 data 변수에 저장한다.

data을 16진수로 된 암호화된 값을 바이트 배열로 변환한다.

 

이번에는 key2로 암호화한 cipher2로 복호화한 값이 m 배열에 있는지 확인하고 있으면 ans 배열에 저장한다.

 

찾아낸 ans 배열을 이용해서 key를 찾아내고, double DES로 give_me_the_flag를 암호화해서 send_msg 변수에 저장한다.

이를 다시 16진수로 변환해서 ascii 코드로 인코딩하자.

 

해당 파일을 실행해보면 flag를 얻을 수 있다.