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}

WhiteHat Grand Prix 2016 Writeup

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

Banh cuon (Cryptography 200)

RSA暗号の問題。素因数分解した結果が提示されているので、それを元に復号するプログラムを作成する。

import gmpy2
import binascii

e = 65537
p1 = 346284627819235013
p2 = 48108377392359593329
p3 = 91836381613824658927
p4 = 37252538404847211828439
p5 = 27262151738933289423634232512139
p = 1553762687409601943499178289783616523235812050573150316009646301542436391128879624646734300008782361965139513359
q = 18393926378291118190346463738382882261613293132387833409
c = 12682879488984861193795141642779903374526316499885184966492486107034525136565770064564244757954564431956497062324291051476829691654157833815737932841382692373398780347

def factorize(N):
    tmp = N

    primes = [p1, p2, p3, p4, p5, q]
    factors = []
    for p in primes:
        if tmp == 1:
            break
        cnt = gmpy2.mpz(0)
        while tmp % p == 0:
            tmp /= p
            cnt += 1
        if cnt > 0:
            factors.append([gmpy2.mpq(p), cnt])
    if tmp != 1:
        return None
    return factors

def find_phi(N):
    factors = factorize(N)
    if factors is None:
        return None
    phi = gmpy2.mpq(N)
    for f in factors:
        phi = phi * (1 - 1 / f[0])
    return phi

def solve():
    N = gmpy2.mpz(str(p*q))
    C = gmpy2.mpz(str(c))
    E = gmpy2.mpz(str(e))
    
    phi = gmpy2.mpz(find_phi(N))
    if phi is None:
        print 'Failed to factorize N'
        return None

    d = gmpy2.invert(E, phi)
    m = gmpy2.powmod(C, d, N)   
    return binascii.unhexlify(gmpy2.digits(m, 16))
    
def main():
    gmpy2.get_context().precision=1000
    result = solve()
    if result is None:
        print 'Failed!'
        return
    print result
    
if __name__ == "__main__":
    main()

実行すると、以下の通り表示された。
Yup! WhiteHat{ff94658cf7612e7f68de6d960caf6f9b92e8a4a4}!

WhiteHat{ff94658cf7612e7f68de6d960caf6f9b92e8a4a4}

SECCON 2016 Online CTF Writeup

この大会は2016/12/10 15:00(JST)~2016/12/11 15:00(JST)に開催されました。
今回もチームで参戦。結果は2000点で930チーム中37位でした。
自分で解けた問題をWriteupとして書いておきます。

Vigenere (Crypto 100)

Vigenere暗号の問題。平文の最初7文字が"SECCON{"で鍵が12文字あることが分かっている。まず鍵の最初7文字を求める。

al_l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ{}'

c = 'LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ'
p = 'SECCON{'

key = ''

for i in range(len(p)):
    index = al_l.index(c[i]) - al_l.index(p[i])
    if index < 0:
        index += len(al_l)
    key += al_l[index]

print key

実行すると、鍵の最初7文字が"VIGENER"であることがわかる。次の文字は"E"であると想定して、4文字のブルートフォースで問題に記載してあるハッシュ値になるものを探す。

import hashlib
import itertools

al_l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ{}'

c = 'LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ'
k = 'VIGENERE'
k_size = 12

for s in itertools.product(al_l, repeat=4):
    key = k + ''.join(s)
    print key
    p = ''
    for i in range(len(c)):
        index = al_l.index(c[i]) - al_l.index(key[i%k_size])
        if index < 0:
            index += 28
        p += al_l[index]
    if p[-1:] == '}':
        if hashlib.md5(p).hexdigest() == 'f528a6ab914c1ecf856a1d93103948fe':
            print p
            break
SECCON{ABABABCDEDEFGHIJJKLMNOPQRSTTUVWXYYZ}

Juniors CTF 2016 Writeup

この大会は2016/11/26 0:00(JST)~2016/11/28 0:00(JST)に開催されました。
今回もチームで参戦。結果は1400点で666チーム中124位でした。
自分で解けた問題をWriteupとして書いておきます。

Black Suprematic Square (trivial web)

ソースを見ると、表示されない画像を読み込んでいる。

<img src = "http://i.imgur.com/YyHPw50.jpg" width=0>

f:id:satou-y:20161203211029j:plain
この文字列がフラグ。

