SECCON 2016 Finals Writeup
事の発端(2月4日)
それとなく書けという圧力を感じた。
総評
SECCON2016 CTF finals ranking table によれば24チーム中13位。1日目ラストでJeopardyを大量に倒したため一時は6位まで上がったが、2日目でAttack Pointが取れずズルズル下がってしまった。
これでも日本の学生チームでは1位だったので文部科学大臣賞や超豪華なスポンサー賞など、結構いい感じに表彰された。
サーバ4
gdbのリモートデバッグが外に露出しているので操作してエクスプロイトしろという問題。 記憶にないけどSECCON 2016のオンライン予選で出た問題の使い回しっぽい? 詳細は坂井さんのページにあります。問題バイナリも配布されています。
問題概要
問題サーバが4つ(ARM, H8, SH, V850)動いているのでそれらをエクスプロイトし、flag.txt
に自チームのDefense flagを書き込めば良い。ARMサーバに関してはword.txt
があり、そこにAttack flagが置いてある。
Defense flagは4つの問題サーバのそれぞれで1 flagしか受け付けない上、受け付けるpayloadの長さは時間が経つほど5000, 3000, 2000, 1000, …, 4と短くなる(一定時間経つと回復する)ため、他のチームより遅く書き込まないとポイントにならない。
バイナリの推測
そもそも4つのサーバで何のアーキテクチャのバイナリが動いているのかすらわからないため、リモートデバッグのAPIを駆使してバイナリを引っこ抜く。
ステップ実行を行うコマンドはs
なので、これとチェックサム(各バイトを足し合わせ、&0xFF)を合わせた+$s#73
を送り、更にレジスタの内容を取得するg
を使ってレジスタ内でIPらしき部分を発見する。今回は0x2000
にバイナリがマップされていた。
次に、メモリからデータを取得するコマンドm[addr],[size]
を使い、m2000,200
のようにしてバイナリを取得する。
最後に、バイナリをよく読み何のアーキテクチャかを推測する。1つ目のサーバに関しては4バイト単位で命令が並んでいたので適当にGoogleに投げたところARMだとわかった。他のバイナリも同様に推測することができる。
Exploit
GDBのリモートデバッグ用プロトコルには多くのコマンドがあるが今回使用できたのはc
,s
,m
,g
,G
くらいで、実質的にできることは実行・メモリリーク・レジスタリーク・レジスタの書き換えだけ。普段のエクスプロイトのように「スタックにファイル名を置いて〜」的なことは一切できない。
というわけで、メモリ上に任意の文字列を配置するためにはレジスタ→メモリ間の転送命令を使う必要がある。
ARMの場合はpush {r4, r5, lr}
があったので3ワード(=12バイト)分一度に転送することができた。またバイナリ内にはopen-read-writeができる分のシステムコールgadgetがあり、そこにIPを飛ばすだけで普通にシステムコールが呼べるようになっていた。
Attack
Exploitで解説した要素を組み合わせると、open('word.txt', 0); read(3, 0x3000, 0x60);
相当を呼ぶことができるのでメモリ上に乗ったフラグをm
コマンドで読み出せば終わり。
Flag: SECCON{5f52d7fcb7fc824c}
Defense
Attackが解き終わった頃には他のチームが既にDefenseを押さえていた上、ARMのflag.txt
はファイルの中身を空にされてしまいフラグを書き込んでもpayloadの長さでは完全に勝てない状態になっていた。他のサーバではまだチャンスがありそうだったが、誰もAttackを取れていないサーバ2の方に移ったため放置してしまった。このときは1日目でサーバ2は設定ミスにより解けない状態だったので大きなロス……。
サーバ5
これも過去のSECCONで出た問題とほぼ同じ。一部バイナリが変わっていて過去のwriteupを使いまわしても動かなかった。
全体のコードはここ
問題概要
UTF-16LEで書いたヘブライ語をx86の機械語として実行してくれる。
2日目はこのサーバは全チームDefenseを取ってくると予想し、1日目の夜に必死にヘブライ語の勉強をした。
ヘブライ語シェルコード
xchg eax, edi add eax, 0xfbXXfbXX add eax, 0x05XXfbXX add eax, 0xfbXX05XX add eax, 0x05XX05XX xor eax, 0xXXfbXXfb xor eax, 0xXX05XXfb xor eax, 0xXXfbXX05 xor eax, 0xXX05XX05 stosb
くらいしかなく、しかもこれらの命令も100%ヘブライ語として通るわけではないので前後に無意味となるような命令を挟みつつstosb
を使ってメモリ上にシェルコードを展開していく。stosb
はmov byte [edi], al; inc edi
をやってくれる命令なのでなんと任意の値をメモリ上に書き込んでいくことができる。
方針
call eax
によりヘブライ語シェルコードの処理を呼び出すため、シェルコードが配置されているアドレスはeax
に格納されている。これにいくらかの値を足した位置にシェルコードを書き込むようにすれば自動的にシェルコードが実行される。
今回はz3を使ってadd eax, 0xAAAABBBB
のAAAA
,BBBB
に0xfb20, 0xfb30, 0x05d0, 0x05e0
のいずれかが入るような命令を組み合わせてeax
をオーバーフローさせつつ最終的にeax
に0xc00から0xf00の値を足し込めるような命令列を計算した。
次に、stagerとかを使って当日にコケたくなかったので、普通のシェルコードをヘブライ語へ変換して実行時に戻すという方法を取った。いくらヘブライ語が不自由とはいえ、下位4bitだけは0~Fのすべてが揃っていたので4bitについてヘブライ語1文字を割り当てれば簡単にデコードできる。
Attack
ここまでできていれば特に言うことはなくkeyword.txt
を持ってくるだけ。
Flag: 無くした。無念
Defense
Defense用のflag.txt
がどこにあるかわからず苦戦するが、httpでアクセス可能だったのでHTTPサーバの設定から頑張って探した。書き込み限定のattrがついていてコケたがとりあえず5分につき2~3ptを入手できるようになった。
どこのチーム(多分CyKor)だったかは忘れてしまったが、flag.txt
に書き込んだ瞬間次の行に自チームのDefense flagを書き込んでくるスクリプトがいて恐ろしかった。