SECCON 2015 Online CTF Writeup

Crypto 100 (Unzip the file)

パスワード付きのzipファイルが渡されます。
中身のファイル名がbacknumber08.txt, backnumber09.txt, flagの3つなので、それでググるとsecconメールマガジンのバックナンバーが引っかかります。
http://2014.seccon.jp/mailmagazine/backnumber08.txt
http://2014.seccon.jp/mailmagazine/backnumber09.txt
あとはpkcrack(https://www.unix-ag.uni-kl.de/~conrad/krypto/pkcrack/pkcrack-readme.html)でKnown-plaintext attackするとWordのファイルが出てきます。
開くと真っ白ですがフォントの色を変えると答えが出ます。

$ zip backnumber08.zip backnumber08.txt
$ ./pkcrack-1.2.2/src/pkcrack -C unzip -c backnumber08.txt -P backnumber08.zip -p backnumber08.txt -d decrypted.zip
Flag: SECCON{1s_th1s_passw0rd_ weak?}

Binary 400 (Reverse-Engineering Hardware 1)

Raspberry Pi 2とブレッドボードで作った回路の写真と動画、pythonのコードが渡されます。
回路の解読のためにはGPIOのピン配置を知る必要があります。
http://qiita.com/aryoa/items/3f6d82b8c63761cef087
74HC74(Dフリップフロップ)が使われているのでデータシートも読みます。
http://www.marutsu.co.jp/contents/shop/marutsu/datasheet/TC74HC74A.pdf
回路は次のようなブロック7つからできています。

ここで\text{LED}=\text{in}_1+\text{in}_2ですが、
プルアップ抵抗なので\text{out}=\bar{\text{LED}}です。
以上に注意して解読すると次のような回路であることがわかります。
{X_1=Q_1\\X_2=Q_2\\X_3=\bar{\text{DA}}\cdot Q_2\\X_4=(\text{DA}+\bar{Q_2})\cdot Q_1\\X5 = \bar{\text{DB}}\cdot\bar{Q_2}\\X_6=\bar{Q_1\oplus Q_2}}
適当にエミュレータを作って動かせば答えが出ます。

#!/usr/bin/python

#import RPi.GPIO as GPIO
import time

CLR = 4
CLK = 5
DA  = 6
DB  = 12

X1  = 13
X2  = 19
X3  = 20
X4  = 21
X5  = 26
X6  = 16

Q1 = 0
Q2 = 1

class VirtualGPIO:
    array = [False] * 27
    def input(self, pin):
        if pin in [X1, X2, X3, X4, X5, X6]:
            self.update()
        return self.array[pin]
    def output(self, pin, val):
        self.array[pin] = val
        if pin == CLK:
            self.array[Q1] = self.array[DA]
            self.array[Q2] = self.array[DB]
    def update(self):
        self.array[X1] = self.array[Q1]
        self.array[X2] = self.array[Q2]
        self.array[X3] = not (self.array[DA] or not self.array[Q2])
        self.array[X4] = not ((not self.array[DA] and self.array[Q2]) or not self.array[Q1])
        self.array[X5] = not (self.array[DB] or self.array[Q2])
        self.array[X6] = not (self.array[Q1] != self.array[Q2])

GPIO = VirtualGPIO()

#GPIO.setmode(GPIO.BCM)
#GPIO.setup(CLR, GPIO.OUT)
#GPIO.setup(CLK, GPIO.OUT)
#GPIO.setup(DA,  GPIO.OUT)
#GPIO.setup(DB,  GPIO.OUT)

#GPIO.setup(X1, GPIO.IN)
#GPIO.setup(X2, GPIO.IN)
#GPIO.setup(X3, GPIO.IN)
#GPIO.setup(X4, GPIO.IN)
#GPIO.setup(X5, GPIO.IN)
#GPIO.setup(X6, GPIO.IN)

GPIO.output(CLK, False)
GPIO.output(DA,  False)
GPIO.output(DB,  False)

#GPIO.output(CLR, False)
#GPIO.output(CLR, True)

flagString = ""

def encoder(x6,x5,x4,x3,x2,x1):
    v = 0
    v = GPIO.input(x1);
    v = 2*v + GPIO.input(x2)
    v = 2*v + GPIO.input(x3)
    v = 2*v + GPIO.input(x4)
    v = 2*v + GPIO.input(x5)
    v = 2*v + GPIO.input(x6)
    return v

c = '@'
flag = ""

try:
    for i in range(10) :
        if c == 'Y' :
            GPIO.output(DA, False)
            GPIO.output(DB, True)
        else:
            if (i & 1) == 0 :
                GPIO.output(DA, False)
            else :
                GPIO.output(DA, True)
            if (i & 2) == 0 :
                GPIO.output(DB, False)
            else :
                GPIO.output(DB, True)

        #time.sleep(0.1)

        c = chr(encoder(X6,X5,X4,X3,X2,X1)+32)
        flag = flag + c

        GPIO.output(CLK, True)
        GPIO.output(CLK, False)

        #time.sleep(0.1)
        flag = flag + chr(encoder(X6,X5,X4,X3,X2,X1)+32)

except KeyboardInterrupt:
    print("stop\n")

#GPIO.cleanup()

print("The flag is SECCON{"+flag+"}")
Flag: SECCON{###FD80UY#!8880UY#!8}

Unknown 100 (4042)

問題文が「謎の文章が2005年に古代遺跡から発見された。
これは何を意味している?」だったので、"4042 2005"で検索するとRFC4042というジョークRFCが出てきます。
http://www.ietf.org/rfc/rfc4042.txt
これで与えられた8進列をUTF-9 -> UTF-16にデコードします。

#!/usr/bin/env python3
with open('no-network.txt') as f:
    text = f.read()

text = ''.join(text.split('\n'))
nonets = []
for i in range(len(text) // 3):
    nonets.append(int(text[i * 3:i * 3 + 3], 8))

i = 0
result = b'\xfe\xff'#utf-16be bom
while i < len(nonets):
    if (nonets[i] & 0x100) == 0:
        result += b'\xff' + (nonets[i] & 0xff).to_bytes(1, byteorder='big')
    else:
        result += (nonets[i] & 0xff).to_bytes(1, byteorder='big') + (nonets[i+1]).to_bytes(1, byteorder='big')
        i += 1
    i += 1
    if i % 27 == 0:
        result += b'\x00\x0a'

with open('ans' ,'wb') as f:
    f.write(result)

頑張って読むと答えがわかります。

Flag: SECCON{A_GROUP_OF_NINE_BITS_IS_CALLED_NONET}

Binary 500 (Reverse-Engineering Hardware 2)

1と同様、Raspberry Pi 2の回路の写真とpythonコードが渡されます。
74HC161を使っているとのことなのでデータシートを取ってきます。
http://toshiba.semicon-storage.com/info/lookup.jsp?pid=TC74HC161AP&lang=ja
回路を解読するとだいたい次のようになります。
{q_7=\text{QA}_1,q_0=\text{QB}_1,q_1=\text{QC}_1,q_2=\text{QD}_1\\q_3=\text{QA}_2,q_4=\text{QB}_2,q_5=\text{QC}_2,q_6=\text{QD}_2\\A_1=p_0,B_1=p_1,C_1=p_2,D_1=p_3\\A_2=p_4,B_2=p_5,C_2=p_6,D_2=p_7\\\text{CK}_1=\text{clock},\text{CK}_2=\text{CO}_1}
与えられたpythonコードをよく読むと、生成されるマスクは平文によらないので、暗号文をそのまま入力すれば平文が復元できます。

#!/usr/bin/env python

#import RPi.GPIO as gpio
import time
import sys
import struct

rfd = open(sys.argv[1], 'rb')
wfd = open(sys.argv[2], 'wb')
len = int(sys.argv[3])

load  = 4
clock = 10
reset = 9

p0=21
p1=20
p2=16
p3=12
p4=25
p5=24
p6=23
p7=18

pns = [p7,p6,p5,p4,p3,p2,p1,p0]

q0=19
q1=13
q2=6
q3=5
q4=22
q5=27
q6=17
q7=26

class Virtual74HC161:
    qa = qb = qc = qd = False
    pre_clk = False
    def set(self, rst, clk, ld, a, b, c, d):
        if rst == False:
            self.qa = self.qb = self.qc = self.qd = False
        else:
            if ld == False:
                self.qa = a
                self.qb = b
                self.qc = c
                self.qd = d
            else:
                if self.pre_clk == False and clk == True:
                    if self.qa == False:
                        self.qa = True
                    elif self.qb == False:
                        self.qa = False
                        self.qb = True
                    elif self.qc == False:
                        self.qa = False
                        self.qb = False
                        self.qc = True
                    elif self.qd == False:
                        self.qa = False
                        self.qb = False
                        self.qc = False
                        self.qd = True
                    else:
                        self.qa = False
                        self.qb = False
                        self.qc = False
                        self.qd = False
        self.pre_clk = clk
    def getQA(self): return self.qa
    def getQB(self): return self.qb
    def getQC(self): return self.qc
    def getQD(self): return self.qd
    def getCO(self): return self.qa and self.qb and self.qc and self.qd

class VirtualGPIO:
    IC1 = Virtual74HC161()
    IC2 = Virtual74HC161()
    LOW = False
    HIGH = True
    array = [False] * 28
    def output(self, pins, val):
        if isinstance(pins, int):
            self.array[pins] = val
        else:
            for pin in pins:
                self.array[pin] = val
        self.IC1.set(self.array[reset], self.array[clock], self.array[load],
                self.array[p0], self.array[p1], self.array[p2], self.array[p3])
        self.IC2.set(self.array[reset], self.IC1.getCO(), self.array[load],
                self.array[p4], self.array[p5], self.array[p6], self.array[p7])
    def input(self, pin):
        self.array[q7] = self.IC1.getQA()
        self.array[q0] = self.IC1.getQB()
        self.array[q1] = self.IC1.getQC()
        self.array[q2] = self.IC1.getQD()
        self.array[q3] = self.IC2.getQA()
        self.array[q4] = self.IC2.getQB()
        self.array[q5] = self.IC2.getQC()
        self.array[q6] = self.IC2.getQD()
        return self.array[pin]

gpio = VirtualGPIO()

def pulse(pin):
    gpio.output(pin, gpio.LOW)
    gpio.output(pin, gpio.HIGH)

def init():
    #gpio.setwarnings(False)
    #gpio.setmode(gpio.BCM)
    #gpio.setup([clock,load,reset], gpio.OUT)
    gpio.output([clock,load,reset], gpio.HIGH)
    pulse(reset)
    #gpio.setup(pns, gpio.OUT)
    gpio.output(pns, gpio.LOW)
    #for q in [q7, q6, q5, q4, q3, q2, q1, q0]:
        #gpio.setup(q, gpio.IN)

def setValue(n):
    pulse(reset)
    for i in range(n):
        pulse(clock)

def a2v(a):
    return a[7]+2*a[6]+4*a[5]+8*a[4]+16*a[3]+32*a[2]+64*a[1]+128*a[0]

# main        
init()

for i in range(len):
    value = a2v([gpio.input(q7), gpio.input(q6), gpio.input(q5),
                 gpio.input(q4), gpio.input(q3), gpio.input(q2),
                 gpio.input(q1), gpio.input(q0)])
    # file convert 
    v = rfd.read(1)
    d = b''
    d += struct.pack('B', ord(v) ^ value)
    wfd.write(d)

    setValue(value)
    #time.sleep(0.1)
    pulse(clock)
    pulse(clock)
    pulse(clock)

#gpio.cleanup()

復元したgzipを解凍するとflagが得られます。

Flag: SECCON{7xgxUbQYixmiJAvtniHF}

Exercises 50 (Last Challenge (Thank you for playing))

やるだけ。

#!/usr/bin/env python
cipher = 'PXFR}QIVTMSZCNDKUWAGJB{LHYEOEV}ZZD{DWZRA}FFDNFGQO'
plain = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ{}{HELLOWORLDSECCONCTF}'
s = 'A}FFDNEA}}HDJN}LGH}PWO'
res = ''
for c in s:
    res += plain[cipher.find(c)]
print(res)
Flag: SECCON{SEEYOUNEXTYEAR}