調べたこと、作ったことをメモしています。
こちらに移行中: https://blog.shimazu.me/

SECCON2015 Writeup - EEIC Advent Calendar Day 6

この記事はeeic Advent Calendar 2015の6日目の記事になります。

目次

はじめに

去年初めてやったSECCONではまとまった時間が取れずに悔しい思いをしたので、今年こそはもう少し・・・!と思い、参戦してみました。 今回は、EEICのメンバー(@Tak_Yaz, @meryngii, @wakanapo_eeic, @show_me_tech)を適当に集めて、SECCON2015のオンライン予選に参加しました。

チームの結果としては、以下の問題を解いて1900点、1251チーム中80位という順位でした。

皆CTF初心者の集まりとしてはわりと頑張ったのではないでしょうか。 チームで解いたものは以下です。

  • [ 50] Start SECCON CTF
  • [100] SECCON WARS 2015
  • [100] Unzip the file
  • [100] Reverse-Engineering Android APK 1
  • [400] Reverse-Engineering Hardware 1
  • [100] Connect the server
  • [300] Exec dmesg
  • [300] Decrypt it
  • [200] QR puzzle (Windows)
  • [100] Steganography 1
  • [100] Steganography 3
  • [ 50] Last Challenge (Thank you for playing)

チームメンバのwriteupはこちら。

ここでは、僕の解いた/解こうとしたものについて簡単にメモを残していこうかと思います。

やったこと

Try 1: Unzip the file

とりあえず暗号化されたファイルが置いてあったので、ブルートフォースで解読するコマンド、fcrackzipを回しました。 ただ、しばらく経っても終わらなかったので、同時にこの問題触れていた@meryngiiにバトンタッチ。@meryngiiがbacknumber08.txt, backnumber09.txtを見つけていたので、それを使って解錠してくれたようです。

Try 2: Fragment2

Unzipしながら僕はこの問題に移行しました。問題としてはpcapファイルがあるだけなので、パケットをwiresharkとかで見たら100点問題だしすぐわかんだろー、とか思って気楽に開いたら、なんと1パケットだけ。 ペイロードはバイナリだけど、"Flag is in header"とかいうメッセージと、ポート80からのレスポンスだからおそらくHTTP界隈なのだろうということは理解しました。HTTPかつバイナリのペイロード、と言ったらHTTP2かな、とか思ったのだけどよく知らないので、とりあえずIP/TCPのヘッダをじっと見てみる。(大会終わってからTwitter見た感じ、これがよくなかったっぽい。)

なにもわからない。ということで、時間使ってるうちに次の問題が開かれたのでそっちに移行。

Try 3: Connect the server

Reverse-Engineering Hardware 1という問題を開いたら、大量のRaspberry Piとそれにつながったブレッドボードの写真、あとgpioをつかってflagを表示するpythonスクリプトが...

解けるけどめんどくさそう、となったのでまず簡単そうなこちらから解いてみました。

Connect the serverはFQDNとポート番号が与えられていたので、とりあえずncで接続。するとこんなのが出てきた。

The server is connected via slow dial-up connection.
Please be patient, and do not brute-force.

login:

何入れていいのかよくわからないけど、カチャカチャいじる。 で、しばらく悩んでたら

The server is connected via slow dial-up connection.
Please be patient, and do not brute-force.

login:

Login timer timed out.
Thank you for your cooperation.

HINT: It is already in your hands.

Good bye.

となったので、あぁ、なるほどということでwiresharkでパケットキャプチャしたらなんかフラグが出てきました。

100点げっと。

Try 4: Start SECCON CTF

まだこれ誰もトライしていなかったので、とりあえず解いてみる。 暗号と復号化後の値が2つサンプルで書いてあるのが問題。

ex1
Cipher:PXFR}QIVTMSZCNDKUWAGJB{LHYEO
Plain: ABCDEFGHIJKLMNOPQRSTUVWXYZ{}

ex2
Cipher:EV}ZZD{DWZRA}FFDNFGQO
Plain: {HELLOWORLDSECCONCTF}

quiz
Cipher:A}FFDNEVPFSGV}KZPN}GO
Plain: ?????????????????????

There is no bonus in this question

普通のシーザー暗号かなっておもったけど、カッコあるし違うじゃん。となり、PlainがABC順なのでマッピングテーブルをrubyでちゃちゃっとつくって解答。 50点。

Try 5: Reverse-Engineering Hardware 1

