🗂️ INDEX

글 작성자: Universe7202

 

Concept

ECB(Electronic codebook)는 암호화하려는 메시지를 여러 불록으로 나누어 각각 암호화 하는 방식이다.

암호화를 하기 위해서는 반드시 암호화에 쓰이는 대칭키 의 길이와 각 블록에 들어있는 길이가 같아야 한다.

 

예를들어 다음과 같은 평문이 있다고 가정하자.

123456789abcdefg

 

우선 암호화를 하기위한 대칭키 K(6자리)가 있고 위 평문을 6개씩 묶어 블록으로 나눈다.

123456
789abc
defg

3번째 defg는 ECB 암호화를 하기에는 규칙에 맞지 않아, 패딩(Padding)이라는 것을 해준다. 이는 빈 공간을 채우는 역할로 여러가지 빈공간을 채우는 방식이 있지만 0으로 채워 넣겠다.

 

123456
789abc
defg00

각 6개씩 3개의 블록으로 나누어 졌고, 이를 대칭키로 암호화를 한다. 암호화를 했을 때 아래와 같은 값이 나오게 된다.

3e306d51adb22af64156aa5d9962a10b
49faea7744ff6b2d12a7b8ff54b26654
11e59127999bd255bb29ec1a7e1f8e5e

간단하게 설명하면 다음과 같이 암호화가 될 것이다.

 

 

 

Vulnerablitiy

이 ECB 방식으로 암호화를 하게되면 단점은 똑같은 key로 암호화를 하게 된다는 것이다.

따라서 몇개의 블록으로 나누었는데, 똑같은 값을 가진 블록이 여러개가 생겨 공격자에게 암호문을 풀 수 있는 힌트를 제공 할 수 있다.

 

예를들어 다음과 같은 상황이다.

 

어떤 메시지를 보내기 위해 메시지 앞뒤로 SALT 값과  개인정보를 붙인 뒤 암호화 한다고 가정을 하자

SALT = R@nd

사용자 메시지 = hello

개인정보 = 1990.1.1 

[ SALT 값 ] + [ 사용자 메시지 ] + [ 개인정보 ]
R@ndhello1990.1.1

 

위 평문처럼 3가지의 정보를 하나의 문장으로 만든 뒤, 각각 6개씩 블록으로 나눈다. 그럼 아래와 같이 나누어 질 것이고, 빈 공간은 0으로 패딩한다.

 

R@ndhe  llo199  0.1.10
3e306d51adb22af64156aa5d9962a10bab6f75f68e8a8fc0538359cd0e56f640ff69027c017e12c5156fdaf08a59c609

이제 이 값은 위와 같은 값으로 암호화가 될 것이다.

 

이제 어떻게 이 암호문을 평문으로 바꾸는 방법을 알려주겠다.

이 공격의 핵심은 오프셋(offset)을 찾는 것이다.

공격자가 사용자 메시지에 연속하는 문자 a를 계속 입력했다고 가정하자. 

주목할 부분은 암호문의 첫번째 32글자이다.

 

 

1. 사용자 메시지 = a

R@nda1 990.1. 100000
2f76a319681abd40ee5e84286c01b92d 3840fec39d70dd387d5a76c7898e965b 3b900c327e46f9301e73ec3dca569f03

 

 

2. 사용자 메시지 = aa

R@ndaa 1990.1 .10000
c25d74f34f5003febfa42632c0a50873 ba8e6c33ad3d46bf05fd52a9f7e8246d d162667efdca8b7228f80e6685c8d54d

 

 

3. 사용자 메시지 = aaa

R@ndaa a1990. 1.1000
c25d74f34f5003febfa42632c0a50873 7c5a58fb1a9ad1e547d6439cc82b1ccd 129d23ac47e0bb684a129e8decdcf464

 

2번과 3번의 암호문 중 앞부분 32글자를 보면 값이 똑같은 것을 볼 수 있다(c25d74f34f5003febfa42632c0a50873).

이것이 의미하는 것은 6개씩 3개의 블록으로 나누었는데, 첫번째 블록에 R@ndaa 라는 값이 고정으로 들어가기 때문에 똑같은 key로 암호화 해서 똑같은 값이 나오게 된 것이다. (당연하다!)

 

위에서 말했듯이 이 공격의 핵심은 오프셋(offset)이라고 말했다. 즉 공격자는 사용자 메시지에 기본적으로 aa 라는 문자를 입력하고 시작해야 한다는 말이다.

 

