SharifCTF 7 Writeup

この大会は2016/12/16 23:30(JST)~2016/12/18 23:30(JST)に開催されました。
今回もチームで参戦。結果は1501点で699チーム中53位でした。
自分で解けた問題をWriteupとして書いておきます。

SharifCTF (Web 1)

ルールのページのアンダーラインのフレーズのmd5がフラグ。

$ echo -n 'Unless stated otherwise' | md5sum
09de4dbead25ee97d40a56938fd17568  -
SharifCTF{09de4dbead25ee97d40a56938fd17568}

Pretty Raw (Forensics 180)

よくわからないバイナリファイルだが、よく見ると途中からPNGファイルが入っている感じがする。
foremostで抽出してみる。

$ foremost pretty_raw
Processing: pretty_raw
|*|

やはりPNGファイルが抽出できた。
f:id:satou-y:20161230223024p:plain
与えられたバイナリの前半部分(PNGより前の部分)も意味がありそうと考え、\x00以外の場合に何か文字を入れ、1570×74に整形する。

WIDTH = 1571
HEIGHT = 74

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

output = ''
for i in range(WIDTH * HEIGHT):
    if data[i] == '\x00':
        output += ' '
    else:
        output += 'M'
    if i % WIDTH == WIDTH - 1:
        output += '\n'

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

ASCII文字のようにフラグが表示された。

SharifCTF{100ae53903cbb68ab523c8e858034988}

f:id:satou-y:20161230223323p:plain

Pretty Slim (Forensics 220)

与えられたファイルは壊れているような感じ。先頭0x51を0x50にして、ZIP repaireで復元し、解凍するとflagggggファイルが抽出できる。
中身を見ると、KGB Archiveのようだが、やはり壊れているようなので、先頭を変更してみる。

4B 47 42 20 69 6E 20 4B 72 65 6D 6C 69 6E 32 38
            ↓
4B 47 42 5F 61 72 63 68 20 2D 33 0D 0A 33 32 38

後ろ3バイトはサイズを表しているので、試行錯誤しながら調整した。
KGB Archiverで解凍すると、PNGファイルが取り出せる。
f:id:satou-y:20161230223858p:plain
このQRコード画像を読み取ると、フラグが出てきた。

SharifCTF{77245d65f9e5849b8355960947517625}

Playfake (Misc 50)

暗号化のスクリプトと暗号文が問題で提示されている。条件に合うよう、英文として読める平文に復号するという問題である。
スクリプトから平文、鍵の条件は、以下の通りとわかる。
・key(鍵): 長さ5 英大文字
・msg(平文): 英大小文字またはスペースでSharifCTF、contestが含まれている。
暗号はPlayfair暗号の変形で、違う平文でも同じ暗号になることがある。まずは、鍵をブルートフォースで見つける。

import itertools
import string

LIN = 'B'
LOUT = 'P'

msg01 = 'SharifCT'
msg02 = 'harifCTF'
msg11 = 'contes'
msg12 = 'ontest'

ENC = 'KPDPDGYJXNUSOIGOJDUSUQGFSHJUGIEAXJZUQVDKSCMQKXIR'

def make_key(key_str):
    key_str += string.ascii_uppercase
    key_str = key_str.replace(' ', '').upper().replace(LIN, LOUT)

    seen = set()
    seen_add = seen.add
    return [x for x in key_str if not (x in seen or seen_add(x))]