さて、400点問題、しかしただただ面倒なだけっぽい問題来ました。 こんな感じ。

大量の画像(ピンぼけ、影多数)からよーーーく見ると使われているICは74HC74ということでD-FFなのがわかります。で、プログラム中もDA, DBといっているのでまぁきっと読み間違いじゃないのだろうと確認。ただ、X1, X2, ..., X6とプログラム中にはあるけど、これは写真中のLEDの左からただ対応しているだけではないのだろうと推測し、きちんと線を追ってリバースエンジニアリングすることにしました。

つらい、、、、、つらい、、、、、、、、と無限に言いながら@wakanapo_eeicと頑張って書いた回路はこんな感じ。(これは最終版の正しいもの)

これの論理式を、もともとのgpio.pyへ突っ込んで、テスト。 間違い。

写真を見直すと、一箇所ピンを見間違えてた・・・。

修正。

間違い。

[1日目終了。とりあえず家帰って寝て翌朝。]

写真を見直すと、一箇所ピンを見間違えてた・・・。

修正。

間違い。

写真を見直すと、一箇所ピンを見間違えてた・・・。

修正。

これでフラグが出てきました。

SECCON{###FD80UY#!8880UY#!8}

これ、また間違いだったかと思ったよ・・・・・・・・・・・・・・

しかし、まったくもって筋肉な問題だったし、どういう意図だったのだろうか。。。

改変したgpio.pyはこんな感じです。

X1 = 0  
X2 = 0  
X3 = 0  
X4 = 0
X5 = 0  
X6 = 0  

D1 = 0
D2 = 0
Q1 = D1
Q2 = D2


def x1(d1, d2, q1, q2):
  return 1 if q1 else 0

def x2(d1, d2, q1, q2):
  return 1 if q2 else 0

def x3(d1, d2, q1, q2):
  return 1 if ((not d1) and q2) else 0

def x4(d1, d2, q1, q2):
  return 1 if ((d1 and q1) or (not q2 and q1)) else 0

def x5(d1, d2, q1, q2):
  return 1 if (not (d2 or q2)) else 0

def x6(d1, d2, q1, q2):
  return 1 if (q1 and q2) or (not q1 and not q2) else 0


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

c = '@'
flag = ""

def calculate():
  global X1, X2, X3, X4, X5, X6, D1, D2, Q1, Q2
  X1 = x1(D1, D2, Q1, Q2)
  X2 = x2(D1, D2, Q1, Q2)
  X3 = x3(D1, D2, Q1, Q2)
  X4 = x4(D1, D2, Q1, Q2)
  X5 = x5(D1, D2, Q1, Q2)
  X6 = x6(D1, D2, Q1, Q2)
  print(Q1, Q2, X3, X4, X5, X6)
#   print(D1, Q1, D2, Q2, X1, X2, X3, X4, X5, X6)

def clock():
  global D1, D2, Q1, Q2
  Q1 = D1
  Q2 = D2

for i in range(10) :
    if c == 'Y' :           # 0x39 = 0x111001
      D1 = 0
      D2 = 1
      # GPIO.output(DA, False)
      # GPIO.output(DB, True)
    else:
      if (i & 1) == 0 :   # 偶数
        D1 = 0
      else :
        D1 = 1
      if (i & 2) == 0 :   # x % 4 <= 1
        D2 = 0
      else :
        D2 = 1

    calculate()
    # 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)
    clock()
    calculate()
    # time.sleep(0.1)

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

print "The flag is SECCON{"+flag+"}"

Try 6: Individual Elebin

これは、ひたすらいろんなbinを実行するという問題。 fileコマンドしたらこんな感じ。

$ ls
1.bin*  10.bin*  11.bin*  2.bin*  3.bin*  4.bin*  5.bin*  6.bin*  7.bin*  8.bin*  9.bin*
$ for i in `seq 1 11`; do file $i.bin; done
1.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), statically linked, stripped
2.bin: ELF 32-bit MSB executable, MC68HC11, version 1 (SYSV), statically linked, stripped
3.bin: ELF 32-bit LSB executable, NEC v850, version 1 (SYSV), statically linked, stripped
4.bin: ELF 32-bit MSB executable, Renesas M32R, version 1 (SYSV), statically linked, stripped
5.bin: ELF 64-bit MSB executable, Renesas SH, version 1 (SYSV), statically linked, stripped
6.bin: ELF 32-bit MSB executable, SPARC, version 1 (SYSV), statically linked, stripped
7.bin: ELF 32-bit LSB executable, Motorola RCE, version 1 (SYSV), statically linked, stripped
8.bin: ELF 32-bit LSB executable, Axis cris, version 1 (SYSV), statically linked, stripped
9.bin: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, stripped
10.bin: ELF 32-bit LSB executable, ARM, version 1, statically linked, stripped
11.bin: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, stripped

