はじめに
8月11日、12日に開かれたInterKosenCTF 2019でいくつか作問を担当しました。 今回は簡単な問題だけ出題するということで、CTF初心者だった頃を思い出しつつ、初心者の勉強になるような問題を心がけて作りました。 その結果、難易度を押さえつつ良い感じの問題もできたのですが、やはり非想定解が大量に発生してしまいました。 まぁもともと簡単だから非想定解があっても難易度は簡単なままなんですけどね。 とりあえず自分が作った分について軽く想定解というかお気持ち的なものを書きます。
公式writeup集はこちら
fastbin tutorial
一番最初に作った問題です。 pwn初心者にヒープ問題のお気持ちを知ってほしかったので作りました。
fastbinなので同じ領域を2回連続でfreeすると落ちてしまいます。 そこに注意して、double freeをしてfdを用意されたフラグのアドレスにつなげればOKです。 fastbinなのでフラグのアドレス-0x10を指定しないといけません。 間違えてフラグのアドレスを繋いだ場合は「ヘッダ忘れてない...?」みたいなメッセージが出力されるようになってます。
結果として30チームに解いてもらい、アンケートでもfastbinの勉強になったという声が上がっていたので出題して良かったと思ってます。
shopkeeper
単純なBOF問題です。 変数を書き換えるだけの一番簡単な部類のBOFですが、単に"AAAAA..."みたいに打ったら解けるのはつまらないので、理解した上でのみ解ける設計にしました。 あとプログラムをやや大きめにしたので、読んで脆弱性を見つける能力も問われるんじゃないかな?
アイテムとしてHopesを選択するとシェルが取れますが、お金が無いのでHopesを選びつつBOFでお金を増やす必要があります。 readで改行コードが来るまで読み続けるので"Hopes\x00AAAA..."みたいなデータをリターンアドレスを書き換えない程度に書き込めばシェルが取れます。
こちらも23チームに解いてもらい、妥当な結果かなーと思ってます。
bullsh
単純なFSB問題です。 x64なのでpwntoolsのfsb機能は対応してないと思います。 x64だとアドレスにnull文字が入るので、アドレスリストは後ろに持って来ないとだめなんですよね。 ちなみにptrlibのfsbを使うとx64でも勝手に書式文字列を作ってくれます。(宣伝)
15チームに解いてもらいました。
stegorop
この辺からpwn初心者は苦しくなってくると思います。
こちらも100バイトのバッファに0x100バイト書き込めるのでBOFがあります。 普通にROPでlibcのアドレスをリークしてret2vulnすれば良さそうですが、一度main関数を呼び出すとロックが掛かってもう一度呼び出せません。 想定解としては
でした。 しかしよく考えるとreadでlockを書き換えれば済みますね......。 lockはmmapで確保してsyscallのmprotectで読み込み専用にすれば良かったなぁ。 まぁあんまり難しくしてもアレなんで、これくらいの難易度で良いでしょう。
kitten
tcache問題1つ欲しいなぁと思って作った問題です。 pwnが多かったのでWinterKosenCTFに回すか悩みましたが、Winterに出すにしては簡単すぎるので今回出題しました。 でもstegoropよりは難しいかなぁという判断で難易度はlunaticにしました。 pwn強い人からすればwarmupなんで解いてみて拍子抜けした方もいるかもしれません.......
IDAとかで読むと一見脆弱性がなさそうに見えるかもしれませんが、よく見ると負のインデックスを確認していないので範囲外参照できます。 nameの後ろにkittensがあるので、負のインデックスを渡すとnameに書き込んだ値をアドレスとして認識し、printfやfreeできます。 想定解は
- nameに適当な関数のGOTのアドレスを書き込んでlibc leak
- nameにkittensのアドレスを書き込んでheap leak
- nameに適当なチャンクのアドレスを書き込んでdouble free
- TCache Poisoningで__free_hookを書き換える
でした。 実はもともとheapのアドレスはnameのbuffer overreadで取る予定だったので、使う前にnameが初期化されていなかったりstrcpyでヒープに書き込んだりしています。 そのため、readで読んだサイズだけmallocで確保するのにstrcpyで書き込むので、ヒープオーバーフローがあります。 運営中にパケットを見ていると多くの方がヒープオーバーフローで解いていました。 これに関してはどちらの解き方でも難易度は変わらないのでまぁいっかなーという感じです。 ただ作問中にヒープオーバーフローがあることに気づいていなかったのはpwnerとしてヤバイ。
magic function
なんかフラグを出力するn次関数を作りたいという思いだけで作った問題です。 正整数xを渡すとx文字目のフラグのasciiコードを返してくれる多項式f(x)が実装されています。 初心者にとってはこれを読むのはつらいですが、入力したコードと比較しているのでgdbなどで動かして比較対象を見ればフラグが求まります。
Image Compressor
ふるつきのImage Extractorの作問チェックをしているときに思いついた問題です。 インターフェースから問題文まで丸パクリしました。
意図としては、zipコマンドの検証用オプションで任意のコマンドが実行できますよー、という内容です。 OSコマンドインジェクションと気づけば"man zip"してマニュアルを読めば解けます。
私はWeb苦手なのでNeko Loaderの方が難しいと思ったのですが、案外解かれませんでした。 ちなみに私がWeb問を作るとかなり高い確率でRCE問になります。
Neko Loader
PHPのincludeで外部URLを参照する問題を作ろうと思って作った問題です。 もともとhttpが読める本当にシンプルなinclude問題だったのですが、作問チェックで簡単すぎる判定が出たので作り直しました。 なんかftpも読めるとマニュアルに書いてあったのでhttpは通らないようにしました。 サーバー持ってない人が解けるか不安だったのですが、ふるつきの作問チェックで"php://"でも解けることが分かったのでそのまま出題しました。
saferm
ディスク系forensics問題です。 Sleuth Kitとかで削除されたzipを取り出せます。 さすがにそれだけでは簡単なのでXORしてから削除した、という問題設定にしました。 それでも簡単なので、最後の数バイトはXORされない、というプログラムにしました。 そしたら思ったより解いて貰えなかったです(´・ω・`)
最後の数バイトがXORされないことに気づかなくても、最後の方が壊れたzipはFFオプションで修復できるので、それで解いても構いません。
Temple of Time
パケット系forensics問題です。 Webサイトに対する攻撃パケットを調べる問題にしようとは思っていたのですが、何か良い問題ないかなーと考えていたらTime-based SQLiなら面白いんじゃね?と思って作りました。 投票システムのWebサイトで、投票に成功しても失敗してもUIが変わらないのでTime-based SQLiを使っている、という想定です。 この問題は思った以上に多くのチームが解いてくれたので嬉しかったです。
おわりに
本当は難しい問題を作る方が好きなんですが、参加者から頂いたフィードバック的には今回の難易度が良かったようです。 次回WinterKosenCTFは極力難しくしようと思ったのですが、今回くらいの難易度の方が需要あるのかな? できれば来年の夏にInterKosenCTF 2020を開きたいので、その時は今回参加者からあがった意見を参考にしつつ、問題設計をしていくつもりです。 スポンサーの方々、参加者のみなさん、ありがとうございました!