aima0AiwahsidupaiToehoong1PhieruqueivahphieKah7uceetair9aiGae1eSsaedoo4becooShohhu8eifahXi7EJoh2gaephechei5chiP9

Find The Gobblewonker (trivial network admin web)

OpenVPNで接続する。問題のURLにアクセスすると、フラグが表示されていた。
f:id:satou-y:20161203211423p:plain

q8mvbp8sFNPpFj2dZkEkweNEFdBIuhVYkEr2brnj2qstJ3QbAtklx0imht8pdb2PafDIXs3Sxojl3m5q

Here goes! (trivial crypto recon)

図形の換字式暗号と考え、調べる。
http://yanazlatinneko.tumblr.com/post/119699418139/cadenthegrey-bills-symbol-substitution-cipher で対応表を見つけた。
f:id:satou-y:20161203211615p:plain

FIXPROBLEMQUICKLYWITHGALVANIZEDJETS

Southern Cross (crypto)

ヴィジュネル暗号。http://www.geocachingtoolbox.com/index.php?lang=en&page=vigenereCipherSolveで復号してみる。全文は長すぎて復号できないので、途中まで切って復号する。鍵はBOLIVARであることがわかった。コードを書いて全文を復号してみる。

import string

al_l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
key = 'BOLIVAR'
k_size = len(key)

with open('crypt.txt', 'r') as f:
    data = f.read()

count = 0
plain = ''
for i in range(len(data)):
    enc_c = data[i:i+1]
    if enc_c in string.uppercase:
        key_c = key[count%k_size:count%k_size+1]
        index = al_l.index(enc_c) - al_l.index(key_c)
        if index < 0:
            index += 26
        plain += al_l[index:index+1]
        count += 1
    else:
        plain += enc_c

print plain

復号した結果、ストーリーのタイトルはSelected Storyであるとわかったが、これはフラグではない。問題に最後の部分にフラグがありそうなことが書いてある。・・・ということで最後の文がフラグ。

BOLIVAR CANNOT CARRY DOUBLE

RC3 CTF 2016 Writeup

この大会は2016/11/19 11:00(JST)~2016/11/21 14:00(JST)に開催されました。
今回もチームで参戦。結果は3340点で663チーム中38位でした。
自分で解けた問題をWriteupとして書いておきます。

What's your virus? (Trivia 20)

「once clicked mailing list unbootable virus」で検索したら、次のページに記載されていた。
http://www.hongkiat.com/blog/famous-malicious-computer-viruses/

ILOVEYOU

Horse from Tinbucktu (Trivia 30)

「Trojan horse Windows browser key logging form grabbing」で検索したら、次のページに記載されていた。
http://www.hacking-tutorial.com/hacking-tutorial/remote-administration-tool-zeus-botnet-rat/#sthash.dQ56P6EO.dpbs

Zeus

Salad (Crypto 100)

先頭がRC3に変換されることを想定して。数字含みで20シフトして復号する。

letters = '0123456789abcdefghijklmnopqrstuvwxyz'
length = len(letters)

cipher = '7sj-ighm-742q3w4t'

flag = ''
for i in range(len(cipher)):
    c = cipher[i:i+1]
    if c == '-':
        flag += c
    else:
        index = letters.find(c) + 20
        if index >= length:
            index = index - length
        flag += letters[index:index+1]

print flag.upper()
RC3-2016-ROMANGOD

Who's a good boy (web 100)

読み込んでいるdoge.cssを見ると、一番最後にフラグが書いてある。

.philarydufflebag{
 
/*hiya*/
/*compress your frontend*/
/*here's a flag :)*/
flag:RC3-2016-CanineSS
}
RC3-2016-CanineSS

Some Pang (Forensics 50)

全パケットのdata部の後半24バイトに2バイトずつ同じデータが繰り返されている。送受信で同じデータなので、片方のパケットだけ2バイトずつつなげBase64デコードし、ファイルに保存する。

#!/usr/bin/env python
from scapy.all import *

packets = rdpcap('somepang.pcap')

data = ''
for p in packets:
    if p['IP'].src == '192.168.1.1':
        data += p['Raw'].load[8:10]

with open('flag.jpg', 'wb') as f:
    f.write(data.decode('base64'))

f:id:satou-y:20161201230102j:plain

RC3-2016-PANG-ME-LIKE-ONE-OF-YOUR-FRENCH-GORILLAZ

Graphic Design (Forensics 200)

