CTFZone 2018 Quals Writeup

この大会は2018/7/21 18:00(JST)~2018/7/23 6:00(JST)に開催されました。
今回もチームで参戦。結果は875点で637チーム中28位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome to CTFZone!

問題にフラグが書いてある。

ctfzone{2018}

Federation Workflow System (CRYPTO)

$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
list</msg>
lorem.txt,flag.txt,admin.txt,password.txt</msg>

$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
login</msg>
8150411504</msg>

$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file lorem.txt</msg>
N5VO/+EIu1GtkPR8HsxkQ8w/18vV42h0xVeBBL4R9QVTcJhPE9ZrqX23saQBdnKcwHp6fVMag+a+k57SMyVcsmRRmGJwWClSCSOUHh+FHyIiup5mHFUHpn1PsFV53GbesDRKE6NjBGpxKdf0iO7S/9GFJcqa5d4vR65QLG0Nw4YEigM2EgZB9F6s/Rrz9z/0sG6lDLEErtVAjungqmnmcYVfLcZ7TpV8GRXlJppwhwrJKwWSTowJ+68DomDZx3jEAxCmrE547TbL5ZVIpfynSNcVYL1mHI1PgWuM1/Rpojoofae4qviTFaDGtOEOOy+TwMuMR8LuVy2nXuKpPFUiOu9td2WKcQA+vf1zMKJPrbVQfBN1CFbZMX5Bgt6UVLuGAoSQn/t9pYMwQ2ourJV20GzmAOObhyQxe/12dBNaYmHTn4/zDJB35GOE7VKAYESVd5rGEZ25miUMLWQkUr+dYiCE3ylKFjkgGk8PGTDdCgdnytqK34gy8x5Lult3rUwQduHlriOa8UQ0awlVIeB7js5JtfgZ2jfSfSJAnw9xTN1yrnJnPaZr5Ofx+bmiwjS4vuHD/rr6DD1Rk2bp0aVSEji80QQbUjfvZitMqtN6IIYmtrWLnzwWOmFLNsugIojhY3OaXb5TdiJcPg58PI7cBfIJT6uYK3VlwQzdFQQAyZXzPclnq5FLE9FgNzeMIOtDHZbCh8SEFeGn3o80YPScJmWVc9WgysLDvaPHFsXxzEI=</msg>
$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file flag.txt</msg>
a0DGKiriDPsP6ILG+SxK2jhUPjEBcHHSJV7gpqcjiQA=</msg>
$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file admin.txt</msg>
wwXYDdGHxF4hjoO5/jOu62I1bZzDof2NwmkGrXCN8tZISxU9L1JJrvbTswK7LVd8XZvfDrRfF715j8DPDFFdDw==</msg>
$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file password.txt</msg>
26yRuctJGSszUouMVgPy2OyKuRFpaHaYlEvvxpZXO31Cz9KAgtd7whefNgExU2wX</msg>

$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file ../top_secret/server.log</msg>
+s9BHx3ZJ63gULLTNFtpwZyaziwmAjPXHs1ob6x28qVk4PHps81ItKmQ48h1xWJn</msg>
$ nc crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one 7331
file ../top_secret/real.flag</msg>
aU+tYnYcQZ+kVti6l+aCEXfvGsdC4jgVpP1nL065BkXaM6MkmQ7MapG2uwT4+obq</msg>
$ nc crypto-04.v7frkwrfyhsjtb
file ../top_secret/aes.key</msg>
/XXk19YZgWPHK+QCzuyXuDclnD1kINNGDFWrcUNDBcBsm2rInegIuMXvFpJpSjdG</msg>
$ nc crypto-04.v7frkwrfyhsjtb
file totp.secret</msg>
tPT1pw0IliLk+Rg/dlUHoNf/z5M3bfY+uFelVQLzJbo=</msg>

各メニューの機能は以下の通り。

■list
以下を表示
・lorem.txt
・flag.txt
・admin.txt
・password.txt

■login
時刻(エポック秒数)を表示

■file
・content: 指定ファイル読み込み
 ・\x00が含まれている場合はそこまではファイル名とする。
・response: "<ファイル名>: <ファイルの内容>"
・encrypted_response: responseのAES暗号(ECB)

■admin
・check_totpでOKである場合にフラグが表示される。

../totp.secretの内容を復号する。あとはAES-ECBで1文字ずつはみ出させ、ブルートフォースで復号する。さらにサーバ時刻を取得し、その情報を復号データを使って、admin tokenを算出して提示すればフラグが得られる。

import hmac
import socket
from hashlib import sha1
from struct import pack, unpack
from base64 import b64decode
import string

totp_key = '../totp.secret'

block_size = 16
buff_size = 1024

def get_enc(file):
    host = 'crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one'
    port = 7331
    snd_format = 'file %s</msg>'

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))

    snd_data = snd_format % file
    s.sendall(snd_data)
    data = s.recv(buff_size)
    print data
    return data.replace('</msg>', '')

def get_time():
    host = 'crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one'
    port = 7331
    snd_format = 'login</msg>'

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))

    snd_data = snd_format
    s.sendall(snd_data)
    data = s.recv(buff_size)
    print data
    return data.replace('</msg>', '')

def snd_admin_tk(pin):
    host = 'crypto-04.v7frkwrfyhsjtbpfcppnu.ctfz.one'
    port = 7331
    snd_format = 'admin %s</msg>'

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))

    snd_data = snd_format % pin
    s.sendall(snd_data)
    data = s.recv(buff_size)
    print data
    return data.replace('</msg>', '')

key = ''
for i in range(48):
    file = totp_key + '\x00' * (i + 1)
    data = get_enc(file)
    correct_data = b64decode(data)[64:80]

    for c in string.hexdigits:
        try_file = totp_key + '\x00' * 2 + c + key
        print try_file
        pad = block_size - len(try_file) % block_size
        try_file = try_file + (pad * chr(pad))
        try_enc = get_enc(try_file)
        if b64decode(try_enc)[16:32] == correct_data:
            key = c + key
            break

print key

tm = int(get_time())
counter = pack('>Q', tm // 30)
print counter.encode('hex')
totp_hmac = hmac.new(key.encode('UTF-8'), counter, sha1).digest()
offset = ord(totp_hmac[19]) & 15
totp_pin = str((unpack('>I', totp_hmac[offset:offset + 4])[0] & 0x7fffffff) % 1000000)
token = totp_pin.zfill(6)
print token

msg = snd_admin_tk(token)
print msg
ctfzone{A74D92B6E05F4457375AC152286C6F51}