Fuzzingに入門したいんすけど①
はじめに
セキュリティ・キャンプのネットワーククラスでは、実践的にファジング(Fuzzing)をして脆弱性を発見するといったことをした。あくまでファジングは脆弱性を見つけるプロセスであるため、アプローチはいくらでもある(はず)。講師であり、リサーチャーである園田さんからSCWやオンライン線形分類器を使ったfuzzのデータセットを研究するたのも面白いのではないかと言われ、黄色い本を読んでいる最中だ。しかし、今回はファジングの可能性について見てみたい。幼稚な日本語を要しているため読みづらいと思うし、間違っているところもあると思うし、正直自分のメモのために書いているのであまり読まないで欲しい。すぐ閉じて欲しい。
ファジング及びDowser
Dowserとは、BufferOverflowやUnderflow脆弱性などを減らすために、Fuzzingとシンボリック実行(symbolic execution)、テイント追跡 (Taint Tracking)と組み合わせたロジックである。
BufferOverflowはずーっとなくなることのないソフトウェアのエラーのひとつだ。Overflowが(実行時に)起きたときにプログラムを強制的に終了させるようなメモリプロテクタによりセキュリティを強化させるか、上記したように、ファジングなどのテスト段階で脆弱性を追跡するかなどがある。
メモリプロテクタには、私達pwnerが殺しまくっているCanaryや、WITのような複雑にするためのコンパイラ拡張などもある。しかし、それでは脆弱性を隠しているようなもので、脆弱性がなくなっているわけではない(=Crashしてしまう可能性がある)。だから、ファジングのような脆弱性を見つけるものが好かれていくわけだ。
Microsoftなどは、2008年から24時間365日ファジングを稼働している。しかし、現在のFuzzer(Codenomiconは除く)の精度は大したことはなく、入力フォーマットにばかり焦点を当てて、Software自体を無視しているとされている。つまり、BlackBoxのファジングは速くて人気ではあるが、効率が悪い。
各々に長所と短所があるように、WhiteboxファジングはBlackBoxに比べて原則が多い。だでぃ子さんのブログに書かれていたシンボリック実行(ex. KLEE , S2E)によって、可能であるすべての実行パスを処理してバグ、脆弱性を見つけるというものがある。だでぃ子さんがブログに書いてあったように、計算量が膨大になってしまうため実用には困難となる。
ターゲットを絞ろうぜっていう話だ。このようなアプローチは、反復方式でコード領域のシンボリック実行を行うことで、バッファオーバーフロを見つけることが出来て、既存のFuzzerでみつけることが難しかった複雑なバグ及び脆弱性をある程度高速に発見することができる(かもしれない)。
方法
上記したように、「victimとなるソフトウェアを決めて、脆弱性はバッファオーバーフローであり、そのソフトウェアのここらへんに脆弱性がありそうだ」と言ったように莫大な計算量を減らすためにスポットを特定しておく必要がある。
1.可能な限りプログラムをたくさん実行する
2.Dowserが脆弱性が潜むであろう箇所を特定してくれる
バッファオーバーフロは、バッファ領域として設定されているアドレス範囲を超えたメモリが上書きされ、誤動作が引き起こされるであるため、そこに集中して、他のコードの大部分を無視する。Dowserはそのようなアクセスをランク付けするようなプログラムを静的に解析する。1つの評価関数ではなく、色々なものを用いる。あと直感的に考えても、ポインタの演算や、複雑な制御フローが単純な配列アクセスに比べてメモリエラーが増えることは明白だ。
Dowserはそのようなプログラムに着目して、脆弱性であろうプロウグラムの優先順位を決定する。なぜそんなことをするのかというと、そうすることにより上記したような「浅いバグ」に無駄な時間を費やすこともなく危険の高い複雑なものを見つけるためだ。他にも、シンボリック実行をするため具体値の入力のベースラインとして、*concolic testing[1]を使う。
DowserはConcolic executionを使うことによってシンボリック実行をより精度の高いものとする。
1.Dowserは上記したようなエラーが起こりうる可能性が高いポインタ参照などを検査する。そして、ポインタの値を変更される可能性の高い木(枝)に沿ってシンボリック実行をしてゆく。
2.Dowserは配列のアクセスのために使われるポインタがどの入力バイトが影響するかを決めるために動的テイント解析(Dynamic Taint Analysis)を使う。つまり、それをシンボリックとして扱う。動的テイント解析自体は新しいものではないが、可能な限り精密なものにシンボリック入力のデータセットを近づけるために最適化を行う。
まとめ
「はじめに」に書いてあるようにアプローチとして、Dowserという新しいファジングの可能性について少しだけ見た(というよりこれ序盤なのでもっと書いていきたい)。上記でめっちゃ言ったが、Dowserは「すべてのバグを見つけてやる👊」という概念ではなくて「はぃ…私はここ(特定のクラス)のバグ見つけます」という概念であるため、実用に富んでいるんではないかと考えている、実際コードインジェクションなども少なくなるはず。
実際、DowserによってNginx,ffmpeg,inspircdみたいな複雑なプログラムのバグを見つけたそうだ。うほ、なるほど。つまり、既存のシンボリックツールで見つけることが困難だったもので見つけることが出来たのか。可能性を感じる。
感想
「だでぃ子さんのブログ内容カッコイイな〜(鼻ホジホジ)」って思って、私もそれに関係しそうな論文とか漁って書いたが、あまり詳しくない部分もあった。例えば、LLVMとしてダウザー解析をしてシンボリック実行ステップとしてS2Eを採用している,とか。それこそ、「LLVM Bitcodeに一回変換して〜」とか言ってみたいものだ。
セキュリティキャンプでファジングに出会ってvictimを何にしようかなどをずーっと考えていた時期があった。しかし、今となってはどういうアプローチでFuzzingするべきであるかということを考えるようになった。可能性は大きいほうが良い。もっと勉強しようと思う。
引用先
[1] concolic testing: symbolic(シンボル)とconcrete(具体値)からなる造語
http://ntddk.github.io/2014/09/12/symexec-intro
参考
Dynamic Taint Analysisに関する
Fuzzing活用の手引き(IPA)
Dowsing for Overflows: A Guided Fuzzer to Find Buffer Boundary Violations論文
追記
Pin Tools使ったMemory-In Fuzzingの記事だが、非常に興味深い。
http://shell-storm.org/blog/In-Memory-fuzzing-with-Pin/
- Choose a targeted piece of code.
- Set a breakpoint before and after our targeted area.
- Save the execution context when the first breakpoint occurs.
- Restore the execution context when the second breakpoint occurs.
- Catch the SIGSEGV signal.
- Repeat the operation 3 and 4 until the crash occurs.
Dowserと近しい部分もある。