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つからできています。
ここでですが、
プルアップ抵抗なのでです。
以上に注意して解読すると次のような回路であることがわかります。
適当にエミュレータを作って動かせば答えが出ます。
#!/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
回路を解読するとだいたい次のようになります。
与えられた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}