3DSCTF Writeup

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

Hot Sun? (CRYPTO 100)

シーザー暗号。http://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipher
でROT14にして復号する。

3DS{th1s_r0t_1s_n0t_s0_H0t}

Results does not matter!!! (PROG 100)

演算の図が与えられているので、FLAGの値を求める。逆算すればよい。

0b10101111101(= 334453 ^ 0b1010001111100001000) :上側のXOR演算
0b10100111001(= 1337) :最終結果

上側のXOR演算とANDで最終結果になるものは次の値。

0b1?1?01110?1(*1)

0b00100111110(= 318) とXORして(*1)になるものは次の値。

0b1?0?00001?1(*2)

0b01000000000(0x200) とORして(*2)になるものは次の値。

0b1?0?00001?1

この条件を満たすもので最小値は次の値。

1029(=0b10000000101)

SHA256('1029')を計算する。

3DS{d9a5223b761c375d1263e6e57ebec42d3e0fe3f6f283488d2eb204fb6ff17ee5}

Excaliflag (STEGO 100)

pngファイルが与えられている。StegSolveで開いてみると、Blueのビットにフラグが隠れていた。
f:id:satou-y:20170103124143p:plain

3DS{Gr4b_Only_th1s_B1ts}

base3200 (MISC 100)

Base64エンコードを複数回行っていると思われる。例外が発生するまでデコードする。

import base64

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

i = 1
while True:
    print 'Try %d times' % i
    try:
        data = base64.b64decode(data)
    except:
        break
    i+= 1

print data
3DS{B453_64_L00p_50}

Crypt or Encode? (MISC 200)

いくつかの文字をこのシステムでエンコードして、そのデータをBase64デコードして比較してみる。

平文:0123456789
暗号文:YGJkZmhqbG5wcg==
暗号文のBase64デコードデータ:'`bdfhjlnpr'

平文:ABCDEFGHIJKLMNOPQRSTUVWXYZ
暗号文:goSGiIqMjpCSlJaYmpyeoKKkpqiqrK6wsrQ=
暗号文のBase64デコードデータ:
'\x82\x84\x86\x88\x8a\x8c\x8e\x90\x92\x94\x96\x98\x9a\x9c\x9e\xa0\xa2\xa4\xa6\xa8\xaa\xac\xae\xb0\xb2\xb4'

平文:abcdefghijklmnopqrstuvwxyz
暗号文:wsTGyMrMztDS1NbY2tze4OLk5ujq7O7w8vQ=
暗号文のBase64デコードデータ:
'\xc2\xc4\xc6\xc8\xca\xcc\xce\xd0\xd2\xd4\xd6\xd8\xda\xdc\xde\xe0\xe2\xe4\xe6\xe8\xea\xec\xee\xf0\xf2\xf4'

暗号文をBase64デコードして、ASCIIコードを半分にしたものを文字にしていけば復号できそう。

enc = 'Zoim9tRgkNy+iGBmvrLeqr6MaGLY+g=='

b64_plain = enc.decode('base64')

flag = ''
for i in range(len(b64_plain)):
    code = ord(b64_plain[i]) / 2
    flag += chr(code)

print flag
3DS{j0Hn_D03_YoU_F41l}

Different and Notorious Alignment (PROG 200)

最初に聞かれるタイプの数値はそのまま提示されたものと同じ数値を答えればよい。あとは文字の違う箇所の数を答えていく。

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('54.175.35.248', 8001))

data = s.recv(2048)
print data
pattern1 = 'To receive the first pair type (.+):'
m = re.search(pattern1, data)
s.sendall(m.group(1) + '\n')

for i in range(1, 101):
    data = s.recv(1024)
    print data,
    pattern2 = 'Sample 01\: (.+) - Sample 02\: (.+)'
    m = re.search(pattern2, data)
    s1 = m.group(1)
    s2 = m.group(2)
    count = 0
    if len(s1) < len(s2):
        LEN = len(s1)
        DIFF = len(s2) - len(s1)
    else:
        LEN = len(s2)
        DIFF = len(s1) - len(s2)
    for i in range(LEN):
        if s1[i] != s2[i]:
            count += 1
    count += DIFF
    print count
    s.sendall(str(count) + '\n')

