Aero CTF 2020 Writeup

この大会は2020/2/29 18:00(JST)~2020/3/1 18:00(JST)に開催されました。
今回もチームで参戦。結果は831点で273チーム中72位でした。
自分で解けた問題をWriteupとして書いておきます。

Babycrypt (Reverse)

あまりバイナリのアセンブリ等の解析はせずに、動作的な暗号の特徴から推測することにした。

$ ./bcry
key: 11111111
[-] Error key size!
$ ./bcry
key: 1111111111111111
text: 123
Encoded: 313433
$ ./bcry
key: 1111111111111112
text: 123
Encoded: 313433
$ ./bcry
key: 3333333333333333
text: 123
Encoded: 353433
$ ./bcry
key: 3333333333333333
text: 156
Encoded: 353938
$ ./bcry
key: 0000000000000000
text: 178
Encoded: 313738
$ ./bcry
key: 0000000000000000
text: 1jj
Encoded: 318a8a

1文字ずつ対応しているようだ。

$ ./bcry
key: 0000000000000000
text: test_test_test_test_test
Encoded: 748573749f748573749f748573749f748573749f74857374
$ ./bcry
key: 1111111111111111
text: test_test_test_test_test
Encoded: 768573769f768573769f768573769f768573769f76857376
$ ./bcry
key: 1111111111111111
text: qwertyuiopasdfgh
Encoded: 71778574767975898f7281738688878a
$ ./bcry
key: aaaaaaaaaaaaaaaa
text: aaaaaaaaaaaaaaaa
Encoded: 61616161616161616161616161616161
$ ./bcry
key: aaaaaaaaaaaaaaaa
text: bbbbbbbbbbbbbbbb
Encoded: 64646464646464646464646464646464
$ ./bcry
key: aaaaaaaaaaaaaaaa
text: cccccccccccccccc
Encoded: 63636363636363636363636363636363
$ ./bcry
key: aaaaaaaaaaaaaaaa 
text: dddddddddddddddd
Encoded: 66666666666666666666666666666666
$ ./bcry
key: aaaaaaaaaaaaaaaa
text: eeeeeeeeeeeeeeee
Encoded: 65656565656565656565656565656565
$ ./bcry
key: aaaaaaaaaaaaaaaa
text: ffffffffffffffff
Encoded: 68686868686868686868686868686868
$ ./bcry
key: 0000000000000000
text: aaaaaaaaaaaaaaaa 
Encoded: 81818181818181818181818181818181

ここまで見てきて、keyとtextでxorしたものをkeyに足していると推測できる。

key: %key%
text: test_test_test_test_test
Encoded: 7685737a9f7895737a9f84857b769f7a657b769f78898378

key: %key%
text: qwertyuiopasdfgh
Encoded: 717785747885858d6f7e917364686776

key: %key%
text: skIllaoInasJjklqo19akq9k13k45k69alq1
Encoded: 7393a992708d8fad708d83aa7273707d6f3939856b7d398bb53b8b34b573b6c5618e7135

暗号と平文が1対1対応していないので、上記の条件で満たす鍵すべてに対して、可能性のあるフラグをリストにしてみる。

def decrypt_char(c, key):
    code = (int(c, 16) - ord(key)) ^ ord(key)
    if code >= 0 and code < 256:
        return chr(code)
    else:
        return 'x'

pt = [
    'test_test_test_test_test',
    'qwertyuiopasdfgh',
    'skIllaoInasJjklqo19akq9k13k45k69alq1']
ct = [
    '7685737a9f7895737a9f84857b769f7a657b769f78898378',
    '717785747885858d6f7e917364686776',
    '7393a992708d8fad708d83aa7273707d6f3939856b7d398bb53b8b34b573b6c5618e7135']

enc_flag = '8185748f7b3b3a3565454584b8babbb8b441323ebc8b3a86b5899283b9c2c56d64388889b781'

keys = []
for i in range(len(enc_flag) / 2):
    for code in range(32, 127):
        found = True
        if i < len(pt[1]):
            for j in range(3):
                if (code ^ ord(pt[j][i])) + code != int(ct[j][i*2:i*2+2], 16):
                    found = False
        elif i < len(pt[0]):
            for j in [0, 2]:
                if (code ^ ord(pt[j][i])) + code != int(ct[j][i*2:i*2+2], 16):
                    found = False
        elif i < len(pt[2]):
            for j in [2]:
                if (code ^ ord(pt[j][i])) + code != int(ct[j][i*2:i*2+2], 16):
                    found = False
        if found:
            keys.append((i, chr(code)))

