この大会は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)
GLSC{3cb_1sn7_53cur3}