とりあえず1は普通に実行できたけど、2.binからは環境がない。しかし、

$ strings 2.bin
B76_76
890123456789abcdef
GCC: (GNU) 3.4.6
.shstrtab
.text
.rodata
.data
.bss
.stack
.comment

ということで、2.binはきっとB76_76なのだろうと推測がつく。でも、3.binからは

$ strings 3.bin
0123456789abcdef
GCC: (GNU) 3.4.6
.shstrtab
.text
.rodata
.data
.bss
.stack
.comment

9.binにいたっては、

$ strings 9.bin
l/}/d
h/y/
/_?OA
!P0@A
h/y/
F/W/
O__OO?Q
(/3'
O__OO?Q
H/Y/O__OPp
h/w'
h.y.
.F/W/n/
1/ /_-N-}-l-
L/]/O]_O
,/=//_?Oq/`/
0123456789abcdef
.shstrtab
.text
.trampolines
.data
.bss
.stack

あぁ、さすがに実行しないと許してくれないのね、と悲しい気持ちになりました。

んじゃぁ、qemuで実行してやろうじゃないの、ということで実行。

$ qemu-mips 11.bin
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
zsh: segmentation fault (core dumped)  qemu-mips 11.bin

実行できない・・・・

ぶっちゃけqemuたたくの初めてだしなーよくわかんねぇなー、ということで諦めて次の問題へ移行しました。

Try 7: Reverse-Engineering Hardware 2

これもマッチョ系か・・・ということでみてみると、今度は配線が綺麗。

利用しているICは74HC161と記述があったので、バイナリカウンタがただ並んでいるだけのように見えます。

回路を(@wakanapo_eeicが)カンバって起こしてみると、どうやらバイナリカウンタをサイクリックシフトしたものがプログラムの入力の1byteになっているようだということまで確認し、付属していた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 Counter():
    def __init__(self):
      self.n = 0
    def init(self):
      self.n = 0
    def setValue(self, n):
      self.n = n
    def getPin(self, x):
        if (x == q0):
          return 1 if self.n & (1 << 1) else 0
        if (x == q1):
          return 1 if self.n & (1 << 2) else 0
        if (x == q2):
          return 1 if self.n & (1 << 3) else 0
        if (x == q3):
          return 1 if self.n & (1 << 4) else 0
        if (x == q4):
          return 1 if self.n & (1 << 5) else 0
        if (x == q5):
          return 1 if self.n & (1 << 6) else 0
        if (x == q6):
          return 1 if self.n & (1 << 7) else 0
        if (x == q7):
          return 1 if self.n & (1 << 0) else 0
        return -1
    def clock(self):
      self.n += 1
        
        
def pulse(pin):
    pass
#     gpio.output(pin, gpio.LOW)
#     gpio.output(pin, gpio.HIGH)

def init():
    pass
#     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()
counter = Counter()

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)])
    value = a2v([counter.getPin(q7), counter.getPin(q6), counter.getPin(q5),
                 counter.getPin(q4), counter.getPin(q3), counter.getPin(q2),
                 counter.getPin(q1), counter.getPin(q0)])
    # file convert 
    v = rfd.read(1)
    d = ''
    d += struct.pack('B', ord(v) ^ value)
    wfd.write(d)

    #     setValue(value)
    counter.setValue(value)
    # time.sleep(0.1)
    # value+3
    counter.clock()
    counter.clock()
    counter.clock()
    #     pulse(clock)
    #     pulse(clock)
    #     pulse(clock)

# gpio.cleanup()

で、入力に付属のencryptedを食わせて出力をみてみたら、どうやらgzipらしいということがわかった。

が、しかし。ここからが解けない。gzipコマンドをつかって修復を試してみも解凍できないし、この67バイトのバイナリは一体なんなのか・・・? というのがわからずに諦めて次の問題へ。

Try 8: Micro computer exploit code challenge

問題は以下。

