WPICTF 2019 Writeup

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

can you read (Intro 1)

問題にフラグが書いてあった。

WPI{y3s_y0u_cAN_r33d}

Discord (Intro 5)

Discordに入ってピン止めされたメッセージを見る。その中にフラグがあった。

WPI{Welcome_to_our_discord}

WebInspect (Web 25)

HTMLソースのコメントにフラグがあった。

WPI{Inspect0r_Gadget}

strings (Reversing 50)

$ strings strings | grep WPI{
WPI{
warbleglarblesomejunkWPI{What_do_you_mean_I_SEE_AHH_SKI}0x13376969
WPI{What_do_you_mean_I_SEE_AHH_SKI}

zoomercrypt (Crypto 50)

顔文字をUnicodeに置き換える。

U+1F603
U+1F601
U+1F615

U+1F617
U+1F608

U+1F617
U+1F607

U+1F60B
U+1F604
U+1F617
{
U+1F606
U+1F613
U+1F604
U+1F613
U+1F602
U+1F608
_
U+1F60E
U+1F603
U+1F603
U+1F601
U+1F613
U+1F606
U+1F607
}

フラグの部分が"WPI{"になるようにコードを変換して復号する。

unicodes = [0x1F603, 0x1F601, 0x1F615, 0, 0x1F617, 0x1F608, 0, 0x1F617, 
    0x1F607, 0, 0x1F60B, 0x1F604, 0x1F617, 1, 0x1F606, 0x1F613, 0x1F604, 
    0x1F613,0x1F602, 0x1F608, 2, 0x1F60E, 0x1F603, 0x1F603, 0x1F601,
    0x1F613, 0x1F606, 0x1F607, 3]
unicode_base1 = 0x1F5B4
unicode_base2 = 0x1F5CE

msg = ''
for unicode in unicodes:
    if unicode == 0:
        msg += ' '
    elif unicode == 1:
        msg += '{'
    elif unicode == 2:
        msg += '_'
    elif unicode == 3:
        msg += '}'
    elif unicode < 0x1F610:
        code = unicode - unicode_base1
        msg += chr(code)
    else:
        code = unicode - unicode_base2
        msg += chr(code)

print msg

実行結果は以下の通り。

OMG IT IS WPI{REPENT_ZOOMERS}
WPI{REPENT_ZOOMERS}

jocipher (Crypto 100)

Easy Python Decompilerでデコンパイルする。

# Embedded file name: ./jocipher.py
import argparse, re
num = ''
first = ''
second = ''
third = ''

def setup():
    global third
    global second
    global num
    global first
    num += '1'
    num += '2'
    num += '3'
    num += '4'
    num += '5'
    num += '6'
    num += '7'
    num += '8'
    num += '9'
    num += '0'
    first += 'q'
    first += 'w'
    first += 'e'
    first += 'r'
    first += 't'
    first += 'y'
    first += 'u'
    first += 'i'
    first += 'o'
    first += 'p'
    second += 'a'
    second += 's'
    second += 'd'
    second += 'f'
    second += 'g'
    second += 'h'
    second += 'j'
    second += 'k'
    second += 'l'
    third += 'z'
    third += 'x'
    third += 'c'
    third += 'v'
    third += 'b'
    third += 'n'
    third += 'm'


def encode(string, shift):
    result = ''
    for i in range(len(string)):
        char = string.lower()[i]
        if char in num:
            new_char = num[(num.index(char) + shift) % len(num)]
            result += new_char
        elif char in first:
            new_char = first[(first.index(char) + shift) % len(first)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in second:
            new_char = second[(second.index(char) + shift) % len(second)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in third:
            new_char = third[(third.index(char) + shift) % len(third)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        else:
            result += char

    print result
    return 0


def decode(string, shift):
    result = ''
    shift = -1 * shift
    for i in range(len(string)):
        char = string.lower()[i]
        if char in num:
            new_char = num[(num.index(char) + shift) % len(num)]
            result += new_char
        elif char in first:
            new_char = first[(first.index(char) + shift) % len(first)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in second:
            new_char = second[(second.index(char) + shift) % len(second)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in third:
            new_char = third[(third.index(char) + shift) % len(third)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        else:
            result += char

    print result
    return 0


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--string', '-s', type=str, required=True, help='the string to encode or decode')
    parser.add_argument('--shift', '-t', type=int, required=True, help='the shift value to use')
    parser.add_argument('--encode', '-e', required=False, action='store_true', help='encode the string')
    parser.add_argument('--decode', '-d', required=False, action='store_true', help='decode the string')
    args = parser.parse_args()
    setup()
    p = re.compile('[a-zA-Z0-9\\-{}]')
    if p.match(args.string) is not None:
        if args.encode:
            ret = encode(args.string, args.shift)
        elif args.decode:
            ret = decode(args.string, args.shift)
        if ret is not 0:
            print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]'
    else:
        print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]'
    return


if __name__ == '__main__':
    main()

デコードする処理もあるので、そのまま使う。num, first, second, thirdでそれぞれシフトするのだが、構成する数が異なり、10, 10, 9, 7になっている。シフト数は最小公倍数の630回までパターンがあるので、全候補を確認する。

num = ''
first = ''
second = ''
third = ''

def setup():
    global third
    global second
    global num
    global first
    num += '1'
    num += '2'
    num += '3'
    num += '4'
    num += '5'
    num += '6'
    num += '7'
    num += '8'
    num += '9'
    num += '0'
    first += 'q'
    first += 'w'
    first += 'e'
    first += 'r'
    first += 't'
    first += 'y'
    first += 'u'
    first += 'i'
    first += 'o'
    first += 'p'
    second += 'a'
    second += 's'
    second += 'd'
    second += 'f'
    second += 'g'
    second += 'h'
    second += 'j'
    second += 'k'
    second += 'l'
    third += 'z'
    third += 'x'
    third += 'c'
    third += 'v'
    third += 'b'
    third += 'n'
    third += 'm'

def decode(string, shift):
    result = ''
    shift = -1 * shift
    for i in range(len(string)):
        char = string.lower()[i]
        if char in num:
            new_char = num[(num.index(char) + shift) % len(num)]
            result += new_char
        elif char in first:
            new_char = first[(first.index(char) + shift) % len(first)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in second:
            new_char = second[(second.index(char) + shift) % len(second)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in third:
            new_char = third[(third.index(char) + shift) % len(third)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        else:
            result += char

    return result

enc = 'PIY{zsxh-sqrvufwh-nfgl}'

setup()

for i in range(630):
    flag = decode(enc, i)
    if flag.startswith('WPI{'):
        print flag

実行結果は以下の通りで、意味の通りそうなものがフラグとなる。

WPI{mdzj-deycogrj-bgha}
WPI{vsbh-seymofrh-xfgl}
WPI{zaxg-aeyvodrg-ndfk}
WPI{blnf-leyzosrf-csdj}
WPI{xkcd-keyboard-mash}★
WPI{njms-jeyxolrs-vlag}
WPI{chva-heynokra-zklf}
WPI{mgzl-geycojrl-bjkd}
WPI{vfbk-feymohrk-xhjs}
WPI{zdxj-deyvogrj-ngha}
WPI{bsnh-seyzofrh-cfgl}
WPI{xacg-aeybodrg-mdfk}
WPI{nlmf-leyxosrf-vsdj}
WPI{ckvd-keynoard-zash}
WPI{mjzs-jeycolrs-blag}
WPI{vhba-heymokra-xklf}
WPI{zgxl-geyvojrl-njkd}
WPI{bfnk-feyzohrk-chjs}
WPI{xdcj-deybogrj-mgha}
WPI{nsmh-seyxofrh-vfgl}
WPI{cavg-aeynodrg-zdfk}
WPI{mlzf-leycosrf-bsdj}
WPI{vkbd-keymoard-xash}
WPI{zjxs-jeyvolrs-nlag}
WPI{bhna-heyzokra-cklf}
WPI{xgcl-geybojrl-mjkd}
WPI{nfmk-feyxohrk-vhjs}
WPI{cdvj-deynogrj-zgha}
WPI{mszh-seycofrh-bfgl}
WPI{vabg-aeymodrg-xdfk}
WPI{zlxf-leyvosrf-nsdj}
WPI{bknd-keyzoard-cash}
WPI{xjcs-jeybolrs-mlag}
WPI{nhma-heyxokra-vklf}
WPI{cgvl-geynojrl-zjkd}
WPI{mfzk-feycohrk-bhjs}
WPI{vdbj-deymogrj-xgha}
WPI{zsxh-seyvofrh-nfgl}
WPI{bang-aeyzodrg-cdfk}
WPI{xlcf-leybosrf-msdj}
WPI{nkmd-keyxoard-vash}
WPI{cjvs-jeynolrs-zlag}
WPI{mhza-heycokra-bklf}
WPI{vgbl-geymojrl-xjkd}
WPI{zfxk-feyvohrk-nhjs}
WPI{bdnj-deyzogrj-cgha}
WPI{xsch-seybofrh-mfgl}
WPI{namg-aeyxodrg-vdfk}
WPI{clvf-leynosrf-zsdj}
WPI{mkzd-keycoard-bash}
WPI{vjbs-jeymolrs-xlag}
WPI{zhxa-heyvokra-nklf}
WPI{bgnl-geyzojrl-cjkd}
WPI{xfck-feybohrk-mhjs}
WPI{ndmj-deyxogrj-vgha}
WPI{csvh-seynofrh-zfgl}
WPI{mazg-aeycodrg-bdfk}
WPI{vlbf-leymosrf-xsdj}
WPI{zkxd-keyvoard-nash}
WPI{bjns-jeyzolrs-clag}
WPI{xhca-heybokra-mklf}
WPI{ngml-geyxojrl-vjkd}
WPI{cfvk-feynohrk-zhjs}
WPI{xkcd-keyboard-mash}