Tech Blog of Beatrobo, Inc.

SECCON 2015 Online CTF Writeup

| Comments

お久しぶりです。Beatrobo竹井です。

12/5 15:00 〜 12/6 15:00 に開催された SECCON 2015 Online CTF の参加レポートと正解できたチャレンジの解法の解説をお送りします。

今回初参加で、MITMというチーム名で私ひとりでのチャレンジ。まったく予習をしていなかったのでQRコードやパケット解析はほぼわからず、結局900点の202位で終了となりました。

時間内に解けた問題は以下。

  • Start SECCON CTF (50)
  • SECCON WARS 2015 (100)
  • Reverse-Engineering Android APK 1 (100)
  • Reverse-Engineering Hardware 1 (400)
  • Connect the server (100)
  • Command-Line Quiz (100)
  • Last Challenge (Thank you for playing) (50)

締め切りの10分後に”Reverse-Engineering Hardware 2 (500)“の答えがわかってしまい、1日目にぐっすり寝たのを後悔。

弊社ではPlugAirというハードウェアな認証キーを作っておりハードウェア周りが得意なので、ハードウェア系の問題 “Reverse-Engineering Hardware 1&2” の詳細な解説をしていきます。

ハードウェアのリバースエンジニアリング系の問題は、配点が高い割に回路構成さえ把握できれば悩むところがあまりない問題ばかりだったのでオススメです。

コードは https://github.com/hideyuki/SECCON-2015-Online-CTF-Writeup に実行可能な状態で置いています。


解説の前に

今回出題された問題は こちらにすべて公開されています。

ハードウェア系の問題2つで共通だったことは、RaspberryPi B+と74HC系のICが使われていたということ、回路構成は多数の外観写真から推定すること、だいたいの動作は提供されたPythonのソースコードで把握できたことでした。
ということで、RaspberryPi B+のGPIOのピン配置を手元に置いておきましょう。GPIO: MODELS A+, B+ AND RASPBERRY PI 2 で公開されています。

Reverse-Engineering Hardware 1

設問

私たちはデコーダーボードの写真と、すてきなテキストを生成するプログラムを入手した。
解読のお手伝いをしてくれない?
gpio.py
ChristmasIllumiations.zip

提供されたもの

  • RaspberryPi上で動作するPythonのコード(プログラム中にGPIOを使っているので、実環境がないと動作しない)
  • LEDがチカチカする動作ムービー
  • 回路構成を類推するための多数のデコーダーボード画像(以下のような画像が30枚ほど)

手順

上にあるデコーダーボード画像に写っているLEDのうち、ブレッドボード上部に刺さっている6個のLEDを左からL1からL6と、L6の下にあるLEDをL7と命名。

また、ブレッドボード左下部に74HC74というDフリップフロップが1つあるのを発見。

まずはコードをざっくりみてRaspberryPiで利用しているGPIOのポートを確認。その後、多数の画像から回路構成を調査する。調査結果が以下。

この適当な回路図ではL1とL2が下のほうに描かれていますが、紙面の関係上下に分けて書いただけで、L1〜L6は横一列に並んでいます。

LED周辺の回路は基本的に、2つのダイオードでORを作り、その出力にLEDとプルアップ抵抗を繋いでいます。所々にGPIOの入力ピンも接続されており負論理となっています(LEDは正論理で、ONだと点灯)。図で表すと以下の様な感じ。

で、D-FFまわりの回路はごちゃごちゃしているんですが、以下のような感じでした。(図やプログラムではD-FF単体をIC1、IC2と書いちゃってますがお気になさらず…)

これら回路構成調査の結果から、以下の関係式となることがわかりました。

  • L1 = DA・QIC2 = X3
  • L2 = L1QIC1 = X4
  • L3 = DB・QIC2 = X5
  • L4 = QIC1QIC2
  • L5 = QIC1L4
  • L6 = QIC2L4
  • L7 = L5L6 = X6
  • DIC1 = DA
  • DIC2 = DB
  • QIC1 = X1
  • QIC2 = X2

(関係式を整理すれば、もうちょっとシンプルになるかも)

この関係式をPythonのプログラムのほうに追加で実装すれば、実機がなくともプログラムを実行できるようになります。

そして愚直に実装したコードがこちらです。

https://github.com/hideyuki/SECCON-2015-Online-CTF-Writeup/blob/master/Reverse-Engineering-Hardware-1/gpio_sim.py

