DamCTF 2020 Writeup

この大会は2020/10/10 9:00(JST)~2020/10/12 9:00(JST)に開催されました。
今回もチームで参戦。結果は1114点で700チーム中158位でした。
自分で解けた問題をWriteupとして書いておきます。

rules (misc)

ルールのページにサンプルのフラグが書いてあった。

dam{rul3s_ar3_t00_c00l_f0r_sk00l}

finger-warmup (beginner) (web)

リンク先をアクセスすると、さらに別のリンク先の付いたページになる。スクリプトで、繰り返しリンク先を追っていく。

import requests
import re

url = 'https://finger-warmup.chals.damctf.xyz/'
path = ''

i = 1
while True:
    print 'Round %d' % i
    i += 1

    print url + path
    r = requests.get(url + path)
    body = r.text
    print body
    if 'dam{' in body:
        break

    pattern = 'href=\"(.+)\">'
    m = re.search(pattern, body)
    path = m.group(1)

実行結果は以下の通り。

          :
Round 1000
https://finger-warmup.chals.damctf.xyz/310491iil95gv69b6qpjp
Nice clicking, I'm very impressed! Now to go onwards and upwards! <br/><pre>dam{I_hope_you_did_this_manually}</pre>
dam{I_hope_you_did_this_manually}

phase1 (forensics) (malware)

smtpでフィルタリングすると、あやしいファイルが転送されていることがわかる。該当するデータをすべてエクスポートする。エクスポートしたファイルを結合し、sha256の値を確認する。

$ cat *.bin > mal.elf
$ sha256sum mal.elf
fc5333c5d1de963b52e57c512e10cf0e37153ac917b7040cf5f95a628a601b10  mal.elf
dam{fc5333c5d1de963b52e57c512e10cf0e37153ac917b7040cf5f95a628a601b10}

cbc-padding-oracle (crypto)

$ nc chals.damctf.xyz 30327
PADDING ORACLE MENU
1. encrypt
2. decrypt
your input?
1
Give me your data, I will encrypt your data like this:
 AES_CBC_256('A'*64 + [flag] + [your_data])
Length of the input (size >= 32, multiple of 16)?
32
Please give me your input!
11111111111111111111111111111111
Here's the encrypted data, length 128
b'\x1dW*~\x1f\xc1T\x80\xaa\xd9\x8d!\x98\x1e\x97\xd9j\xd4y\xda(\xbb\xb0\xd1\x17\x04\t\xd4\xd0\x1e\xb8\xda\x1b\x9d\xd7N\r_\x80\x1d:\xcc\xef\x9bI\xb7dr\x82\xe1\x84\x81\xe6\xa7/K\x0cFvl\xdf\xe9\x8b2\x01~\x1cX}\xa3\xc2H"?\xacj\xda\xf5i\xca\xf6\xab\xae\x1d\x11\x8b\xd5\xa8\xa6\xa2F\xc5o\xf7=S?Hd\xb7b=\xf5\xc5\xbeA\xa15D\xd2*\xbb\xf7\x0f\xa3w\xe8?\xad\x9a\xc5P\xb1Fx\x92<\xcd'

サーバの暗号化処理は以下の通り。

■encrypt
・平文のサイズを指定する(32以上、16の倍数)
・平文を指定する
・平文をpaddingする。
・'A'*64 + [flag] + [指定文字列]をAES-CBC暗号化
・iv + 暗号化データを返す(ivは毎回生成)

■decrypt
・暗号文のサイズを指定する(32以上、16の倍数)
・暗号文を指定する
・AES-CBCで復号(パディングのチェックあり)

CBC Padding Oracle Atackで復号できる。
なぜかencryptとdecryptを同じセッションでできないので、まず適当なデータを指定し、ファイルに保存する。

import socket

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chals.damctf.xyz', 30327))

data = recvuntil(s, '?\n').rstrip()
print data
print '1'
s.sendall('1\n')
data = recvuntil(s, '?\n').rstrip()
print data
print '32'
s.sendall('32\n')
data = recvuntil(s, '!\n').rstrip()
print data
try_pt = '1' * 32
print try_pt
s.sendall(try_pt + '\n')
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data
enc = eval(data)

with open('msg.enc', 'wb') as f:
    f.write(enc)

次に復号で、CBC Padding Oracle Attackを実行する。末尾が指定データで、途中でパディングが2バイトがわかたため、フラグが入っているブロックのみ実行する。

import socket

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

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def unpad(s):
    return s[:-ord(s[-1])]

def is_valid(enc):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('chals.damctf.xyz', 30327))

    data = recvuntil(s, '?\n').rstrip()
    print data
    print '2'
    s.sendall('2\n')
    data = recvuntil(s, '?\n').rstrip()
    print data
    print '32'
    s.sendall('32\n')
    data = recvuntil(s, '!\n').rstrip()
    print data
    print enc
    s.sendall(enc + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    if 'ERROR' in data:
        return False
    else:
        return True

with open('msg.enc', 'rb') as f:
    enc = f.read()

enc_blocks = [enc[i:i+16] for i in range(0, len(enc), 16)]
print len(enc_blocks)

tail_pt_blocks = ['1' * 16, '1' * 14 + '\x02' * 2]

xor_blocks = []
for i in range(2):
    xor_blocks.append(str_xor(enc_blocks[-i-2], tail_pt_blocks[-i-1]))

for i in range(len(enc_blocks)-3, 4, -1):
    xor_block = ''
    for j in range(16):
        for code in range(256):
            print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex'))
            print '****', str_xor(xor_block, enc_blocks[i-1][-j:]), '****'
            try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j)
            try_cipher = try_pre_block + enc_blocks[i]
            if is_valid(try_cipher):
                xor_code = (j+1) ^ code
                xor_block = chr(xor_code) + xor_block
                break

    xor_blocks.append(xor_block)

xor_blocks.reverse()

msg = ''
for i in range(len(xor_blocks)):
    msg += str_xor(enc_blocks[i+4], xor_blocks[i])

msg = unpad(msg)
print msg

実行結果は以下の通り。

         :
5 - 15 - 209: 0fb6e00c116b4aa85e53cf11e7b48e
**** am{_Or4Cl3_}
11 ****
PADDING ORACLE MENU
1. encrypt
2. decrypt
your input?
2
Length of the input (size >= 32, multiple of 16)?
32
Please give me your input!
ムヲ・{ZクNC゚槫ョ/ソ詭セチ02ルSュD
Your input has been successfully decrypted in length 0
dam{_Or4Cl3_}
11111111111111111111111111111111
dam{_Or4Cl3_}