BackdoorCTF 2016 Writeup

この大会は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=」と載っていた。
f:id:satou-y:20160606220343p:plain
これを結合して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で全画像が表示されており、フラグが表示されている。
f:id:satou-y:20160607225949p:plain

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')

f:id:satou-y:20160606231731p:plain
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