Hack.lu CTF 2016 Writeup

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

cryptolocker (Crypto 200)

問題はODTファイルがランサムウェアで暗号化されてしまったので、それを復号することのようだ。暗号処理のコードが与えられているので、コードを読んでみる。
入力パスワードは8バイト。パスワードを2バイトずつ区切ってsha256を算出し、AES暗号の鍵4つを決定する。その鍵を使って、平文を4回暗号化する処理になっている。
そこで復号データを確認し、パディング文字とその数が合っているものを総当たりで探す。その際、暗号データは必ず32の倍数のサイズになり、IV(16バイト)を先頭に付けているので、パディング長は16になり、最後の平文に復号するときは、ODTの最初の2バイトはPKとなることをチェックする。コードは以下の通り。

import hashlib
from Crypto.Cipher import AES

def decrypt(key, enc):
    iv = enc[:AES.block_size]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return cipher.decrypt(enc[AES.block_size:])

def _unpad(s):
    return s[:-ord(s[len(s)-1:])]

def chk_odt(s):
    if s[0:2] != 'PK':
        return False
    return True

def chk_pad(s):
    num = ord(s[-1:])
    if num != 16:
        return False
    for i in range(num - 1):
        if s[-i-2:-i-1] != chr(num):
            return False
    return True

with open('flag.encrypted', 'rb') as f:
    ciphertext = f.read()

found = 0
for i in range(256):
    for j in range(256):
        key_str = chr(i) + chr(j)
        key = hashlib.sha256(key_str).digest()
        dec = decrypt(key, ciphertext)
        if chk_pad(dec):
            three = _unpad(dec)
            found = 1
            break
    if found == 1:
        break

found = 0
for i in range(256):
    for j in range(256):
        key_str = chr(i) + chr(j)
        key = hashlib.sha256(key_str).digest()
        dec = decrypt(key, three)
        if chk_pad(dec):
            two = _unpad(dec)
            found = 1
            break
    if found == 1:
        break

found = 0
for i in range(256):
    for j in range(256):
        key_str = chr(i) + chr(j)
        key = hashlib.sha256(key_str).digest()
        dec = decrypt(key, two)
        if chk_pad(dec):
            one = _unpad(dec)
            found = 1
            break
    if found == 1:
        break

found = 0
for i in range(256):
    for j in range(256):
        key_str = chr(i) + chr(j)
        key = hashlib.sha256(key_str).digest()
        dec = decrypt(key, one)
        if chk_odt(dec):
            plaintext = _unpad(dec)
            found = 1
            break
    if found == 1:
        break

with open('flag', 'wb') as f:
    f.write(plaintext)

上記コードを実行し、復号したデータをflagファイルに保存する。

$ file flag
flag: OpenDocument Text

Wordでこのファイルを開くと、フラグが書かれていることがわかる。

flag{v3ry_b4d_crypt0_l0ck3r}