HITCON CTF 2014

CTF ていう、セキュリティコンテスト的なやつ、前から少し気になってたんだけど、おすすめしてもらったこともあって初参加してみました。結果は 20 位。大変たのしかった。

リポジトリ: https://github.com/shinh/hitcon-ctf-2014

vash 10

練習的なやつ。いきなりホームディレクトリに flag てファイルがあるので、それの中身を提出してみるが、ダメ。おかしーなーと find とかして他のファイルを探してみるけど、特に何もなく。

そのうちわかるだろう、と、あまり気にせず次へいった。真相は運営のアレコレでまだ submit が動いてなかったらしい。 TLE とか ICFPC とかの脱力運営に慣れてるので、こういうのでハマらない…

rsaha

RSA を解け的な問題。ただし、通常 m^e mod n だけしか教えてもらえないはずなのに、 (m+1)^e mod n もわかる。

これは3次の項が消えるので、 e が phi(n) と互いに素という条件を満たせてなくて、ダメってことかな…と思った。正しいかは知らない。そうだとして、はてこの間違った e を選んだ時の破りかたなんてわからないぞ…と考えてた。

ついでに web service を攻撃する系も見てみるが、よくわからない…と考えてるうちにバイナリ問題が追加されたのでそっちへ。

rsbo 150

ディスアセンブリ見てると心がいやされるなーと。いやされる。なんだか知らんが、おもむろに flag のファイルを読んだあと、メモリをクリアしてから意味不明な入出力をする、という素晴らしいプログラムらしい。

入力の部分、 0x70 確保してから最大 0x80 読むとかしてて、少しだけあふれちゃっているようなので、ありがたく return address を書き換えさせていただこうと思う。しかしあふれてる量が少なすぎるので、これどーしたもんかなーと考えてた気がする。あと RWX な領域もないし、 RELRO も入ってた…と思う。

しかしまぁ案ずるより生むがやすしで、実際はじめてみると、 0x80 読む関数に一段階目でもう一回飛んで、今度は大幅に色々書けるので、 return to libc で適当に open から return した先が引数の stack ぶん捨てて ret するコード片で、そこから read に行って引数捨てて write に飛ぶ、みたいな経路を作ってやった。

都合の良いコード片は ROPgadget というやつを見つけたので、それを使って探してみた。これ便利だな。

あたり前のように書いてるが、人生二度目の buffer overflow で実際に攻撃してみた経験なので楽しかった。

ここで vash も投稿できると気付いて2問。 3:42 (3時間42分経過という意味)

ty 150

rsbo やってる最中に増えてたバイナリ問題をやる。 Aarch64 です。 Aarch64 の objdump とかは入れてあったんだけど、 gcc とか qemu の使う lib とかがセットアップされてなかったので、入れながら他の問題眺めたりとか。

この問題は、なんか入力を受けて、そのコードを実行してくれます、と。ただし、実行する前に /dev/urandom からの入力との xor を取るので、ランダムなコードを実行することになって死ぬ。

ええとどうやったんだっけ。これまたオーバーフローさせれるんで、 /dev/urandom を open した fd の入ってるスタックの中身を 0 に変えてやって、 stdin からゼロを流し込んで xor を no-op 化する、て感じだったと思う。

あとは Aarch64 のコードを頑張って書いてやる感じ。 Aarch64 てことを除けば前の問題の方が難しかったと思う。しかし data 領域が実行可能ってのは、もはや馴染みがなくて、違和感があるっていうか、最初はどうやって protect 外そうとか無駄なこと考える感じになっていた。 6:52

tarmful 128

他の問題にハマってる間に、 tarmful という雑用ぽい問題が増える。単に 1024 回、ヘンなファイル名とか経由しながら圧縮されてるファイルを展開するだけ。適当なスクリプトで解決。 9:32

hop 350

ハマってた問題だと思う。 Windows バイナリがあって、 flag を入力すると Correct! てでるようになってるんだけど、単に strcmp してくれてるわけじゃなくて、入力1文字ごとに jmp table でどこかに飛んで、ってのを繰り返して判定しているらしい。 jmp table の関係を探らないといけない。

さて Windows バイナリですよ…ということで WINE を使っていたものの、イマイチ不便で、 cygwin64 などをセットアップしてみたり、しかし Windows というやつも不慣れで不便だなぁ…となる。このあたりでパスワードをチェックする部分は特にシステムの関数とか呼ばない気がしてた。

それなら比較的お得意のやつですなーと、 Linux 上で Windows バイナリ内のそのチェックする部分をロードしてみてやる。うまく動く。こうなりゃしめたもんと、 jmp table の連鎖になってることとか、そういう構造がすぐわかる。

でー、 jmp table の連鎖からグラフ構造ができたのだけど、しかしこのグラフ構造、最短経路を取れば正解というわけではなく、ちょうど40文字で到達できるパスが正解という感じ。どうすればいいんだこれは、とはまる。

バイナリの内容わかってから解けないのはくやしいなーと思いながら寝る。

起きてから、アレコレとやってるうちに、それぽい単語が頻繁に出たりしてくるので、それを正解と仮定してアレコレ調整…みたいなことをやってると、正解に辿りつく。 15:05

TLE でもこういうのあった気がするなー。

mid

これはハマって解けなかった問題。 ACM ICPC 形式の問題ということで、長い数値列の median を求めよと。ただしメモリ制限が厳しいので頑張ってね、と。