オブジェクトファイルをテキストエディタで開くと先頭に「Blender v2.78 (sub 0) OBJ File」と書いてある。・・ということでBlenderでこのオブジェクトファイルを開く。内容はステゴザウルスの3D画像。ステゴザウルスを非表示にすると、フラグが隠れていた。
f:id:satou-y:20161201230203p:plain

RC3-2016-St3GG3rz

Dirty Birdy (Forensics 400)

FTK Imagerでイメージファイルを開いてみると、secretfilesというフォルダがあるので、その配下を見てみる。document.txtには「password123」と書かれているので、どこかで使うのかもしれない。Workbook1.xlsx.gpgがあるので、このファイルがあやしい。とりあえずエクスポートする。
さらに探すと、.gnupgフォルダにsecring.gpg(秘密鍵リング)があるので、エクスポートして、先ほどのファイルを復号するのに使う。

$ gpg --import ./secring.gpg
gpg: 鍵8FFDF6D6: 秘密鍵を読み込みました
gpg: /home/yoshinori/.gnupg/trustdb.gpg: 信用データベースができました
gpg: 鍵8FFDF6D6: 公開鍵“ThugG (lolz) <nope@gmail.com>”を読み込みました
gpg: 処理数の合計: 1
gpg:               読込み: 1  (RSA: 1)
gpg:       秘密鍵の読出し: 1
gpg:   秘密鍵の読込み: 1

$ gpg  Workbook1.xlsx.gpg
gpg: 1024-ビットRSA鍵, ID E22CB12D, 日付2016-11-18に暗号化されました
      “ThugG (lolz) <nope@gmail.com>”

これでWorkbook1.xlsxが復号された。このファイルを開こうとすると、パスワードがかかっている。ここで先ほどのパスワードを使って、このファイルを開く。開いたら、Sheet2のフォーカスされているセルに白字でフラグが書いてある。

RC3-2016-SNEAKY21

Qiwi-Infosec CTF-2016 Writeup

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

Reverse 100_2 (Reverse 100)

pycファイルが与えられているので、Easy Python Decompilerでデコンパイルする。

# Embedded file name: task.py
import marshal
src = 'YwAAAAADAAAAGAAAAEMAAABz7wAAAGQBAGoAAGcAAGQCAGQDAGQEAGQFAGQGAGQHAGQIAGQJAGQKAGQLAGQMAGQNAGQOAGQPAGQMAGcPAERdHAB9AAB0AQB0AgB8AACDAQBkEAAXgwEAXgIAcToAgwEAfQEAdAMAZBEAgwEAfQIAfAIAfAEAawIAcuYAZAEAagAAZwAAZBIAZBMAZBQAZBUAZBYAZBcAZBgAZBkAZBoAZBsAZBwAZB0AZB4AZAsAZBwAZB8AZAMAZB0AZAgAZB4AZCAAZCEAZxYARF0VAH0AAHwAAGoEAGQiAIMBAF4CAHHGAIMBAEdIbgUAZCMAR0hkAABTKCQAAABOdAAAAAB0AQAAAF50AQAAADR0AQAAAEt0AQAAAGl0AQAAAC50AQAAAC90AQAAAE50AQAAAGp0AQAAAFB0AQAAAG90AQAAAD90AQAAAGx0AQAAADJ0AQAAAFRpAwAAAHMJAAAAWW91IHBhc3M6dAEAAABzdAEAAAB5dAEAAABudAEAAAB0dAEAAAA6dAEAAAB7dAEAAAB3dAEAAABxdAEAAABFdAEAAAA2dAEAAABmdAEAAABYdAEAAAB1dAEAAABhdAEAAAAxdAEAAAB9dAUAAABST1QxM3MFAAAATm8gOigoBQAAAHQEAAAAam9pbnQDAAAAY2hydAMAAABvcmR0CQAAAHJhd19pbnB1dHQGAAAAZGVjb2RlKAMAAAB0AQAAAGV0AwAAAHRtcHQGAAAAcGFzc3dkKAAAAAAoAAAAAHMHAAAAdGFzay5weVIcAAAAAgAAAHMKAAAAAAFfAQwBDAFvAQ=='.decode('base64')
code = marshal.loads(src)
exec code

pycのヘッダを付けてauth.pycを作成する。