サーバーURL 10000
* プログラムはサーバの10000番ポートで稼働中
* フラッグ "SECCON{...}" はマシンコードの 0x1800 にある
* プログラムはGDBシミュレータの上で動いている
  (gdb-7.10 + special patch for read service)
avr-elf.x

もともとAVRは使っていたので環境が整っている気がするぞラッキー、と思いながら、すでに始めていた@meryngiiを手伝い始めました。 avr-elf.xはAVRマイコン向けのバイナリだということがfileコマンドからもわかったので、avr-objdumpでアセンブラからスタート。

これは、そのうちのメインルーチン(唯一入力を受け取る部分)のproc関数です。

00001126 <proc>:
    1126:       cf 93           push    r28
    1128:       df 93           push    r29
    112a:       cd b7           in      r28, 0x3d       ; 61
    112c:       de b7           in      r29, 0x3e       ; 62
    112e:       60 97           sbiw    r28, 0x10       ; 16
    1130:       0f b6           in      r0, 0x3f        ; 63
    1132:       f8 94           cli
    1134:       de bf           out     0x3e, r29       ; 62
    1136:       0f be           out     0x3f, r0        ; 63
    1138:       cd bf           out     0x3d, r28       ; 61
    113a:       83 e2           ldi     r24, 0x23       ; 35
    113c:       98 e1           ldi     r25, 0x18       ; 24
    113e:       0e 94 70 08     call    0x10e0  ; 0x10e0 <puts>
    1142:       ce 01           movw    r24, r28
    1144:       01 96           adiw    r24, 0x01       ; 1
    1146:       0e 94 60 08     call    0x10c0  ; 0x10c0 <gets>
    114a:       80 e3           ldi     r24, 0x30       ; 48
    114c:       98 e1           ldi     r25, 0x18       ; 24
    114e:       0e 94 70 08     call    0x10e0  ; 0x10e0 <puts>
    1152:       ce 01           movw    r24, r28
    1154:       01 96           adiw    r24, 0x01       ; 1
    1156:       0e 94 70 08     call    0x10e0  ; 0x10e0 <puts>
    115a:       8c e1           ldi     r24, 0x1C       ; 28
    115c:       98 e1           ldi     r25, 0x18       ; 24
    115e:       0e 94 70 08     call    0x10e0  ; 0x10e0 <puts>
    1162:       80 e0           ldi     r24, 0x00       ; 0
    1164:       90 e0           ldi     r25, 0x00       ; 0
    1166:       60 96           adiw    r28, 0x10       ; 16
    1168:       0f b6           in      r0, 0x3f        ; 63
    116a:       f8 94           cli
    116c:       de bf           out     0x3e, r29       ; 62
    116e:       0f be           out     0x3f, r0        ; 63
    1170:       cd bf           out     0x3d, r28       ; 61
    1172:       df 91           pop     r29
    1174:       cf 91           pop     r28
    1176:       08 95           ret
    

AVRの関数呼び出しのconvention ruleをググって、r24, r25が関数の引数/返り値だということを把握しました。また、in r28, 0x3d/in r29, 0x3eはスタックポインタだということもAVRの命令セット一覧からわかりました。

で、この関数はr28, r29をpushしたあと、スタックを0x10下げているので、getsのバッファ(0x10バイト、つまり16バイト)をこえると、17バイト目がr29, 18がr28となり、19/20バイト目がprocからの戻り番地になっていると考えられます。

しかし。そうなるように入力を与えてもプログラムカウンタを好きなところに飛ばせない。飛ばしたあとの処理も思いついていない上にこれではどうしようもないなぁ。というところで残り30分。

残り30分はいろいろパパっと他の問題見たり、@muscle_azosonが4042を解けそうになっていたのでそれを手伝ったりしましたが、得点にはつながらなかったです。

END

僕が絡んだ得点は結局550点でした。

おわりに

QRコードが死ぬほど出てたり、ハードウェアのマッチョ系問題があったりと初心者でも労力かければ解ける系問題が案外多いなぁと思いましたし、そこからある程度得点を稼ぐことができたように感じます。(こんなもんなのかな?)

一方、バイナリだとかネットワークだとかはなかなか知識が足りずに難しく、スッキリ解くことができなくて辛かったです。

またもうちょっと知識を付けてぜひ再戦したいですね。興味ある人いたらぜひ声かけて下さいな。”コンピューター”のいろんな知識がつくのでとてもおすすめです。