공격의 시작 부분을 찾는 것이 왜 중요한지 이해가 가질 않는다면, 아래 글을 더 읽어보자.

 

 

우리는 이제 공격의 시작 부분을 찾았다.

사용자 메시지에 aa를 기본으로 입력하고 추가적으로 a 값을 총 6개 더 적는다.

그러면 다음과 같은 평문과 암호문이 만들어 진다.

R@ndaa aaaaaa 1990.1 .10000
c25d74f34f5003febfa42632c0a50873 80776064b877bca5ffecb41bcee5b5e6 aaaac5fc3c2dee4f79bffe57b4c93d84 4bc359a66cb5671bf8e0656405e548a6

a를 기본적으로 2개를 입력하면 앞에 암호문을 고정 시킬 수 있게 되고, 6개가 하나의 블록 이라는 것을 알기 때문에 추가적으로 a값을 6개 더 적어 주었다.

 

여기서 약간의 발상을 해보자.

aa + aaaaaa 에서 a를 하나 빼보자.

 

그러면 aa + aaaaa_ 이렇게 된다. under bar(_)에는 뒤에 있던 개인정보의 첫 자리가 들어가게 된다.

그러면 다음과 같은 평문과 암호문이 만들어 진다.

R@ndaa aaaaa1 990.1. 100000
c25d74f34f5003febfa42632c0a50873 43662817f791cbb0ab7d415dadf29d90 64f6e358e634cc1d51f2a0668c9caf94 05e65681deb03c7711a52d293e516161

aaaaa1 의 암호문이 위 암호문 중 2번째인 43662817f791cbb0ab7d415dadf29d90 인 것을 알 수 있게 된다.!!!

공격자 입장에서는 aaaaa? 에서 43662817f791cbb0ab7d415dadf29d90 로 암호화 된것으로 보이기 때문에 ? 물음표 하나만 찾으면 된다.

 

찾는 방법은 무차별 대입 공격을 하면 된다.

아래 처럼 마지막 한자리를 바꿔 가면서 43662817f791cbb0ab7d415dadf29d90 와 같은 값을 찾으면 된다.

aaaaaa => 26284af...
aaaaab => 7ad4aa4...
aaaaac => 12aa124...
.
.
.
aaaaa1 => 43662817f791cbb0ab7d415dadf29d90 

따라서 개인정보 첫자리가 "1" 인 것을 알아 낼 수 있게 된다.

 

똑같은 방법으로 이번에는 aa + aaaaa 에서 a를 하나 빼어 aa + aaaa 를 입력하면 아래와 같은 평문과 암호문이 만들어 진다.

R@ndaa aaaa19 90.1.1
c25d74f34f5003febfa42632c0a50873 b45587d3f25cfdaaaf9042e39b3b3fdc c8e42f88b970f2a3931784c8feb02891

공격자 입장으로 봤을 때, aaaa1? 이므로 ?의 값을 바꿔 가면서  b45587d3f25cfdaaaf9042e39b3b3fdc 이 값이 나올때 까지 무차별 대입 공격을 하면 된다.

 

aaaa1a => 4d362140...
aaaa1b => 1d46c49c...
aaaa1c => 1cf48acd...
.
.
.
aaaa19 => b45587d3f25cfdaaaf9042e39b3b3fdc  

 

이런 과정을 반복하면 암호문을 복호화 할 수 있게 된다.

 

 

 

 

 

Proof Of Concept

아래 코드는 ECB 방식을 사용하여 평문을 암호문으로 만들어 주는 python code이다.

SALT 값은 랜덤으로 줄 수 있으나, 설명을 위해 SALT 값을 고정해주었다.

from Crypto.Cipher import AES
import random

def padding(msg):
    return msg + chr(block_size - len(msg) % block_size) * (block_size - len(msg) % block_size)

# Generate random SALT
def generate_SALT():
    ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    chars=[]
    
    for i in range(random.randint(10,14)):
        chars.append(random.choice(ALPHABET))
    return "".join(chars)

block_size = 16          # Block size
key = "Un1v3rs3_awesome" # AES key
# SALT = generate_SALT()    # Random SALT
SALT = "R@nd0mVaLu3"
msg = ''                 # User input value
flag = 'flag{flag_test_T35t}'   # flag

cipher = AES.new(key, AES.MODE_ECB)
decipher = AES.new(key, AES.MODE_ECB)

while True:
    msg = input("Input value: ")
    msg = padding(SALT + str(msg) + flag)                 
    crypto_text = cipher.encrypt(msg).hex() 
    
    print(crypto_text)

 

 