んー pipe かな、てことで pipe にデータを置いてやるプログラムを作る。しばしバグとか取って、んーこれでいいと思うんだけどなーというものが全然通らない。

最初は bss 領域使うとなぜかメモリリミットが実は越えてたとかで、そっちも修正したり。でも最後まで通らなかった。終わったあとの話によると pipe でいいらしいし、 return まで行ってから sleep すると 1 秒余計に消費するので、答えが間違ってたってことですかねえ…しかしテストケースはそれなりにあるんだけど。

polyglot 500

mid や callme 、あるいは他の問題に無駄な時間を使ってるあいだに、 polyglot という問題が追加される。超得意分野なので、俺を誰だと思ってる的な余裕で瞬殺。いや、それなりには苦労した。 Haskell, Python, C は意外と polyglot 力が低い言語なんですよ。

この問題は最初の解答者だったらしく、主催からどんな解答だったん? みたいなメールが来る。このあとたしか寝た。 22:25

Pneu 80

起きて、他の問題にハマって、やる気を失なってる間にクソゲーが増える。クソゲーだが解けそうなのでがんばって解いた。意外と時間がかかった… 30:07

sha1lcode 300

callme と 24 にハマりすぎてたので、他のバイナリぽいの見るか…と見る。なんだこれは得意分野じゃないか。

自由な入力を、 SHA1 してコード領域に置いてから実行する、って話。適当にランダムな SHA1 結果のコードを見てみるも、当然ながら使えそうなものは無い、からもっとちゃんと狙ってやらないといけない。

高度な数学とか使わなくても、適当に探せば 2,3 bytes のコード片は見つかることは、 ShaFuck を潰した時 にもやったので、同じことをやる。

今回は、先頭に次の code chunk の終端 2,3 bytes 前に jmp するコード片と、終端でなんか 1 命令実行するコード片を探した。 36:38

ducky 250

なんか (){}; を使わずに C コードを書けという、どこかで見たような話が…技を借りるぞーということで kik さんの技を使って倒す。 37:34

24 256

いやーハマっていた。与えられた4つの数字と、四則演算子を組み合わせまくって Python3 で eval して、その結果を 24 にするクイズを 24 回解けというもの。

普通に Ruby で書いて、累乗とか // も必要かーと足して、色々足して、でも最後の問題が解けない。最後はヘンな工夫がいるのかなーとずっと考えていた。

しかしどうも結論としては Ruby で書いてたせいで、 Python のシミュレーションが不十分だったらしく、手であれこれやってるうちに、アレっこれ Python複素数出るからこれかな…とかやってるうちに、複素数使わず手で解けた。

反省としては、嫌いでも Python 使いましょう。 39:49

callme 350

長い時間考えてた問題。ほぼ諦めて日曜の深夜に寝て、月曜の朝起きてから、解法らしきものが見つかって、なんとかまにあった。

sprintf での buffer overflow があるんだけど、 stack smashing protector がかかってるので、 return address を書き変えたところで、 ret するまでに __stack_chk_fail する。

しばらく勘違いしてたんだけど、この問題はよく見ると RWX なページはあるし、 RELRO もかかってない。つまりなんでもいいから RWX なとこに飛べばそれで勝ちで、そこが問題の本質なんだなぁとか考えるもわからない。

しょうがないので起きてからやったのは等価ぽい C のコードを書いてみたりして。生システムコールで入出力してるとかが気になったりしたけど、たぶん関係なかった。

でまぁ C のコードながめてると、 sprintf に渡してる書き込み先は stack なんだけど、よく考えると format 文字列自体も stack のだいぶ高位のアドレスなんだよな、と気付く。つまり sprintf で書いてる最中に format 文字列が変化して、当初の予定よりどんどん伸びていく…技が可能だ。つまり、 "%s%s\n\0" みたいな format だったはずなのに、自由に設定できる2個目の %s を解決してみると、 "aaaa%d%d%d.." みたいな感じで書きかわってるーみたいなことができると。

あとは、なんであるか不明機能であるところの %n を使って、 __stack_chk_fail の GOT をいじって RWX な自由な空間に飛ばしてやる。思うに __stack_chk_fail の GOT とか、もうちょい防衛してやってもいい気がするんだけど。

あと、これで死ぬ時に行く先である、 __fortify_fail は stack の argv[0] を出力しようとするから、書き変えてやれば memory exposure になる とか知って少し脱力した。今回 SSP に対する攻撃手段を色々調べていて、今回は全く役に立たなかったというかむしろ混乱したけど、しかし勉強になった。

あとは時間に間に合うかーという感じで、あれこれやって、あわてて shell code 書いて、なんとか通った。 shell code くらいはいい感じのを自分で作っておくと良いんだろうな。 46:50

感想

たいへん楽しかった。普段は守る側…に自分が直接いるわけではなく、しかし守る側の人を比較的近い距離で眺める、みたいなことが多いだけに、知ってる知識を逆側に使えるのは楽しい。曖昧な理解になってるところがハッキリするのも良い。

MD5 とか RSA とか、暗号系も興味があるんだけど、全く手が出なかったのは残念だった。まぁ一人だと時間的にもキツいが、それでも勉強しておきたい感はある。ネットワーク系も手が出なかったんだけど、しかしそっちはそもそも興味があまりなくて…

あと一人は楽しいものの、やはりキツい。問題が増える速度の方が解く速度より圧倒的にはやい。チームでやってる人は、何人くらいでやってるのかなぁ。こういうルールだと、組んでやるのもやってみたくはある。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h