Great Lakes Security Conference CTF Writeup

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

PYC's? (Reverse Engineering)

pycが添付されているが、フォーマットが違う。添付されているpycをPython 2.7のpycのヘッダと比較し、分析してみる。

正しいPython 2.7のpyc
03 f3 0d 0a

E_NCSA.pyc
79 8a 87 8d

>>> '%x' % (0x03 ^ 0x79)
'7a'
>>> '%x' % (0xf3 ^ 0x8a)
'79'
>>> '%x' % (0x0d ^ 0x87)
'8a'
>>> '%x' % (0x0a ^ 0x8d)
'87'

暗号の前のバイトが復号のXORの鍵になっている。このことを元にpycを復号する。

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

key = 0x7a

dec = ''
for c in enc:
    dec += chr(ord(c) ^ key)
    key = ord(c)

with open('E_NCSA_fix.pyc', 'wb') as f:
    f.write(dec)

復号したpycをデコンパイルする。

$ uncompyle6 E_NCSA_fix.pyc 
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.6.9 (default, Jan 26 2021, 15:33:00) 
# [GCC 8.4.0]
# Embedded file name: NCSA.py
# Compiled at: 2021-02-24 10:40:50
import requests, hashlib, os, struct
from Crypto.Cipher import AES
k = hashlib.sha256(requests.get('http://www.script-o-rama.com/movie_scripts/t/300-script-transcript-sparta.html').text.encode()).hexdigest()[0:32].encode()
iv = hashlib.md5(requests.get('https://subslikescript.com/movie/300_Rise_of_an_Empire-1253863').text.encode()).hexdigest()[0:16].encode()
in_filename = 'flag.zip'
out_filename = 'flag.enc'
chunksize = 65536
encryptor = AES.new(k, AES.MODE_CBC, iv)
filesize = os.path.getsize(in_filename)
c = ''
with open(in_filename, 'rb') as (infile):
    with open(out_filename, 'wb') as (outfile):
        outfile.write(struct.pack('<Q', filesize))
        outfile.write(iv)
        while True:
            chunk = infile.read(chunksize)
            if len(chunk) == 0:
                break
            else:
                if len(chunk) % 16 != 0:
                    chunk += str(' ' * (16 - len(chunk) % 16)).encode()
                out = ''
                for i in range(len(chunk)):
                    c += bytes.fromhex(("b'{:02x}'").format(chunk[i] ^ 19)[2:4][1] + '' + ("b'{:02x}'").format(chunk[i] ^ 19)[2:4][0])

                for i in range(len(c) - 1, -1, -1):
                    out += bytes.fromhex(("b'{:02x}'").format(c[i])[2:4])

            outfile.write(encryptor.encrypt(out))
# okay decompiling E_NCSA_fix.pyc

この暗号の処理は以下の通り。

・k: スクリプトで指定しているURLのページ本体のsha256の16進数の前半32バイト
・iv: スクリプトで指定しているURLのページ本体のmd5の16進数の前半16バイト
・ファイルサイズをリトルエンディアンで8バイト書込み
・ivを書き込み
・入力データ65536バイトごとに以下実行
 ・データサイズが16の倍数でない場合はスペースをパディング
 ・c: 各バイトごとに16進数で19とXORしたものの1バイト目と2バイト目を入れ替える。
 ・バイナリ全体の順序を逆にする。
 ・AES-CBCの暗号化をしてファイルに出力する。

このことから逆算して、復号する。

import requests, hashlib, struct
from Crypto.Cipher import AES

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

k = hashlib.sha256(requests.get('http://www.script-o-rama.com/movie_scripts/t/300-script-transcript-sparta.html').text.encode()).hexdigest()[0:32].encode()
iv = hashlib.md5(requests.get('https://subslikescript.com/movie/300_Rise_of_an_Empire-1253863').text.encode()).hexdigest()[0:16].encode()

filesize = struct.unpack('<Q', enc[:8])[0]
assert iv == enc[8:24]

encryptor = AES.new(k, AES.MODE_CBC, iv)
out = encryptor.decrypt(enc[24:])
c = out[::-1]
chunk = ''
for i in range(len(c)):
    byte = c[i].encode('hex')
    chunk += chr(int(byte[1] + byte[0], 16) ^ 19)

chunk = chunk.rstrip(' ')

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

復号したflag.zipを解凍すると、flag.txtが展開され、フラグが書いてあった。

GLSC{S3m1_Cus70m_3ncryptions}

RansomWary (Crypto)

AES-ECBの暗号なので、16バイト単位で暗号化される。bmpのヘッダだけ正しいものにすれば、なんとなくの画像は復元できるはず。header部分のみ差し替えて、画像にする。

with open('forensics.out', 'r') as f:
    lines = f.read().split('\n')

with open('flag.bmp', 'rb') as f:
    data = f.read()

flag = ''
for line in lines:
    codes = line.split(' ')[1:17]
    for c in codes:
        flag += chr(int(c, 16))

flag += data[len(flag):]

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

f:id:satou-y:20210428071859j:plain

GLSC{3cb_1sn7_53cur3}