위 코드를 실행하면 아래처럼 값을 입력할 수 있는데 a를 하나씩 늘려가며 값을 주면 암호문 앞에서 32글자가 똑같이 출력 되는 것을 볼 수 있다.

ubuntu:~/environment/ctf $ python3 ecb.py 
Input value: a
d21dcc401de699d884fb960b7753e5f31ba508c2e3f44593e34a1bf2b80767b651ba458678a63167ea759913f35cc6aa
Input value: aa
fea8ba51ed57eed7f9800fccbffbee2e11e59127999bd255bb29ec1a7e1f8e5eff69027c017e12c5156fdaf08a59c609
Input value: aaa
cd2778b36fb926c9d514fa32bc957f03c25d74f34f5003febfa42632c0a50873be55a4f8958045aae8122596b991aaf6
Input value: aaaa
cd2778b36fb926c9d514fa32bc957f03343cc122dd8547b607706b1a677d4816a1ebedb337930a723d5e268de61cb137
Input value: aaaaa
cd2778b36fb926c9d514fa32bc957f03ebefe993cc4df8e6116ab53d5fd20b3662956c3a2787209fb5977fa2ede7a8db
Input value: aaaaaa
cd2778b36fb926c9d514fa32bc957f033cdc36ab08e6de238149e2a1fe90eda4ebf8dcd636263efb987863e90b451a43
Input value: 

 

 

평문 값을 유출시키기 위해 위에서 설명한 방법을 python code로 구현해 보았다. (코드가 그닥 좋지는 않을....)

암호문을 생성하는 python code를 xinetd로 돌리고 pwntool 모듈을 사용하여 nc로 붙어서 문제를 풀었다.

import string
from pwn import *
import time

p = remote("localhost", 7202)
send_data = 'a'
front = ''
length = ''

print ("Guessing..")
while True:
    p.recvuntil("Input value: ")    
    p.sendline(send_data)
    result = p.recvline()
    print(result)
    print(send_data)
    
    if front == result[:32]:
        length = len(send_data)
        break
    else:
        front = result[:32]
        send_data += 'a'
    
    
print ("Finding flag...")
count = 14 + len(send_data)
flag = ''
 
while count != 0:
    p.recvuntil("Input value: ")
    p.sendline("a"*count)
    result = p.recvline()
    result = result[32:64]

    for char in string.printable:
        send_data2 = "a"*count + flag + char
        p.recvuntil("Input value: ")
        p.sendline(send_data2)
        
        result2 = p.recvline()
        result2 = result2[32:64]
        
        print(send_data2)
        print(result + " " + result2)
        
        if result == result2:
            count -= 1
            flag += char
            print(">>> " + flag)
            
            break
        print("")
            
            
print(result)

 

 

 

위 코드를 실행하면 평문을 유출하여 flag 값을 획득 할 수 있는 것을 볼 수 있다.

.
.
.

aflag{flag_test_T35t[
9e4909fccc0638eeb70612c5feba272e 35c0eff6946b6e0623f48f7c0736031e

aflag{flag_test_T35t\
9e4909fccc0638eeb70612c5feba272e 4cb48a0f32e931f1b38a0950e1b58e66

aflag{flag_test_T35t]
9e4909fccc0638eeb70612c5feba272e 036331820c0379bff9a255839f3824b8

aflag{flag_test_T35t^
9e4909fccc0638eeb70612c5feba272e 6a42dd8163bafce9bdf80b5a535c3548

aflag{flag_test_T35t_
9e4909fccc0638eeb70612c5feba272e 410eb8c4e7297f446fff773e687a01d8

aflag{flag_test_T35t`
9e4909fccc0638eeb70612c5feba272e 87bc5fb694930eadca031f735fa37b94

aflag{flag_test_T35t{
9e4909fccc0638eeb70612c5feba272e 7c783269a50aadaf2b22995d7da41d2f

aflag{flag_test_T35t|
9e4909fccc0638eeb70612c5feba272e 36c2b0e33c248dd5a71a55f16ff52104

aflag{flag_test_T35t}
9e4909fccc0638eeb70612c5feba272e 9e4909fccc0638eeb70612c5feba272e
>>> flag{flag_test_T35t}
9e4909fccc0638eeb70612c5feba272e
[*] Closed connection to localhost port 7202

 

 

 

이 문제는 CTF에서 ECB 를 이용한 crypto 문제가 나왔는데, 풀이를 보다가 재미있어 보여서 공부를 하고

실제 문제를 만들어 poc code를 작성하여 글을 작성하게 되었다.