DEFCON CTF 本戦

binjaの現地枠がギリギリ空いてそうだったので、入れてもらいました。思ったことは、みんなすごいなあ、ということと、これしんどいなあということ、ルールはともかくみんなでわちゃわちゃやるの楽しいなということ、あとまあなんと言っても俺ふがいないな、ってことでした。

今回はCTFの前日に優勝賞金2億円のDARPA主催のCyber Grand Challenge(以下CGC)というのが行なわれ、それで優勝したAIを含めて15チームで同じフォーマットで競う、という趣旨でした。CGCはELFベースにABIを簡略化してあり、いつものようにシェルコードを実行するまではやる必要がなく、

  • type 1: EIPを指定されたところに飛ばす
  • type 2: 特定のアドレスにある、フラグページから4byte取り出す

のいずれかを達成すればOK、というルールでした。

準備

準備の段階では、 bjmit という名前でバイナリに mitigation (日本語でなんと言うのだろう) を入れるツールと、それの評価をするものを作っていました。バイナリジャンルの mitigation というのは ASLR や stack canary なんかがよく使われるものです。 stack canary みたいなのを入れるのはまぁまぁ大変なので、簡単に実装できるものをたくさん準備する、という方針で

  • retをフックして戻り先が元々.textだったか、callの直後に戻っているかをチェックする
  • jmp/callをフックして戻り先が元々.textだったかをチェックする
  • sub esp, XXをフックして未初期化スタックを使わないようにする
  • writeシステムコール相当をフックして、フラグページを直接writeできないようにする
  • mmap(PROT_EXEC)相当を禁止する
  • デフォルトでスタックがRWXなのでRWな空間に引っ越す
  • ヒープを適当にrandomizeする
  • 共通で使われるコードにある、いらないコードを削除もしくは総取っ替えし、ROPなどに使いにくくする

というような機能を作りました。安易な攻撃には最初の2つが特に効く感じで、前もってサンプルで試してる感じでは、まあ半分強くらいのサンプル攻撃が止まるな、ってところでした。バイナリいじってパフォーマンス(CPUメモリディスク使用量)が落ちると評価が下がってしまうんですが、そのへんもまぁ、問題によっては使えないけどギリギリOKかな、ってレベルでした。

mitigationというのは本質的にあまり意味がないものだと思っていて(おそらく世の中的にもそう思われていて)、やはりそういうゴマかしはさらなる回避策とのイタチごっこになるので、なんらかのsandboxで根本的に根本から断つのが筋というのが最近の流れだと思います。ですが、短期間のコンテストではまあまあ時間かせぎになるし(運が良ければ完治もしうる)、ということで作ってみた感じでした。C++アセンブリで書き散らかして、2000行くらい。libdisasmとlibdisasm_cfgというのを使った感じ。

初日

練習のために出てきたバイナリが非常に小さく、かつムダな初期化ルーチンが無くなっていたためmitigationを入れるとパフォーマンスの制約を満たさなそう。実際投稿してみて得られる点数は明らかにダメ。あー覚悟はしてたけどbjmit完全にムダだったかな、と思いつつ短期間で使える方法を考える…が思いつかない。

で本番のバイナリは初期化ルーチンが復活してて、手元の確認では問題ないぽい感じになってた…けど投稿してみると実行時間が2倍以上になってるとか言われて泣く。とりあえず速度2倍では0点のはずなので、それはよくないということで焦って、stack overflowしてるところだけmitigateするようないい加減な処置をする。

それでも遅い。そこで、んなわけないだろ!!てかこれ速度で点数引かれてなくね??みたいな感じで混乱しつつ、正当なパッチにした。相変わらず実行時間は2.6倍とか異様なことを言われる不思議大会。

もうこれ何信じたらいいんかわからん…ということでbjmitの積極的な適用ができなくなってしまう。が、この攻撃食らったぽい、というのをmitigationずみのバイナリで試すとたいてい止まっているし、パフォーマンスも手元で測る限りは問題ないし…ということで微妙に使ってみたり使わなかったり、というようなことをしてたと思う。

結論としては手元の評価は間違ってなかったぽいので、最初から手元で大丈夫だと思うだけの全力mitigationをバイナリの内容を考慮に入れつつかけておくべきだった。