#!/usr/bin/env python
code = 'YwAAAAADAAAAGAAAAEMAAABz7wAAAGQBAGoAAGcAAGQCAGQDAGQEAGQFAGQGAGQHAGQIAGQJAGQKAGQLAGQMAGQNAGQOAGQPAGQMAGcPAERdHAB9AAB0AQB0AgB8AACDAQBkEAAXgwEAXgIAcToAgwEAfQEAdAMAZBEAgwEAfQIAfAIAfAEAawIAcuYAZAEAagAAZwAAZBIAZBMAZBQAZBUAZBYAZBcAZBgAZBkAZBoAZBsAZBwAZB0AZB4AZAsAZBwAZB8AZAMAZB0AZAgAZB4AZCAAZCEAZxYARF0VAH0AAHwAAGoEAGQiAIMBAF4CAHHGAIMBAEdIbgUAZCMAR0hkAABTKCQAAABOdAAAAAB0AQAAAF50AQAAADR0AQAAAEt0AQAAAGl0AQAAAC50AQAAAC90AQAAAE50AQAAAGp0AQAAAFB0AQAAAG90AQAAAD90AQAAAGx0AQAAADJ0AQAAAFRpAwAAAHMJAAAAWW91IHBhc3M6dAEAAABzdAEAAAB5dAEAAABudAEAAAB0dAEAAAA6dAEAAAB7dAEAAAB3dAEAAABxdAEAAABFdAEAAAA2dAEAAABmdAEAAABYdAEAAAB1dAEAAABhdAEAAAAxdAEAAAB9dAUAAABST1QxM3MFAAAATm8gOigoBQAAAHQEAAAAam9pbnQDAAAAY2hydAMAAABvcmR0CQAAAHJhd19pbnB1dHQGAAAAZGVjb2RlKAMAAAB0AQAAAGV0AwAAAHRtcHQGAAAAcGFzc3dkKAAAAAAoAAAAAHMHAAAAdGFzay5weVIcAAAAAgAAAHMKAAAAAAFfAQwBDAFvAQ=='
PYC_HEADER = '\x03\xf3\x0d\x0a\x14\x0c\xf4\x57'

data = code.decode('base64')
data = PYC_HEADER + data

with open('auth.pyc', 'wb') as f:
    f.write(data)

auth.pycをEasy Python Decompilerでデコンパイルする。

# Embedded file name: task.py
tmp = ''.join([ chr(ord(e) + 3) for e in ['^',
 '4',
 'K',
 'i',
 '.',
 '/',
 'N',
 'j',
 'P',
 'o',
 '?',
 'l',
 '2',
 'T',
 '?'] ])
passwd = raw_input('You pass:')
if passwd == tmp:
    print ''.join([ e.decode('ROT13') for e in ['s',
     'y',
     'n',
     't',
     ':',
     '{',
     'w',
     'q',
     'E',
     '6',
     'f',
     'X',
     'u',
     'o',
     'f',
     'a',
     '4',
     'X',
     'N',
     'u',
     '1',
     '}'] ])
else:
    print 'No :('

tmpを表示させ、その値を入力すると、フラグが表示された。

>python auth.py
You pass:a7Nl12QmSrBo5WB
flag:{jdR6sKhbsn4KAh1}
jdR6sKhbsn4KAh1

Reverse 200_1 (Reverse 200)

pyファイルが与えられている、Reverse 100_2 と似たような問題。pycのヘッダを付けてauth.pycを作成する。

#!/usr/bin/env python
code = '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'
PYC_HEADER = '\x03\xf3\x0d\x0a\x14\x0c\xf4\x57'

data = code.decode('base64')
data = PYC_HEADER + data

with open('auth.pyc', 'wb') as f:
    f.write(data)

auth.pycをEasy Python Decompilerでデコンパイルする。

# Embedded file name: task1.py
passwd = raw_input('You pass:')
if ''.join([ chr(ord(e[0]) ^ ord(e[1])) for e in zip('PQ1OJiRWAitBJg5QP2A='.decode('base64'), passwd) ]) == ''.join((e.decode('ROT13') for e in ['t',
 'P',
 'a',
 'B',
 'J',
 'i',
 'b',
 'E',
 'n',
 'W',
 't',
 '6',
 'M',
 'N'])):
    print ''.join([ [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i] + [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i + 4] + [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i + 4 + 4] + [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i + 4 + 4 + 4] + [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i + 4 + 4 + 4 + 4] + [ chr(ord(e) + 30) for e in ''.join([ e.decode('ROT13') for e in ['K',
     'm',
     'L',
     'g',
     'K',
     'G',
     'A',
     'C',
     'A',
     'E',
     'k',
     'U',
     'F',
     'I',
     'E',
     'W',
     'S',
     'H',
     'k',
     'p',
     'D',
     'm',
     'x',
     'x',
     'D',
     '0',
     '4',
     'J',
     'E',
     'F',
     'q',
     'V'] ]).decode('base64')[::-1] ][i + 4 + 4 + 4 + 4 + 4] for i in range(4) ])