data = s.recv(256)
print data
3DS{Y0u_4ch13v3d_h4mm1ng_D1st4nc3}

We also have memes! (STEGO 300)

画像の中に文字データとしてフラグを隠している。そのロジックはスクリプトとして提示されている。スクリプトからpは0~5、offsetは1~h(=5000)-255の範囲であることまでわかったので、ブルートフォースでフラグが"3DS{"で始まるものを探す。

from PIL import Image

pre_flag = '3DS{'

def get_pixel(t, x1, y1):
    if t == 0:
        r, g, b = img.getpixel((x1, y1))
    elif t == 1: 
        r, b, g = img.getpixel((x1, y1))
    elif t == 2:
        g, r, b = img.getpixel((x1, y1))
    elif t == 3: 
        g, b, r = img.getpixel((x1, y1))
    elif t == 4:
        b, r, g = img.getpixel((x1, y1))
    elif t == 5: 
        b, g, r = img.getpixel((x1, y1))
    return r, g, b

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

found = 0
for p in range(6):
    for offset in range(1, h - 255 + 1):
        LEN, x, y = get_pixel(0, 0, 0)
        flag = ''
        for i in range(LEN):
            r, g, b = get_pixel(p, x + offset, y + offset)
            if i < len(pre_flag):
                if chr(r) != pre_flag[i]:
                    break
            else:
               found = 1
            flag += chr(r)
            x = g
            y = b
        if found == 1:
            break
    if found == 1:
        break

print flag
3DS{w0w_aw3s0me_scr1pt}

Return of the Notorious Alignment (PROG 300)

部分文字列で共通する文字列の最大長を答える問題。

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('209.190.1.131', 9002))

data = s.recv(2048)
print data
pattern1 = 'To receive the first pair type (.+):'
m = re.search(pattern1, data)
s.sendall(m.group(1) + '\n')

for i in range(1, 101):
    data = s.recv(2048)
    print data,
    pattern2 = 'Sample 01\: (.+) - Sample 02\: (.+)'
    m = re.search(pattern2, data)
    s1 = m.group(1)
    s2 = m.group(2)
    if len(s1) < len(s2):
        s_s = s1
        s_l = s2
    else:
        s_s = s2
        s_l = s1
    found = 0
    for l in range(len(s_s), 0, -1):
        for i in range(len(s_s) - l + 1):
            if s_s[i:i+l] in s_l:
                found = 1
                ans = l
                break
        if found == 1:
            break
    if found == 0:
        ans = 0
    print ans
    s.sendall(str(ans) + '\n')

data = s.recv(256)
print data
3DS{C0mp4ris0n_0f_substr1ngs_1s_c00l}

Fibonacci Calls (PROG 400)

何回関数が呼ばれるかを答える問題。回数はG(N) = G(N-1) + G(N-2) + 2、G(0) = 0、 G(1) = 0の漸化式になるので、それを使って最初に想定するNの範囲で計算結果をメモリ上に保存しておき、聞かれたときに取り出せるようにする。

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

dic = {}
dic[0] = 0
dic[1] = 0
for i in range(2, 1000):
    dic[i] = dic[i-1] + dic[i-2] + 2

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('54.175.35.248', 8000))

data = s.recv(2048)
print data
pattern1 = 'To start the challenge inform the number (.+):'
m = re.search(pattern1, data)
s.sendall(m.group(1) + '\n')
data = s.recv(256)
print data

for i in range(1, 101):
    data = s.recv(256)
    print data,
    pattern2 = 'Stage (.+) -> N = (.+)\n'
    m = re.search(pattern2, data)
    count = int(str(dic[int(m.group(2))])[-3:])
    print count
    s.sendall(str(count) + '\n')
    data = s.recv(256)
    print data

data = s.recv(256)
print data
3DS{g00d4lgorithmsC4nSaveYourTime}