gpio.py からの主な変更点は

  • DA, DBが変更する時やFFのQの値が変わるタイミングでLEDの状態を更新するupdate_led()を実行
  • encoder()内で参照していたX1〜X6をFFのQやLEDの状態で置き換え
  • CLKを進めていたところでupdate_ff_q()を実行し、Qの値を更新する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def encoder():
    v = 0
    v = ic1q                # x1 = ic1q
    v = 2*v + ic2q          # x2 = ic2q
    v = 2*v + int(not(l1))  # x3 = not l1
    v = 2*v + int(not(l2))  # x4 = not l2
    v = 2*v + int(not(l3))  # x5 = not l3
    v = 2*v + int(not(l7))  # x6 = not l7
    return v

def update_led():
  global l1, l2, l3, l4, l5, l6, l7
  l1 = da or not(ic2q)
  l2 = not(l1) or not(ic1q)
  l3 = db or ic2q
  l4 = not(ic1q) or not(ic2q)
  l5 = not(ic1q) or not(l4)
  l6 = not(ic2q) or not(l4)
  l7 = not(l5) or not(l6)

def update_ff_q():
  global ic1q, ic2q
  ic1q = da
  ic2q = db

で、結果は

1
2
$ ./gpio_sim.py
The flag is SECCON{###FD80UY#!8880UY#!8}

Reverse-Engineering Hardware 2

設問

我々は2つの74HC161を使ったエンコーダーボードによるバイナリを入手した。 復元を手伝ってほしい。
gpio2.py
encripted
counterhardware.zip

提供されたもの

  • RaspberryPi上で動作するPythonのコード(プログラム中にGPIOを使っているので、実環境がないと動作しない)
  • LEDがチカチカする動作ムービー
  • 回路構成を類推するための多数のエンコーダーボード画像(以下のような画像が30枚ほど)

手順

ブレッドボード上に74HC161という4bitのバイナリーカウンターが2つ載っています。74HC161はCLKが入るとカウンターが+1されるものです。

そして、Pythonのコードを眺めているとP系の出力は一切動いていないのと、データはXORでエンコードされているのでもう一度同じ操作をすればデコードできることがわかりました。

そして多数の画像から回路図をおこしてみたのが以下(GPIOのピンが変な配置になってますが気にせず…)

動画はあまり参考にならず、とりあえずこの回路図通りにPythonのコードにバイナリカウンタの部分を追加で実装してみました。

https://github.com/hideyuki/SECCON-2015-Online-CTF-Writeup/blob/master/Reverse-Engineering-Hardware-2/gpio2_sim.py

gpio2.pyからの変更点としては

  • pulse(clock)count_up()に変更。カウンタがクロックで叩かれると1つ進むハードの構造をプログラムした
    • count_up() は、バイナリカウンタ2つをそのまま実装。1つめのカウンタのCO(キャリーオーバー)が2つめのカウンタのCLKになっている。COがHighとなる条件は全部のQがHighの時
  • バイナリカウンタの出力Qの状態を変数化(q0〜q7)
  • lenには入力ファイルのバイト数が自動で入るように
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def get_current_count_a():
    return q2*8 + q1*4 + q0*2 + q7

def get_current_count_b():
    return q6*8 + q5*4 + q4*2 + q3

def count_up():
    global q0, q1, q2, q3, q4, q5, q6, q7
    da = get_current_count_a()
    dan = da + 1
    q7 = 0x01 & dan
    q0 = 0x01 & (dan>>1)
    q1 = 0x01 & (dan>>2)
    q2 = 0x01 & (dan>>3)

    if q7 == 1 and q0 == 1 and q1 == 1 and q2 == 1:
        db = get_current_count_b()
        dbn = db + 1
        q3 = 0x01 & dbn
        q4 = 0x01 & (dbn>>1)
        q5 = 0x01 & (dbn>>2)
        q6 = 0x01 & (dbn>>3)

このスクリプトを実行しても、出てくるのはテキストファイルではないバイナリでした。 試しにHexEditでバイナリの中身を確認しても、やっぱりよくわかりません。

とりあえず頭の 1F 8B 08 でググってみると、gzip形式らしいとのことがわかったのでgunzipで解凍。すると、flagが書かれているテキストファイルが出てきました!

1
2
3
$ gunzip output
$ cat output
The flag is SECCON{7xgxUbQYixmiJAvtniHF}.

感想

これ以外にもAndroidのAPKを初めて逆コンパイルするなどの体験ができたので非常に勉強になりました。組み込み系の問題があってちょっとびっくりしました。おもしろいですよね。

問題の傾向として、

  • QRコードの人(QRコードの問題数多い印象)
  • パケット解析の人
  • Cryptoな人
  • 画像(バイナリアン)な人
  • ハードウェアな人

が揃っていれば結構上位が目指せそうな気がするので、次回は複数人で参加しようかと思います。運営の方々どうもありがとうございました。

最後に

こんなセキュリティとハードウェアが好きなアルバイトや社員を募集しております!jobs@beatrobo.comにご連絡ください。

ぜひBeatrobo代々木オフィスに遊びに来てくださーい!

Comments