else:
    print 'No :('

最初の条件の==を!=にして、適当なpassを入力すると、フラグが表示された。

>python auth.py
You pass:aa
flag:{EazrSKcBjgmT4W3eQ}
EazrSKcBjgmT4W3eQ

Crypto 100_1 (Crypto 100)

数字とアンダースコア(_)のみの暗号。数字2文字で、1つのアルファベットに対応すると考え、コードを書く。

alp = {'11': 'a', '12': 'b', '13': 'c', '14': 'd', '15': 'e',
    '21': 'f', '22': 'g', '23': 'h', '24': 'i', '25': 'k',
    '31': 'l', '32': 'm', '33': 'n', '34': 'o', '35': 'p',
    '41': 'q', '42': 'r', '43': 's', '44': 't', '45': 'u',
    '51': 'v', '52': 'w', '53': 'x', '54': 'y', '55': 'z'
}

with open('code.txt', 'r') as f:
    lines = f.readlines()

for line in lines:
    s = ''
    code = ''
    for i in range(len(line)):
        c = line[i]
        if c == '_':
            s += c
        else:
           code += c
           if len(code) == 2:
               s += alp[code]
               code = ''
    print s

実行すると、以下の文になる。

wake_up_neo
the_matrix_has_you
follow_the_white_qabbit
knock_knock_neo
the_flag_is_the_third_line

follow_the_white_qabbitだと通らないので、最後の単語を意味のある単語に変える。

follow_the_white_rabbit

Crypto 400_1 (Crypto 400)

3組のN,Cが与えられ、eは3...ということでHastad's broadcast attackで攻撃する。

#!/usr/bin/env python3
n1 = 95118357989037539883272168746004652872958890562445814301889866663072352421703264985997800660075311645555799745426868343365321502734736006248007902409628540578635925559742217480797487130202747020211452620743021097565113059392504472785227154824117231077844444672393221838192941390309312484066647007469668558141
n2 = 98364165919251246243846667323542318022804234833677924161175733253689581393607346667895298253718184273532268982060905629399628154981918712070241451494491161470827737146176316011843738943427121602324208773653180782732999422869439588198318422451697920640563880777385577064913983202033744281727004289781821019463
n3 = 68827940939353189613090392226898155021742772897822438483545021944215812146809318686510375724064888705296373853398955093076663323001380047857809774866390083434272781362447147441422207967577323769812896038816586757242130224524828935043187315579523412439309138816335569845470021720847405857361000537204746060031
c1 = 64830446708169012766414587327568812421130434817526089146190136796461298592071238930384707543318390292451118980302805512151790248989622269362958718228298427212630272525186478627299999847489018400624400671876697708952447638990802345587381905407236935494271436960764899006430941507608152322588169896193268212007
c2 = 96907490717344346588432491603722312694208660334282964234487687654593984714144825656198180777872327279250667961465169799267405734431675111035362089729249995027326863099262522421206459400405230377631141132882997336829218810171728925087535674907455584557956801831447125486753515868079342148815961792481779375529
c3 = 43683874913011746530056103145445250281307732634045437486524605104639785469050499171640521477036470750903341523336599602288176611160637522568868391237689241446392699321910723235061180826945464649780373301028139049288881578234840739545000338202917678008269794179100732341269448362920924719338148857398181962112

import functools
import itertools

def chinese_remainder(n, a):
    sum = 0
    prod = functools.reduce(lambda a, b: a*b, n)
    for n_i, a_i in zip(n, a):
        p = prod // n_i
        sum += a_i * mul_inv(p, n_i) * p
    return sum % prod
 
def mul_inv(a, b):
    b0 = b
    x0, x1 = 0, 1
    if b == 1: return 1
    while a > 1:
        q = a // b
        a, b = b, a%b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0: x1 += b0
    return x1

