CTF | wargame

hackcenter / Broken Encryption 0 (ECB 블록 암호, 평문 변경 공격......)

nopdata 2017. 3. 7. 21:07
keyword : ecb 블록 암호, 파이썬 소스코드 분석


Take a look at MI6's latest service, running on enigma2017.hackcenter.com:43589. It may use the secure AES algorithm, but there must be some way you can recover the flag...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
root@nopdata:/home/iy/ctf# python ecb.py
 
************ MI6 Secure Encryption Service ************
                  [We're super secure]
       ________   ________    _________  ____________;_
      - ______ \ - ______ \ / _____   //.  .  ._______/ 
     / /     / // /     / //_/     / // ___   /
    / /     / // /     / /       .-'//_/|_/,-'
   / /     / // /     / /     .-'.-'
  / /     / // /     / /     / /
 / /     / // /     / /     / /
/ /_____/ // /_____/ /     / /
\________- \________-     /_/
Agent number: a
this is m :  agent a wants to see 97979797
pad m :  agent a wants to see 97979797100
fcbd400e4eb2be7809f5ab38fa2d9f144304cfbcd9982c61ccf3302374689ed5
root@nopdata:/home/iy/ctf# python ecb.py
************ MI6 Secure Encryption Service ************
                  [We're super secure]
       ________   ________    _________  ____________;_
      - ______ \ - ______ \ / _____   //.  .  ._______/ 
     / /     / // /     / //_/     / // ___   /
    / /     / // /     / /       .-'//_/|_/,-'
   / /     / // /     / /     .-'.-'
  / /     / // /     / /     / /
 / /     / // /     / /     / /
/ /_____/ // /_____/ /     / /
\________- \________-     /_/
 
 
Agent number: 1111
this is m :  agent 1111 wants to see 97979797
pad m :  agent 1111 wants to see 979797971000000000000000
9bcaca09a6520d6d907a3e5d99a6ad639faa9127c8341512b328cf1eef3efb79a0ea732f2a267da68e0a881ddb882456
root@nopdata:/home/iy/ctf
 
cs

파이썬 소스코드가 주어지고 해당 소스가 서버에서 돌고 있다. 그 소스를 분석해서 풀어내면 되는 문제이다. 소스코드는 enc_key파일과 flag파일이 필요하며, 다음과 같은 구조를 지닌다.
클라이언트에게 문자열을 입력 받도록 하고, "agent 입력값 wants to see flag값"으로 새로운 message를 만들고 이를 암호화 시킨다. 암호화 방식은 AES이며 ECB방식을 사용하고 있다.

[ Electronic Codebook mode ]
ECB는 코드북 암호로, 각 암호문이 상관이 없는 독립적인 암호 방식이다. 
이제 원리를 알았으니, 각 암호화된 plaintext의 길이를 알아내야 한다. 본래 소스코드를 조금 변형해서 보기 편하게 수정한 다음 결과를 보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
root@nopdata:/home/iy/ctf# python ecb.py
 
************ MI6 Secure Encryption Service ************
                  [We're super secure]
       ________   ________    _________  ____________;_
      - ______ \ - ______ \ / _____   //.  .  ._______/ 
     / /     / // /     / //_/     / // ___   /
    / /     / // /     / /       .-'//_/|_/,-'
   / /     / // /     / /     .-'.-'
  / /     / // /     / /     / /
 / /     / // /     / /     / /
/ /_____/ // /_____/ /     / /
\________- \________-     /_/
Agent number: a
this is m :  agent a wants to see 97979797
pad m :  agent a wants to see 97979797100
fcbd400e4eb2be7809f5ab38fa2d9f144304cfbcd9982c61ccf3302374689ed5
root@nopdata:/home/iy/ctf# python ecb.py
************ MI6 Secure Encryption Service ************
                  [We're super secure]
       ________   ________    _________  ____________;_
      - ______ \ - ______ \ / _____   //.  .  ._______/ 
     / /     / // /     / //_/     / // ___   /
    / /     / // /     / /       .-'//_/|_/,-'
   / /     / // /     / /     .-'.-'
  / /     / // /     / /     / /
 / /     / // /     / /     / /
/ /_____/ // /_____/ /     / /
\________- \________-     /_/
 
 
Agent number: 1111
this is m :  agent 1111 wants to see 97979797
pad m :  agent 1111 wants to see 979797971000000000000000
9bcaca09a6520d6d907a3e5d99a6ad639faa9127c8341512b328cf1eef3efb79a0ea732f2a267da68e0a881ddb882456
root@nopdata:/home/iy/ctf
 
cs

flag파일은 97979797이 들어있고 enc_key는 길이값에 맞추어 임의의값을 넣어 주었다.
입력값이 1일때와 1111일 때 인데 보면 padding된 값의 차이가 크다. 그 이유는 AES암호화를 시키는데 블록 크기가 각각 16bytes가 되어야 하기 때문에 16의 길이로 나뉘지 않으면 pad 함수를 호출하여 길이값을 맞추기 때문이다.여기서 생각해야할 점은 암호에서 사용한 블록 암호의 방식이 ECB라는 것이다. 각각의 평문이 암호화 되는 것은 독립적이다. 그 소리는 같은 1111을 두 번 따로 암호화 시키면 그 결과가 똑같이 나와야 한다는 소리이다.

일단 여기까지 알아냈고, 먼저 flag값의 길이를 알아 보기로 했다. flag값의 길이는 서버에 전송을 하였을 때 결과로 돌아오는 암호문의 길이를 보고 알 수 있다. 가령, 위에서 보면 flag가 97979797로 길이값은 8이다. 여기서 1을 넣었을 때와 1111을 넣었을 때의 패딩으로 인한 길이값 차이로 알아내면 된다. 이 방법으로 알아냈을 때, flag의 길이값은 50이 된다.

입력값을 1*107을 했을 때의 결과값이다.

61b13709279a3a22c9e60992686bbc26f189fb5f6e81e9286fc6b4305bec8cd9f189fb5f6e81e9286fc6b4305bec8cd9f189fb5f6e81e9286fc6b4305bec8cd9f189fb5f6e81e9286fc6b4305bec8cd9f189fb5f6e81e9286fc6b4305bec8cd9f189fb5f6e81e9286fc6b4305bec8cd9320dd9ba0ec43c5429297a672d994caaa6ebba0ff7b87217a3299a32a3a843d3b836bdf700bc9a830577208ceffc75a6fdade8675dfde5bb4e90291e37b057f327ad9e330f456e97fc6031e252335eb0

기본 셋팅 문자열과 flag, 패딩값이 들어간 결과를 암호화한 값일 것이다. 하지만 1이 16길이 이상으로 들어갔기 때문에 반복이 이루어질 것다. 각 평문 16자리에 나오는 결과는 그 두배인 32자리이다. 이걸 기준으로 1*107의 암호화된 값은 다음과 같다.


61b13709279a3a22c9e60992686bbc26
f189fb5f6e81e9286fc6b4305bec8cd9
f189fb5f6e81e9286fc6b4305bec8cd9
f189fb5f6e81e9286fc6b4305bec8cd9
f189fb5f6e81e9286fc6b4305bec8cd9
f189fb5f6e81e9286fc6b4305bec8cd9
f189fb5f6e81e9286fc6b4305bec8cd9
320dd9ba0ec43c5429297a672d994caa
a6ebba0ff7b87217a3299a32a3a843d3
b836bdf700bc9a830577208ceffc75a6
fdade8675dfde5bb4e90291e37b057f3
27ad9e330f456e97fc6031e252335eb0

1*107 : f189fb5f6e81e9286fc6b4305bec8cd9

이제 enc_key값을 알아내야 하는데 1*16의 결과가 f189fb5f6e81e9286fc6b4305bec8cd9라는 것을 알고 있으니 이걸 가지고 알아내면 된다.

모여서 스터디 할 때 얻은 힌트를 기반으로 1자리리씩 증가시켜가며 키값을 얻는 방식으로 다시 소스코드를 작성하였다.
초기 flag값 2자리는 테스트를 하며 획득 하여서 그냥 소스코드에 박아놨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import socket
from pwn import log
 
def pad(m):
    while (len(m)-10) % 16 != 0:
 
        m += '0'
    return m
 
 
 
table="_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}-=!@#$%^&*()+,./<>?;:'\""
table="_1234567890BCEabcdefisthwr"
flag = ""
 
 
flag = "5687507d379a2d25496854ec97ea0fa2_ECB_is_the_w0rst"
flag = "56"
prefix = '1111111111'
chk_message = "wants to see 56"
dummy = len(flag)-1
 
= log.progress("flag ")
while True:
    for i in table:
        sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sock.connect(('enigma2017.hackcenter.com',43589))
 
        res = sock.recv(4096)
        res = sock.recv(4096)
        message = prefix + chk_message + i + '0'*((64-dummy)%16)
        res = sock.send(message+'\n')
        res = sock.recv(4096)
 
        n = (len(flag)-2)/16
        n *= 32
        p.status("%s %s"%(flag,i));
        if res[32:64== res[96+n:128+n]:
            flag += i
            break
    dummy += 1
    chk_message = chk_message[1:]+i
 
 
p.success(flag)
 
cs

이 소스코드를 돌려보면 마지막에 w0rst1이 나오게 되는데 1은 패딩값이므로 flag에서 제외를 해 주어야 한다.

Answer : 5687507d379a2d25496854ec97ea0fa2_ECB_is_the_w0rst