flags = []
for i in range(len(enc_flag) / 2):
    if i == 0:
        target = 'A'
    elif i == 1:
        target = 'e'
    elif i == 2:
        target = 'r'
    elif i == 3:
        target = 'o'
    elif i == 4:
        target = '{'
    elif i == len(enc_flag) / 2 - 1:
        target = '}'
    else:
        target = '0123456789abcdef'

    decs = []
    for j in keys:
        if j[0] == i:
            dec = decrypt_char(enc_flag[i*2:i*2+2], j[1])
            if dec in target:
                if dec not in decs:
                    decs.append(dec)

    if i == 0:
        tmps = decs
    else:
        tmps = []
        for flag in flags:
            for dec in decs:
                tmps.append(flag + dec)
    flags = tmps

for flag in flags:
    print flag

実行結果は以下の通り。

Aero{381a95d003649088c8f1abc989ad6fe7}
Aero{381a95d003649088c8f1abc989ab6fe7}
Aero{381a95d003649088c8f1abc189ad6fe7}
Aero{381a95d003649088c8f1abc189ab6fe7}
Aero{381a95d003649088c8f1ebc989ad6fe7}
Aero{381a95d003649088c8f1ebc989ab6fe7}
Aero{381a95d003649088c8f1ebc189ad6fe7}
Aero{381a95d003649088c8f1ebc189ab6fe7}
Aero{381a95d003629088c8f1abc989ad6fe7}
Aero{381a95d003629088c8f1abc989ab6fe7}
Aero{381a95d003629088c8f1abc189ad6fe7}
Aero{381a95d003629088c8f1abc189ab6fe7}
Aero{381a95d003629088c8f1ebc989ad6fe7}
Aero{381a95d003629088c8f1ebc989ab6fe7}
Aero{381a95d003629088c8f1ebc189ad6fe7}
Aero{381a95d003629088c8f1ebc189ab6fe7}★

上記リストの最後でフラグが通った。正当な解き方ではなさそうだが、とりあえず良しとする。

Aero{381a95d003629088c8f1ebc189ab6fe7}

Old Crypto Server (Crypto)

$ nc tasks.aeroctf.com 44323
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 1
Enter cipher key: 1234567890abcdef
Enter data: 111
{+} Encryption result: b'sOLO+RhZo8dU5ZEQ3k4VRw=='
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 2
Enter cipher key: 1234567890abcdef
Enter ciphertext(in base64): sOLO+RhZo8dU5ZEQ3k4VRw==
{+} Decryption result: b'111\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

サーバの処理概要は以下の通り。

■1
・key、dataを指定
・AES-ECB暗号(paddingは\x00埋め)
 dataをkeyで暗号化->base64
■2
・key、dataを指定
・dataをbase64デコード
・AES-ECB復号
 dataをkeyで復号
■3
・saltを指定
・BASE64(AES-ECB暗号(salt + flag + pading))

saltの長さを増やしてみる。

$ nc tasks.aeroctf.com 44323
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 1
{+} Encrypted secret: b'UvDjXUCMhBi1+lUb4ztm3fdWlMqhCNDhedvtq3vOpe5PDtL1hss3iKEPEe2BTAj0'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 11
{+} Encrypted secret: b'3MQm2PkBxtv5fOQov/tLQUUljJ51RIuC0Hbbz7i+BOHqWhABnQ8+UMoU/72NPH30'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 111
{+} Encrypted secret: b'Tx5EDWeNxRM6jddLbB8YJdZCV9VsK/nIc/4xpA73TcWjkimiGfR07YcueoaEO9cS'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 1111
{+} Encrypted secret: b'R4DO+oWKdq48+TwdFx2M+BIL6FnhxLocKq5DSthL5x2v/NLDSfgQZlCpwfSVloHA'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3 
Enter salt: 11111
{+} Encrypted secret: b'zm+seM/Ynu2o4A8mpAW3SPFVOD7LLtu349PRzzO+1JvzosXkIL6Cj+NBVwJva6HY'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 111111
{+} Encrypted secret: b'yOf+91h/YFPH54xM51kFOQznJVJxJd5oY9IYpiPFHEeJYcqveu9bxXSSTQWm83mT'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 1111111
{+} Encrypted secret: b'9QJxrhAJHKN12f8IT5Q73C6NARu5EDjxl8xDOaeG9l9vys+iWg0ivDkU7OhHBcS9'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 11111111
{+} Encrypted secret: b'h/JmgvVhJLjFTkU9pkWCZSJIXlG7kcS8FV4/CbBQvq5IL7xMi8Cr9gk3ZWTjkC5/'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 111111111
{+} Encrypted secret: b'XTAh8QRosBhMmUufQQp0mIE5aIti/o8nF61c+n5zyobk27CjlHerCT4pWBT8ZWrI'
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: 1111111111
{+} Encrypted secret: b'ZqxZgLF/c1ZPz1xlmpq4JCHrjeUZYAVxCpb/Xl83z4jrfI3JbJK08hB3hZ2h117mb1rxX7a53SZS1QVcPNLiZg=='
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit

flagの形式からもその長さは38バイトとわかる。

0123456789abcdef
SSSSSSSSSSFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

0123456789abcdef	0
?PPPPPPPPPPPPPPP0
SSSSSSSSSSSFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FPPPPPPPPPPPPPPP4

0123456789abcdef	1
?FPPPPPPPPPPPPPP0
SSSSSSSSSSSSFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFPPPPPPPPPPPPPP4

0123456789abcdef	2
?FFPPPPPPPPPPPPP0
SSSSSSSSSSSSSFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFPPPPPPPPPPPPP4

0123456789abcdef	3
?FFFPPPPPPPPPPPP0
SSSSSSSSSSSSSSFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFPPPPPPPPPPPP4

0123456789abcdef	4
?FFFFPPPPPPPPPPP0
SSSSSSSSSSSSSSSF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFPPPPPPPPPPP4

0123456789abcdef	5
?FFFFFPPPPPPPPPP0
SSSSSSSSSSSSSSSS
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFPPPPPPPPPP4

0123456789abcdef	6
?FFFFFFPPPPPPPPP0
SSSSSSSSSSSSSSSS
SFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFPPPPPPPPP4

       :

0123456789abcdef	14
?FFFFFFFFFFFFFFP0
SSSSSSSSSSSSSSSS
SSSSSSSSSFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFP4

0123456789abcdef	15
?FFFFFFFFFFFFFFF0
SSSSSSSSSSSSSSSS
SSSSSSSSSSFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF4
PPPPPPPPPPPPPPPP

0123456789abcdef	16
?FFFFFFFFFFFFFFF0
SSSSSSSSSSSSSSSS
SSSSSSSSSSSFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF4
FPPPPPPPPPPPPPPP

フラグを1文字ずつはみ出させ、ブロック単位で暗号化したデータが一致するものを探すことを続けることによってフラグを割り出す。

import socket
from base64 import b64encode, b64decode

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def pad(data):
    return data + '\x00' * (16 - (len(data) % 16))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('tasks.aeroctf.com', 44323))

chars = '0123456789abcdefAro{}'

#flag = ''
flag = '3a76ed3b98bae1e79169b3495f47a}' # <- 接続が不安定だったため、接続が切れたら、そのときまでに分かったフラグを入れながら実行。
for i in range(len(flag), 38):
    for c in chars:
        print '[+] flag =', flag
        if len(flag) < 15:
            salt = pad(c + flag) + 'S' * (i + 11)
        else:
            salt = c + flag[:15] + 'S' * (i + 11)

        data = recvuntil(s, '> ')
        print data + '3'
        s.sendall('3\n')
        data = recvuntil(s, ': ')
        print data + salt
        s.sendall(salt + '\n')
        data = recvuntil(s, '\n').rstrip()
        print data
        enc = b64decode(data.split(' ')[-1][2:-1])
        if enc[16*0:16*1] == enc[16*4:16*5]:
            flag = c + flag
            break

print flag

実行結果は以下の通り。

        :
[+] flag = ero{5013a76ed3b98bae1e79169b3495f47a}
1. Encrypt
2. Decrypt
3. Get server secret
4. Exit
> 3
Enter salt: Aero{5013a76ed3bSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
{+} Encrypted secret: b'KNKValNs6lx7ENPtvxKAPXPUjKD1BluvFjW1DErLsg5z1Iyg9QZbrxY1tQxKy7IOc9SMoPUGW68WNbUMSsuyDijSlWpTbOpcexDT7b8SgD0cmpNN3YyBK6DkPjsKrR5fr/8oWbpTSDk5qHlA69M+Iw=='
Aero{5013a76ed3b98bae1e79169b3495f47a}
Aero{5013a76ed3b98bae1e79169b3495f47a}