この大会は2016/6/5 0:30(JST)~2016/6/6 0:30(JST)に開催されました。
今回もチームで参戦。結果は990点で429チーム中23位でした。
自分で解けた問題をWriteupとして書いておきます。
Intro BackdoorCTF16 (10)
フラグの前半はIRCの問題になっていて、freenodeで#backdoorctfに入ると、メッセージが流れてきた。
00:41 *topic : This channel is for asking queries about BackdoorCTF 2016. Please send a DM to admins for more specific queries. First half of the flag for challenge INTRO16 is NWQ1bDRiNV9iNG
フラグの後半については、Twitterの@BackdoorCTFアカウントのページに「NrZDAwcmM3ZjIwMTY=」と載っていた。
これを結合してBASE64デコードすると、フラグが取得できる。
5d5l4b5_b4ckd00rc7f2016 →[sha256] 17bf8d359a23b1e126d1e00259cb6ed307dddb50a6c4c7aa123efd09412959d2
Incomplete (600)
以下のPartial Contentのパケットデータをすべてエクスポートし、[No.].binというファイル名で保存する。
No.25, 42, 55, 70, 83, 94, 108, 118, 132, 144, 156
それぞれのパケットデータのContent-Rangeを確認する。
25: 80099-109822 42: 48073-64831 55: 8034-24013 70: 17220-30063 83: 68073-80088 94: 48065-48068 108: 97316-119733 118: 24-28 132: 27220-48063 144: 38-13066 156: 58073-72088
足りない箇所があるが、PNGファイルなので、ファイルフォーマット構造から計算して穴を埋めるようにコードを書く。画像サイズの幅と高さだけは算出できないので、仮置き。
import struct import binascii def crc(trget): crc = binascii.crc32(trget) return struct.pack(">i", crc) # PNG signature SIG = '\x89\x50\x4e\x47\x0d\x0a\x1a\x0a' # IHDR chunk header IHDR_LEN = '\x00\x00\x00\x0d' IHDR_CHUNK = 'IHDR' img_flag = SIG + IHDR_LEN # IHDR Temporary Image Size w = 256 h = 256 ihdr_w = struct.pack(">I", w) ihdr_h = struct.pack(">I", h) with open('118.bin', 'rb') as f: data = f.read() ihdr_body = IHDR_CHUNK + ihdr_w + ihdr_h + data ihdr_crc = crc(ihdr_body) img_flag += ihdr_body + ihdr_crc # IDAT Size IDAT_LEN = '\x00\x00\x3e\x80' IDAT_CHUNK_1 = "I" img_flag += IDAT_LEN + IDAT_CHUNK_1 with open('144.bin', 'rb') as f: data = f.read() img_flag += data with open('55.bin', 'rb') as f: data = f.read() pos_start = 13066 - 8034 + 1 img_flag += data[pos_start:] with open('70.bin', 'rb') as f: data = f.read() pos_start = 24013 - 17220 + 1 img_flag += data[pos_start:] with open('132.bin', 'rb') as f: data = f.read() pos_start = 30063 - 27220 + 1 img_flag += data[pos_start:] with open('94.bin', 'rb') as f: crc_data = f.read() idat_body_pre = data[0x12e9:] for i in range(256): if crc(idat_body_pre + chr(i)) == crc_data: img_flag += chr(i) img_flag += crc_data img_flag += IDAT_LEN with open('42.bin', 'rb') as f: data = f.read() img_flag += data with open('156.bin', 'rb') as f: data156 = f.read() pos_start = 64831 - 58073 + 1 img_flag += data156[pos_start:] with open('83.bin', 'rb') as f: data = f.read() pos_start = 72088 - 68073 + 1 img_flag += data[pos_start:] idat_body = data156[0x177c:] idat_body += data[pos_start:] crc_data = crc(idat_body) img_flag += crc_data IDAT_CHUNK_2 = "ID" img_flag += IDAT_LEN img_flag += IDAT_CHUNK_2 with open('25.bin', 'rb') as f: data = f.read() img_flag += data with open('108.bin', 'rb') as f: data = f.read() pos_start = 109822 - 97316 + 1 img_flag += data[pos_start:] IEND_CRC = '\xae\x42\x60\x82' img_flag += IEND_CRC with open('tmp_flag.png', 'wb') as f: f.write(img_flag)
幅と高さだけが解決できていないので、まずは幅を1024ピクセル以内で総当たりで探す。
import struct import binascii def crc(trget): crc = binascii.crc32(trget) return struct.pack(">i", crc) with open('tmp_flag.png', 'rb') as f: data = f.read() sig = data[0:8] ihdr_len = data[8:12] ihdr_chunk = data[12:16] ihdr_data_part = data[24:29] tail = data[33:] for w in range(1, 1025): print w ihdr_w = struct.pack(">I", w) ihdr_h = struct.pack(">I", 256) ihdr_body = ihdr_chunk + ihdr_w + ihdr_h + ihdr_data_part ihdr_crc = crc(ihdr_body) img_flag = sig + ihdr_len img_flag += ihdr_body + ihdr_crc img_flag += tail filename = 'out\\flag-w_%04d.png' % w with open(filename, 'wb') as f: f.write(img_flag)
幅が667のものが画像として綺麗に表示されていることがわかる。次に高さを調整する。
import struct import binascii def crc(trget): crc = binascii.crc32(trget) return struct.pack(">i", crc) with open('flag-w_0667.png', 'rb') as f: data = f.read() sig = data[0:8] ihdr_len = data[8:12] ihdr_chunk = data[12:16] ihdr_data_part = data[24:29] tail = data[33:] for h in range(1, 1025): print h ihdr_w = struct.pack(">I", 667) ihdr_h = struct.pack(">I", h) ihdr_body = ihdr_chunk + ihdr_w + ihdr_h + ihdr_data_part ihdr_crc = crc(ihdr_body) img_flag = sig + ihdr_len img_flag += ihdr_body + ihdr_crc img_flag += tail filename = 'out2\\flag_%04d.png' % h with open(filename, 'wb') as f: f.write(img_flag)
高さは462で全画像が表示されており、フラグが表示されている。
sha256{G0t_s0m3_r4r3_th1ng5_0n_5al3_5tr4ng3r!} →[sha256] 17bf8d359a23b1e126d1e00259cb6ed307dddb50a6c4c7aa123efd09412959d2
Lossless (100)
オリジナルと暗号の画像が与えられている。まずはピクセル単位の差分の有無を見てみる。
from PIL import Image o_img = Image.open('original.png').convert('RGB') e_img = Image.open('encrypted.png').convert('RGB') w, h = o_img.size output_img = Image.new('RGB', (w, h), (255, 255, 255)) for y in range(h): for x in range(w): r1, g1, b1 = o_img.getpixel((x, y)) r2, g2, b2 = e_img.getpixel((x, y)) if r1 == r2 and g1 == g2 and b1 == b2: output_img.putpixel((x, y), (0, 0, 0)) else: output_img.putpixel((x, y), (255, 255, 255)) output_img.save('diff.png')
49×7の範囲しか差のある部分がなく、その中でも差の有無が分かれる。1列ごとに差の有無で1,0を割り当て、ASCIIコードとして読み取る。
from PIL import Image o_img = Image.open('original.png').convert('RGB') e_img = Image.open('encrypted.png').convert('RGB') flag = '' for x in range(49): bin_code = '' for y in range(7): r1, g1, b1 = o_img.getpixel((x, y)) r2, g2, b2 = e_img.getpixel((x, y)) if r1 == r2 and g1 == g2 and b1 == b2: bin_code += '0' else: bin_code += '1' flag += chr(int(bin_code, 2)) print flag
実行するとフラグが表示された。
The flag is SHA256{d1ff1cul7_t0_f0cu5,wa5n't_i7?} →[sha256] bdfa42fd05bfa808e9bcb63786ed3983353ac7f6874c37046722ab5944c79f0d