개념 설명
ECB is the most simple mode, with each plaintext block encrypted entirely independently. In this case, your input is prepended to the secret flag and encrypted and that's it. We don't even provide a decrypt function. Perhaps you don't need a padding oracle when you have an "ECB oracle"?
ECB는 가장 간단한 모드로, 각 일반 텍스트 블록이 완전히 독립적으로 암호화된다.
이 경우 입력 내용이 비밀 플래그 앞에 추가되고 암호화되며, 그게 전부이다.
암호 해독 기능도 제공하지 않는다. "ECB 오라클"이 있으면 패딩 오라클이 필요하지 않을까?
Play at https://aes.cryptohack.org/ecb_oracle
문제 풀이
ECB Oracle 문제를 풀기 위해서 ECB가 뭔지 알아야 한다.
ECB는 Electronic CodeBook의 약자로 블록 암호의 작동 모드 중 하나이다. ECB 모드는 각 평문 블록이 독립적으로 암호화되며 같은 평문 블록은 동일한 암호문으로 암호화된다. 같은 키를 사용하기 때문이다.
ECB는 이런 특성 때문에 보안상 취약하다. 이를 대체하기 위해 평문 사이에 정보를 섞는(아마 iv, initial vector) CBC나 CTR 모드가 있다.
ECB가 어떻게 취약한지 자세히 살펴보자.
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
KEY = ?
FLAG = ?
@chal.route('/ecb_oracle/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)
padded = pad(plaintext + FLAG.encode(), 16)
cipher = AES.new(KEY, AES.MODE_ECB)
try:
encrypted = cipher.encrypt(padded)
except ValueError as e:
return {"error": str(e)}
return {"ciphertext": encrypted.hex()}
이건 문제 코드이다.
위 문제에서는 AES로 암호화 하기 전에 pad 함수를 사용해서 padding과정을 거친다. padding은 기본적으로 PKCS#7을 사용한다고는 하는데 여기서 크게 중요하지는 않고, padding은 블록의 크기를 16byte의 배수로 맞추기 위해서 사용한다.
https://lactea.kr/entry/ECB-%EC%84%A4%EB%AA%85-%EB%B0%8F-%EC%B7%A8%EC%95%BD%EC%A0%90
https://crypto.stackexchange.com/questions/42891/chosen-plaintext-attack-on-aes-in-ecb-mode
위 글들을 읽어보면 이해가 수월해진다.
앞서 ECB 모드가 같은 내용을 가진 블록에서는 동일한 암호문이 나온다고 하였는데 이를 이용해서 앞부터 한 바이트씩 맞춘다.
우리는 문제에서 평문 + flag의 조합으로 암호화를 진행한다.
평문은 우리가 알고 있는 값이다.
A를 15개를 넣어두면 flag의 첫 글자까지 해서 한 블록으로 묶인다.
해당 블록을 암호화한 값을 우리는 알고 있다.
예시
이미 crypto{} 형식으로 플래그가 구성된다는 것을 안다. 15개로 평문을 채우면,
AAAA AAAA AAAA AAAc
로 블록이 구성된다.
그럼 암호문의 첫 16byte는 AAAA AAAA AAAA AAAc 를 암호화한 것이다. (블록 암호의 특성)
우리는 암호화한 값을 알고 있다.
브루트포싱을 통해서 A 15개 + 임의의 1byte로 했을 때, 알고 있는 암호문과 일치하는지 판단한다.
일치한다면, flag의 첫 글자를 알아낸 것이다.
다음으로 패딩을 1개 줄여서 A 14개 + 알고있는 1byte + 임의 1 byte로 하여 위 과정을 반복한다.
그러면 flag를 알아낼 수 있다.
import requests
import string
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
flag = b'crypto{'
def response(plaintext):
url = 'https://aes.cryptohack.org/ecb_oracle/encrypt/'
url += plaintext.hex()
url += '/'
r = requests.get(url)
js = r.json()
return bytes.fromhex(js['ciphertext'])
for i in range(7, 26):
plaintext = b'\x41' * (31 - i)
target = response(plaintext)[:32]
plaintext += flag
for j in range(33, 127):
plaintext = plaintext[:31]
plaintext += j.to_bytes(1, byteorder='big')
print(j)
bullet = response(plaintext)[:32]
if (bullet == target):
flag += j.to_bytes(1, byteorder='big')
print(flag)
break
exploit code에서는 블록을 32byte 까지로 하여 A를 채웠다. flag의 길이가 25 byte이므로 16byte를 넘어가기 때문이다. flag의 길이가 25byte임을 알아낸 건 그냥 하나하나 평문을 넣어보다가 7byte 평문을 넣었을 때 암호문의 길이가 길어졌기 때문이다.
cryptohack문제를 풀 때 requests 모듈을 사용해야 하는지는 몰랐는데, 저렇게 풀 수 있는걸 알았다.
ECB가 아니라 CBC에서의 Oracle Padding Attack 이 궁금하다. 드림핵에 관련 문제가 있던데..