2問目を解析してた人がこれはそこらじゅうバグだらけで大変、ということでここを修正すべき、みたいなことを教えてもらって適当にパッチ当てる。ただそれではその時検知されてた問題全ては塞がらなかったぽい…とかやってるうちに、もうこれ相手のバイナリパクろうぜーということで、バイナリパクりゲーになった。

その後出題された3問目もバイナリパクったので、これパッチ担当ていらないよねーと晩に相談する。あとbjmitは見てる限り結構止めてるので信じませんか…というような話もする。

あと運営が時間は内部的には計測できているが、表示上おかしくなっている。明日までに直す努力をするが、本当にできるかはわからない、とか意味不明なことを言っていた。表示上の問題てどう考えてもおかしいので、時間での減点を最初の練習の後くらいにすごくゆるくしたんじゃないかなぁ…と今では邪推している。

2日目

bjmitの評価をしつつ、見つけたバグを眺めていた。ムダに時間を使ったけど、特に発展がなくて残念だった。

とかやってる間に色々と検知できたバグとかの情報が来るので、パッチ書きをする。このへんで色々パッチ書いたけど、どうにも攻撃の数が増加するのにたいしてパッチ書きが後手後手に回ってる感じになる。方針が決まっていればパッチはかなり迅速に書ける準備してあったし実際書けたと思うんだけど、自分でコード読めてないから方針が立たなかったりと。いくつかバグふさいでも全部ふさがってないと更新する意味ないどころか損になる、というルールのせいで、これで全部、とわかるまでどうしてもバイナリ更新に臆病になってしまう。次々と新しいタイプの攻撃を検知してる中、もうバグが無いと確信するのは難しい…

この日から他チームがバックドアを仕込んでくるようになったんだけど、チームの人がバックドアを外す方法を確立しはじめる。結論としてはパッチを頑張って当てるより、バックドア外しの方が良かったのではないかと思う。

このあたりでbjmitがmitigateしただけのバイナリをパクられる。光栄です。bjmitの挙動はチームの人に説明してあるので、チームの人がmitigateを回避するexploitを書いて攻撃して笑う。たしかにある意味mitigateってバックドアみたいなもんだな…とか思う。

謎のパフォーマンス数値表示は相変わらずで、この日は実行時間30倍になってるぜーとか言われてても、点数的には問題ないとかなんとか。

この日、相手チームのバイナリでprogram headerが後ろについてるものがあって、ああたしかにアレ後ろにつけても問題ないよな…とか感心した。program headerを置いてあった領域がcode領域として使えるようになるのでオトク。

2日目が終わった後に、いくつかbjmitに対してやっておいたら良さそうなこととか、適当な自分用ツールなどを整備したりした。

3日目

眠くて疲れて、完全に脳が死んでいた。

前日終了間際に出題された複数バイナリ問題のパッチを準備しておく。開幕早々それを使うと全然動いてないと出て、bjmitのせいかと思ったけどbjmitは無罪でpatchが悪かった。今見るとなんでこんなので動くと思ったの、て感じのパッチで本当にひどい。結構な失点になったぽいので、本当に申し訳ない…

色々バグ探しとかやってたけど、bjmitの運用以外はほとんど意味ない感じになってしまってたと思う。メモリ使用量が増えてる問題があって焦ったけど、運営に聞くと問題ないとのことで、時間に続いてメモリ使用量も不正確だということがわかったりした。本当にひどい。

まとめると

1,3日目ひどくて失点しちゃったのが本当にすいませんすいません…という感じ。1日目はどっちかというと運営が悪いと思うんだけど、3日目は本当にひどかった。まあそれでなくてもパッチ担当はちょっとした失敗が結構でかい失点につながるので、かなりビクビクするルールだなぁ…などと思っていた。それ以外にも、どうもチーム連携みたいなの全然うまくできてないなあ、など反省が多い感じでした。

あとどうもCTF本番もいいんだけど、ツールの準備とかも楽しかったなと。なんかモノ作るの好きなんだなぁと、今更ながら。実際CTF中も私本人は全く信用できないけど、ツールは今思えばまぁまぁ信用できる動作してた気がします…

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