この大会は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ファイルが抽出できた。
与えられたバイナリの前半部分(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}
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ファイルが取り出せる。
この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}