def inv_pow(c, e):
    low = -1
    high = c+1
    while low + 1 < high:
        m = (low + high) // 2
        p = pow(m, e)
        if p < c:
            low = m
        else:
            high = m
    m = high
    assert pow(m, e) == c
    return m
 
N = [n1, n2, n3]
C = [c1, c2, c3]
e = len(N)
a = chinese_remainder(N, C)
for n, c in zip(N, C):
    assert a % n == c
m = inv_pow(a, e)
print(bytes.fromhex(hex(m)[2:]).decode())

実行すると復号される。

theoretical_computer_scientist_johan_torkel_hastad

PPC 100_1(PPC 100)

年月日、曜日がファイル名に含まれているファイルが7937個与えられる。どうやら日付と曜日が合っているものを探せばよいらしい。コードは以下の通り。

import os
import datetime

WEEK_DAYS = ['Monday', 'Tuesday', 'Wednesday',
    'Thursday', 'Friday', 'Saturday', 'Sunday']

files = os.listdir('messages\\')

for file in files:
    str_date = file.split('.')[0]
    dweek = str_date.split('_', 1)[0]
    date = str_date.split('_', 1)[1]
    d = datetime.datetime.strptime(date, '%d_%B_%Y')
    if WEEK_DAYS[d.weekday()] == dweek:
        print file
        with open('messages\\' + file, 'r') as f:
            print f.read()

実行すると該当するファイルが1つだけ見つかり、ファイルの中身がフラグであった。

4eec2cd9e4bb0062d0e41c8af1bd8a0f

Hack The Vote 2016 Writeup

この大会は2016/11/5 8:00(JST)~2016/11/7 8:00(JST)に開催されました。
今回もチームで参戦。結果は601点で1030チーム中116位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity (Vote 1)

問題にフラグが書かれている。

flag{th3r3_1s_0nly_on3_ch0ic3}

Vermatrix Supreme (Crypto 100)

暗号のコードが与えられ、読み解くと処理の概要は以下の通りであることがわかる。

・IV の文字とseedのASCIIコードを連結
・連結した配列の長さが9の倍数になるように0をパディングする。
・9バイトごとに&や|、~の計算をし、その結果とseedが表示される。
・IVを求め、定型フォーマットで指定すれば、フラグが表示される。

IVはXORを繰り返せば、求められる。コードは以下の通り。

#!/usr/bin/env python
import socket
import re

def pad(s):
    if len(s)%9 == 0:
        return s
    for i in xrange((9-(len(s)%9))):
        s.append(0)
    return s

def genBlockMatrix(s):
    outm = [[[7 for x in xrange(3)] for x in xrange(3)] for x in xrange(len(s)/9)]
    for matnum in xrange(0,len(s)/9):
        for y in xrange(0,3):
            for x in xrange(0,3):
                outm[matnum][y][x] = s[(matnum*9)+x+(y*3)]
    return outm

def copyMatrix(m):
    copym = [[0 for i in range(3)] for i in range(3)]
    for y in xrange(3):
        for x in xrange(3):
            copym[y][x] = m[y][x]
    return copym

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('vermatrix.pwn.democrat', 4201))

data = s.recv(256)
print data

pattern = 'SEED: (.+)\n(.+)\n(.+)\n(.+)\n'
m = re.search(pattern, data)
seed = m.group(1)
seed_blocks = genBlockMatrix(pad([ord(c) for c in seed]))

matrixz = []
matrixz.append(map(int, m.group(2).split(' ')))
matrixz.append(map(int, m.group(3).split(' ')))
matrixz.append(map(int, m.group(4).split(' ')))

matrix_tmp = copyMatrix(matrixz)
matrix_tmp2 = [[0 for i in range(3)] for i in range(3)]
for i in range(len(seed_blocks)-1, -1, -1):
    for row in range(3):
        for col in range(3):
           matrix_tmp2[row][col] = matrix_tmp[col][row] ^ seed_blocks[i][col][row]
    matrix_tmp = copyMatrix(matrix_tmp2)

ans = ''
for row in range(3):
    for col in range(3):
        ans += str(matrix_tmp2[row][col])
        ans += ','

ans = ans[:-1]
print ans
s.sendall(ans + '\n')

data = s.recv(256)
print data
flag{IV_wh4t_y0u_DiD_Th3r3}