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