def get_pos(key, letter):
    i = key.index(letter)
    return (i//5, i%5)

def get_letter(key, i, j):
    i %= 5
    j %= 5
    return key[5*i + j]

def make_message(msg):
    msg = msg.replace(' ', '').upper().replace(LIN, LOUT)
    outp = ''
    i = 0
    while True:
        if i+1 >= len(msg):
            if i == len(msg)-1:
                outp += msg[i]
            break
        if msg[i] == msg[i+1]:
            outp += msg[i] + 'Y'
            i += 1
        else:
            outp += msg[i] + msg[i+1]
            i += 2
    if len(outp) % 2 == 1:
        outp += 'Y'
    return outp

def playfair_enc(key, msg):
    assert len(msg) % 2 == 0
    assert len(key) == 25
    ctxt = ''
    for i in range(0, len(msg), 2):
        r0, c0 = get_pos(key, msg[i])
        r1, c1 = get_pos(key, msg[i+1])
        if r0 == r1:
            ctxt += get_letter(key, r0+1, c0+1) + get_letter(key, r1+1, c1+1)
        elif c0 == c1:
            ctxt += get_letter(key, r0-1, c0-1) + get_letter(key, r1-1, c1-1)
        else:
            ctxt += get_letter(key, r0+1, c1-1) + get_letter(key, r1+1, c0-1)
    return ctxt

for s in itertools.product(string.ascii_uppercase, repeat=5):
    k = ''.join(s)
    key = make_key(k)
    msg01_2 = make_message(msg01)
    ctxt01 = playfair_enc(key, msg01_2)
    msg02_2 = make_message(msg02)
    ctxt02 = playfair_enc(key, msg02_2)
    msg11_2 = make_message(msg11)
    ctxt11 = playfair_enc(key, msg11_2)
    msg12_2 = make_message(msg12)
    ctxt12 = playfair_enc(key, msg12_2)
    if (ctxt01 in ENC or ctxt02 in ENC) and (ctxt11 in ENC or ctxt12 in ENC):
        print 'Found key : ' + k

実行すると以下の表示になり、可能性のある鍵がわかる。

Found key : BROVN
Found key : BROWN
Found key : PROVN
Found key : PROWN
Found key : VROBN
Found key : VROPN
Found key : WROBN
Found key : WROPN

2文字一組で暗号の2文字が決まることから、それぞれの鍵の場合にブルートフォースで平文に復号する。

import itertools
import string

LIN = 'B'
LOUT = 'P'

ENC = 'KPDPDGYJXNUSOIGOJDUSUQGFSHJUGIEAXJZUQVDKSCMQKXIR'

k = ['BROVN', 'BROWN', 'PROVN', 'PROWN', 'VROBN', 'VROPN', 'WROBN', 'WROPN']

def make_key(key_str):
    key_str += string.ascii_uppercase
    key_str = key_str.replace(' ', '').upper().replace(LIN, LOUT)

    seen = set()
    seen_add = seen.add
    return [x for x in key_str if not (x in seen or seen_add(x))]

def get_pos(key, letter):
    i = key.index(letter)
    return (i//5, i%5)

def get_letter(key, i, j):
    i %= 5
    j %= 5
    return key[5*i + j]

def make_message(msg):
    msg = msg.replace(' ', '').upper().replace(LIN, LOUT)
    outp = ''
    i = 0
    while True:
        if i+1 >= len(msg):
            if i == len(msg)-1:
                outp += msg[i]
            break
        if msg[i] == msg[i+1]:
            outp += msg[i] + 'Y'
            i += 1
        else:
            outp += msg[i] + msg[i+1]
            i += 2
    if len(outp) % 2 == 1:
        outp += 'Y'
    return outp

def playfair_enc(key, msg):
    assert len(msg) % 2 == 0
    assert len(key) == 25
    ctxt = ''
    for i in range(0, len(msg), 2):
        r0, c0 = get_pos(key, msg[i])
        r1, c1 = get_pos(key, msg[i+1])
        if r0 == r1:
            ctxt += get_letter(key, r0+1, c0+1) + get_letter(key, r1+1, c1+1)
        elif c0 == c1:
            ctxt += get_letter(key, r0-1, c0-1) + get_letter(key, r1-1, c1-1)
        else:
            ctxt += get_letter(key, r0+1, c1-1) + get_letter(key, r1+1, c0-1)
    return ctxt

for i in range(len(k)):
    print 'Key: %s' % k[i]
    key = make_key(k[i])
    plain = ''
    for j in range(0, len(ENC), 2):
        for s in itertools.product(string.ascii_uppercase, repeat=2):
            tmp_plain = ''.join(s)
            tmp_msg = make_message(tmp_plain)
            ctxt = playfair_enc(key, tmp_msg)
            if ctxt == ENC[j:j+2]:
                plain += tmp_plain
                break
    print plain

実行すると、それぞれの鍵の場合の復号結果が得られる。

Key: BROVN
CURYRENTLYTHESEWENTHSHARIFCTFCONTESTKYBEINGHELDY
Key: BROWN
CURYRENTLYTHESEVENTHSHARIFCTFCONTESTISBEINGHELDY
Key: PROVN
CURYRENTLYTHESEWENTHSHARIFCTFCONTESTKYBEINGHELDY
Key: PROWN
CURYRENTLYTHESEVENTHSHARIFCTFCONTESTISBEINGHELDY
Key: VROBN
FUNYRENTLYTHESEWENTHSHARIFCTFCONTESTHYVEINGHELDY
Key: VROPN
FUNYRENTLYTHESEWENTHSHARIFCTFCONTESTHYVEINGHELDY
Key: WROBN
FUNYRENTLYTHESEVENTHSHARIFCTFCONTESTISWEINGHELDY
Key: WROPN
FUNYRENTLYTHESEVENTHSHARIFCTFCONTESTISWEINGHELDY

この中からYと取り除いて、英文になりそうなものを探す。
鍵がBROWNの場合の平文にYを取り除き、スペースを入れ、英文にする。このとき 大文字、小文字は識別しない。

CURRENTLY THE SEVENTH SharifCTF contest IS BEING HELD

md5のハッシュを計算したものがフラグ。

from hashlib import md5

def make_flag(msg):
    return 'SharifCTF{%s}' % md5(msg.replace(' ', '').upper().encode('ASCII')).hexdigest()

print make_flag('CURRENTLY THE SEVENTH SharifCTF contest IS BEING HELD')
SharifCTF{655ad15484a60457f3af49512a5d5206}