fuzzuf: Fuzzing Unification Framework
2021-12-28 19:39:0 Author: ricercasecurity.blogspot.com(查看原文) 阅读量:62 收藏

fuzzufは独自のDSLを搭載したファジングツール(ファザー)を記述するためのフレームワークです。様々なファザーによって多様な形で定義されるファジングループを、DSLを用いてブロックを組み合わせるように記述することで、アルゴリズムの拡張性を保ちながら、ファジングループ内の挙動を柔軟に変更可能にします。既に、マルチプラットフォームに対応可能な形でAFL、VUzzer、libFuzzerを含む複数のファザーが実装されています。ユーザは、それらの定義をDSLで書き換えることによって、更に拡張することができます。

本記事では、fuzzufの概要と開発の経緯を紹介します。
まず初めに@RKX1209がファジングの概説とフレームワークの開発に至った経緯を説明します。

TL;DR

これまでAFLやlibFuzzerなどの数多くのファジング手法が開発されてきました。ファジングの研究者が新しい手法を実装するとき、実装コストを抑えるためにこのような既存のファザーにパッチを当てる方法が一般的です。しかし、元のファザーの改変が進むにつれて、そのコード変更に追従できなくなり、再利用性が落ちてしまうという欠点があります。

このような問題を解決するため、我々は新たなファジングフレームワークであるfuzzufを開発しました。fuzzufはHierarFlowと呼ばれる独自のDSLを搭載したフレームワークです。HierarFlowを活用することにより、従来のフレームワークでは実現が難しかった多種多様なファジングループを視覚的に分かりやすく定義できます。その上、定義した実行フローを後から改変することも容易になります。

fuzzufは、全てのファジングの研究者にとって便利なフレームワークになることを目指しています。みなさんが実装したファジング手法に新規性があり、論文などの成果で認められたなら、ぜひPRを送ってfuzzufへマージさせてください。
将来的には、様々なファジングアルゴリズムの性能をfuzzuf上だけで比較できるようにしたいと考えています。このために、我々は今後も既存の有名なファジングアルゴリズムをfuzzuf上で実装していきます。これに関しても、外部から是非コントリビューションしていただけると幸いです。

ファジングとは

ファジングとはプログラムの入力をランダムに生成して実行、その結果を観測することでプログラムが正常に動作しているかを検証するソフトウェアテスト技術です。近年特にセキュリティ研究者によってメモリ破壊バグの検出に使用されていますが、プログラムの異常を検出するロジック(バグオラクル)を工夫すれば他にも様々なバグの検出に使える事が知られています

ファジングというテスト手法が初めて登場したのは1990年とされていますが、それから今日に至るまで様々な改良案が提案され、現代のファジングアルゴリズムは非常に多岐にわたっています。これらを整理することは容易ではありませんが、2018年研究者達によってこれまでのファジング技術を調査、分類するサーベイ論文が発表されました。この論文では多岐にわたるファジングアルゴリズムの構造が本質的には以下のような擬似コードで抽象化できることを示しています。

この擬似コードで表される一連の実行フローを我々はファジングループと呼んでいます。

一例としてAmerican Fuzzy Lop (AFL)というファザーを見てみましょう。AFLは既存の入力の一部に変異を加えることで新たな入力を生成するミューテーションベースドファザー(Mutation based Fuzzer)です。さらに、生成した入力でプログラムを実行した際に通過したカバレッジを観測しフィードバックするグレーボックスファザー(Grey box Fuzzer)にも分類されます。カバレッジが今まで観測した事のない新しい物であれば当該入力をファイルに保存します。この保存された入力をシードと呼びます。AFLのファジングループはまとめると、まずシードの集合(以下シードキュー)から1つ選択し、当該シードの一部に変異を加えて(ミューテーションを行って)新しい入力を生成、プログラムを実行して新しいカバレッジを発見した場合当該入力をシードとしてファイルに保存、シードキューに追加、以上の繰り返しです。

実際の処理は以下のようなコードで概説できます。Algorithm 1で示されるファジングループと実行フローが同じことが確認できます。
(各ステートメントのコメントに、対応するAlgorithm 1の擬似コードを示しています)

while(true){
    /* conf ← SCHEDULE(C) */
    seed = select_seed(seed_queue); 

    /* tc ← INPUTGEN(conf) */
    new_input = mutate(seed.input);

    /* B',execinfos ← INPUTEVAL(tc, O_bug) */
    feedback = execute(new_input);

    /* C ← CONFUPDATE(C, tc, execinfos) */
    seed_queue = add_to_queue(seed_queue, new_input, feedback);
}

ファジングフレームワークによる共通化の試み

前述の通りAFLの実行フローはAlgorithm 1のファジングループと同じであり、各ステートメントも擬似コード中のプリミティブと1:1に対応していることが分かりました。AFLにパッチを当てたAFLFastやCollAFL、AFL++といったファザーも同様です。そのため近年ではAlgorithm 1のファジングループをベースに設計されたファジングフレームワーク([libAFL], [FOT], etc.)が登場し、これらのファザーを共通的に実装できるようにしようとする動きがあります。しかし同じファザーの中でも少し変わったファジングループを構成する物も存在します。

例えば2017年に提案されたVUzzerはAFLと同じくミューテーションベースドグレーボックスファザーですが、動的/静的プログラム解析技術を駆使して入力生成や評価値の計算に様々な工夫を加えています。サーベイ論文中では主にプログラム解析技術を用いて入力生成をガイドする`INPUTGEN`と、シードの評価値の計算を工夫したCONFUPDATEが他のファザーとの相違点として挙げられています。アルゴリズムを分類するという観点ではこれらの差異を挙げるだけで十分ですが、実装の観点においてはファジングループにも違いがあると言えるでしょう。実際のVUzzerのファジングループをフレームワークを使って実装するのは一筋縄では行きません。
なぜならVUzzerのファジングループは以下のようなコードになっているからです。
(各ステートメントのコメントに対応するAlgorithm 1の擬似コードを示しています)

while (true) {
    for(seed : seed_queue) { // conf ← SCHEDULE(C)
        /* B',execinfos ← INPUTEVAL(conf, O_bug) */
        feedback = execute(seed.input);
        seed.score = calc_score(feedback);
        if (has_new_cov(feedback)) {
            /* execinfos2 ← INPUTEVAL(conf, O_bug) */
            feedback2 = execute_with_taint(seed.input);
            taints += feedback2;
        }      
    } 
    /* conf ← SCHEDULE(C) */
    seed = select_seed(seed_queue);
    /* tc ← INPUTGEN(conf, execinfos2_set) */
    new_input = mutate(seed.input, taints);
    /* C ← CONFUPDATE(C, tc) */
    seed_queue = add_to_queue(seed_queue, new_input);
    /* C ← CONFUPDATE(C) */
    seed_queue = update_queue(seed_queue);
}

VUzzerはまずシードキューの各シードに対して、プログラムを実行してカバレッジを計測、それを元にシードの評価値を計算します。新しいカバレッジを発見していた場合、動的テイント解析器でプログラムを再度実行しなおしテイント情報を取得します。以上の処理をキュー内の全てのシードに対して実施します。続いてシードキューからいくつかシードを選択し、一部に変異を加えて入力を生成、これを新しいシードとしてキューに追加します。その後シードキューから最大の評価値を持つ物以外全てのシードを消去します。(ただし直前のミューテーションで生成したシードは除く)

以上の処理をAlgorithm 1のファジングループに対応させることは困難です。実際上記コメント中に対応する擬似コードを記載しましたが、Algorithm 1と比べて各関数を実行する順番や回数、受け渡しているデータの種類も異なっています。そのため必然的にAlgorithm 1をベースにしたファジングフレームワーク上でVUzzerを実装することはできません。

ファザーの乱立

Algorithm 1のように高度に抽象化されたモデルはあくまでファザーの分類を目的として設計された物であり、実際の実装をフレームワークとして共通化するためにはVUzzerを始めとする特殊なループも考慮する必要があることが分かりました。(他にもEclipserなどループが特殊なファザーは数多く存在します)
そのため、2021年現在においても、新しいファザーを開発する際に既存のファジングフレームワークを利用できることの方が稀です。大抵の場合、新しいファザーを開発する際は以下の2つの方法を取らざるをえません:

(a) 既存のファザーにパッチを当てる 

(b) ファザー全体をフルスクラッチで実装する

(a)の方法は既にあるファザーの資産を流用する事で研究者の本質的なコントリビューションが見えやすくなる利点がありますが、大元のコードの変更に追従できなくなり再利用性が著しく落ちてしまう欠点があります。特に、アカデミアの分野では自分のキーアイデアを手早く実現したい研究者がProof of Conceptを実装するために、パッチを採用している例が多く見られます[AFLFast, MOpt, IJON, NEZHA]。自身のコントリビューションにのみ注力する方法としてこれは合理的な手段ですが、他の研究者が使用する際は、パッチが適用される前の(オリジナルの)ファザーと適用された後の(新規の)ファザーの双方に注意を払う必要が生じます。新規のファザーが放置され、オリジナルのファザーのみが更新され続けた場合、いつか比較実験が不可能になるかもしれません。

(b)は他のファザーのコードとの整合性を気にする必要はありませんが、多くのファザーに共通する機能まで再発明を行う必要が生じ、膨大な実装コストを要します。また全てが独自実装であるため本質的なコントリビューションがどこにあるのかが見えづらくなります。
更には、既存のファザーとの比較も困難になってしまう欠点があります。フルスクラッチで開発されたファザーが新しい脆弱性を見つけることはよくあります。しかしそれは本当にキーアイデアの部分が効いているのか、ファザー全体のアルゴリズムや実装が刷新されたことで、既存のファザーでは発見されていなかった脆弱性が見つかりやすくなっただけなのか、判断が難しいところです。最悪の場合、キーアイデアのみを抜き出して既存のファザーに載せると実は性能が出なかったという現象も起こりえます。

理想のファジングフレームワーク

以上より、ファジングフレームワークに必要とされる要件は

  • 多様なファジングアルゴリズムの実行フローを再現できること
  • アルゴリズムの本質的なコントリビューションが見えやすいこと(つまり、パッチを使用することなしに、コードの差分を見やすくこと)
  • 共通するコンポーネントを資産として使い回せること

です。

fuzzuf

こういった理想を叶えるため、我々はfuzzufを開発しました。fuzzufは、C++17で実装されたファジングフレームワークで、現時点で AFL [AFL], AFLFast [AFLFast], VUzzer [VUzzer], libFuzzer [libFuzzer], NEZHA [NEZHA] の5種類のアルゴリズムを実装しています。

fuzzufは、前述した問題に対処するため、HierarFlow(Hierarchical Flow)と呼ばれる独自のDSLを搭載しています。HierarFlowを上手く活用することで、視覚的に分かりやすく実行フローを定義することができます。その上、定義した実行フローは、後から改変することが容易です。本章の以降の節では、@hugeh0geがHierarFlowを考案するに至った経緯やHierarFlowの利点を解説します。

なぜHierarFlowが必要になったか

fuzzufを開発し始めた当初、HierarFlowと呼ばれるDSLは存在していませんでした。我々は、かねてからファザーが乱立し統一性がないことに問題を感じており、それを解決するために便利なフレームワークが必要であることは分かっていましたが、フレームワークにどのような機能があればいいのかということについては答えを持ち合わせていませんでした。

そこで私は、まずAFLをC++17でリライトしてみるところから始めました。8000行を超えるC言語のコードを読解し、C++17に移植する作業は大変なものでしたが、多くの知見を得ることができました。AFLをリライトする前、私はAlgorithm 1のような形で一般化すれば大抵のファザーはきれいに書くことができると信じていました。Algorithm 1のような形で記述できないファザーはごく少数であって、それについては何らかのうまい解決策を後から考えればよく、ひとまずはAlgorithm 1のような形の一般化を、そのための基底クラスなどを設計して提供すればよいと。

しかしながら、現実には、AFLですら綺麗に書くことができないということに気づきました。なぜ綺麗に書けないでしょうか? これについて考えるのに、先ほどよりもう少し詳細なAFLの擬似コードを見てみましょう:

while (1) {
     /* conf ← SCHEDULE(C) */
    seed = select_seed(seed_queue); 

    while (!did_bitflips_enough) { // will be true at some point
        /* tc ← INPUTGEN(conf) */
        new_input1 = mutate_by_bitflip(seed);
        /* B',execinfos ← INPUTEVAL(tc, O_bug) */
        feedback1 = execute(new_input);
        /* C ← CONFUPDATE(C, tc, execinfos) */
        seed_queue = add_to_queue(seed_queue, new_input1, feedback1);
    }
    
    while (!did_byteflips_enough) { // will be true at some point
        /* tc ← INPUTGEN(conf) */
        new_input2 = mutate_by_byteflip(seed);
        /* B',execinfos' ← INPUTEVAL(tc, O_bug) */
        feedback2 = execute(new_input);
        /* C ← CONFUPDATE(C, tc, execinfos) */
        opt_info = build_info_for_optimization(feedback2);
        seed_queue = add_to_queue(seed_queue, new_input2, feedback2);
    }
    
    while (!did_arithops_enough) { // will be true at some point
        /* tc ← INPUTGEN(conf, execinfos') */
        new_input3 = mutate_by_arithop(seed, opt_info);
        /* B',execinfos ← INPUTEVAL(tc, O_bug) */
        feedback3 = execute(new_input);
        /* C ← CONFUPDATE(C, tc, execinfos) */
        seed_queue = add_to_queue(seed_queue, new_input3, feedback3);
    }
    
    while (!did_dictionaries_enough) { // will be true at some point
        /* tc ← INPUTGEN(conf, execinfos') */
        new_input4 = mutate_by_dictionary(seed, opt_info);
        
    ...

見ての通り、ミューテーションには様々な種類があります。言い換えると、擬似コードにおけるINPUTGENが行う操作は状況によって変化するのです。更に、よく見ると、INPUTGENは前回の実行結果(opt_info)に依存して振る舞いを変える必要もあります。私はここで初めて、AFLのアルゴリズムは、理想的な擬似コードほど、対称な操作を行い続けるわけではないということを理解しました。

これを素直にAlgorithm 1のような形で一般化して実装するとどのようになるでしょうか。一番始めに書いた私のコードは以下のようになりました:

INPUTGEN(seed) {
    if (current_state == "bitflip") {
        new_input = do_bitflip(seed);
        INPUTEVAL(new_input);
        if (did_enough_bitflips) {
            current_state = "byteflip";
        }
        return;
    } else if (current_state == "byteflip") {
        new_input = do_byteflip(seed);
        INPUTEVAL(new_input); // CONFUPDATE will update opt_info
        if (did_enough_byteflips) {
            current_state = "arith_operation";
        }
        return;
    } else if (current_state == "arith_operation") {
        new_input = do_arithop(seed, opt_info);
        INPUTEVAL(new_input); // creates opt_info
        if (did_enough_arithops) {
            current_state = "arith_operation";
        }
        return;
    }
    
    ...
} 

バカらしいことに、INPUTGENの内部で「INPUTGENは今何をすべきか」を表す 状態 を持たなければならないのです。これは、まったくもって書きやすいとは言えません。また、読みやすくもありません。「current_state"bitflip"なら、INPUTGENではここが実行されて、CONFUPDATEではここが実行されて…」ということを考えながら読んでいく必要があります。

何がいけなかったのでしょうか。それは、以下の図のように、実行フローを縦に割ろうとしたのがいけないのです:

“Collect Information for Optimization” のように、各ステップごとで全く異なる処理が行われる可能性も考慮すると、横に割るほうが良いのではないでしょうか:

コードの流れを理解する上でも、こちらのほうが自然です。こうしてみると、各ステップにおいて、mutate, execute, updateの3種類の操作があり、それらを複数個登録できれば十分そうに見えます。例えば、「Executeイベント・Updateイベントという2種類のイベントを持ったObserverパターン」などで対処できそうですし、実際にそのような設計になっているファジングフレームワークもあります。

しかしながら、実はこれはAlgorithm 1の擬似コードを少し一般化したものにすぎません。暗黙のうちに、「mutate -> execute -> update」の順でファジングが進んでいくということを仮定しています(もちろん、AFLの場合はそれで十分ではあります)。この順番では解釈できないアルゴリズムは、無理やり表現することになるでしょう。あるいは、「単一の入力ではなく、入力の集合を受け取り、遺伝的アルゴリズムなどで新しい入力の集合を生成するアルゴリズム」なども、(初めから集合を受け取る形で一般化しておかない限り)表現しづらいのではないでしょうか。どうしても、設計の変更が必要になりそうです。

HierarFlow: ファジングアルゴリズムを記述するためのDSL

こうした例外を全て内包しつつ、ファジングアルゴリズムを書きやすく、見やすくするために考え出されたのが、HierarFlowです。HierarFlowは端的に表現するならば、「関数に親子関係をもたせ、木構造にしたもの」です。具体的な定義の例を見てみましょう。例えば、現行のfuzzufにおいては、AFLのHierarFlowは以下の様に定義されています:

    fuzz_loop << (
         cull_queue
      || select_seed
    );

    select_seed << (
         consider_skip_mut
      || retry_calibrate
      || trim_case
      || calc_score
      || apply_det_muts << (
             bit_flip1 << execute << (normal_update || construct_auto_dict)
          || bit_flip_other << execute.HardLink() << normal_update.HardLink()
          || byte_flip1 << execute.HardLink() << (normal_update.HardLink()
                                               || construct_eff_map)
          || byte_flip_other << execute.HardLink() << normal_update.HardLink()
          || arith << execute.HardLink() << normal_update.HardLink()
          || interest << execute.HardLink() << normal_update.HardLink()
          || user_dict_overwrite << execute.HardLink() << normal_update.HardLink()
          || auto_dict_overwrite << execute.HardLink() << normal_update.HardLink()
         )
       || apply_rand_muts << (
               havoc << execute.HardLink() << normal_update.HardLink()
            || splicing << execute.HardLink() << normal_update.HardLink()
          )
       || abandon_node
    );

この定義に対応する木構造は以下のように図示されます:

HierarFlowはDSLといっても、独自の定義文法や定義ファイル、パーサーを持っているわけではなく、C++の演算子オーバーロードを用いて実現されています。fuzz_loopcull_queueなどの変数は、それぞれ関数(のようなもの)を表しています。

演算子a << bによって、木構造上でaの子がbであること、演算子a || bによって、木構造上でabが兄弟関係にあることを表します。例えば、bit_flip1は子としてexecuteを持ち、そのexecuteは子としてnormal_updateおよびconstruct_auto_dictを持ちます。

親ノード(親関数)は子ノード(子関数)を任意の回数、任意の引数で呼び出すことができます。ここで、同じ親を持つ子ノードは、全て同一の引数型を持たなければなりません。例えば、bit_flip1bit_flip_otherなどのノードは、apply_det_mutsという同一の親を持つため、全て同じ引数型で定義されています。

通常、兄弟ノードは、上(あるいは左)から順に実行されていきます。例えば、apply_det_mutsが子ノードを呼び出すと、bit_flip1から順に、bit_flip1bit_flip_other, …の順でノードが呼び出されていきます。並列に子ノードが呼び出されるわけではないことに注意してください。

ただし、子ノードは「次に実行される兄弟ノード」を変更することも可能です。例えば、apply_det_muts は、実行中に致命的なエラーが起きた場合には、apply_rand_mutsをスキップしてabandon_nodeに遷移し、ミューテーションを強制的に終了することがあります。

このように、Observerパターンのように特定の場所に任意の処理を挿入できるようにするのではなく、実行フロー全体の定義もできるようにしておくことで、非常に柔軟にアルゴリズムを表現できるようになります。

もしかすると、勘の良い人は「このHierarFlowというのは、ただの関数呼び出しとほとんど変わらないのではないか」という疑問を持ったかもしれません。誤解を恐れずに言えば、その通りです。HierarFlowを用いずとも、関数apply_det_mutsを定義し、内部で関数bit_flip1bit_flip_other, … を順番に呼び出せば、同じ実行フローを実現することは可能です。しかし、この実行フローを後から改変することを考えると、HierarFlowを利用していれば、変更差分を非常に小さくできることが分かります。詳細はなぜfuzzufはRustに移行しなかったのかという別の文書で説明されているので、気になった人はそちらを参考にしてください。

また、アルゴリズム全体の実行フローを、DSLによってひと目で確認できるという利点もあります。アカデミアの分野でfuzzufが活用されたときのことを想像すると、論文中の疑似コードと実際の実装の対応を検証しやすい方が安心でしょう。

Algorithm 1から逸脱した実行フローを持っていたVUzzerも、HierarFlowを用いることで以下の様にシンプルに表すことができます:

fuzz_loop <<
        decide_keep <<
        run_ehb << (
            execute << update_fitness << trim_queue
        ||  execute_taint << update_taint
        ||  mutate
        ||  update_queue
        );

ここまでの説明で、fuzzufの独自性はある程度伝わったかと思います。私は、自由度と簡潔さはトレードオフの関係にあると思います。Observerパターンのように、自由度を制限すれば制限しただけ、ユーザーが記述するコードは簡潔になっていきます。一方で、表現できるアルゴリズムの幅は減少するでしょう。fuzzufは、「いかなるファジングアルゴリズムも記述できるようにする」という無謀にも思える理念を持って設計されています。私は、この目的においては、HierarFlowという概念は一定の合理性を持っていると信じています。この取り組みを面白いと思った方、特に研究者の皆さんには、ぜひとも使ってもらえたら嬉しいです。

再現性とパフォーマンス

現在、fuzzufに実装されているアルゴリズムの再現性とパフォーマンスは、各アルゴリズムによってまちまちです。

例えば、AFLはコアのアルゴリズムがほとんど完全に再現されています。fuzzuf-afl-artifactレポジトリで確認されている通り、fuzzuf上のAFLは、オリジナルのAFLと一切振る舞いが変わらない上に、少し高速化されています。AFLFastについても、確認は行われていませんが、同様のクオリティであると考えられます(今後確認します)。

一方、fuzzufのlibFuzzerは、fuzzufのバイナリ計装(instrumentation)ツールが不完全であることに起因して、オリジナルのlibFuzzerの一部の機能を実装できていません。また、オリジナルのlibFuzzerはファジングの対象となるバイナリ内でファジングを行う(in-process fuzzing)ことが特徴であるものの、fuzzufのlibFuzzerはそのようになっていません。fuzzufはあくまでファジングアルゴリズムの理論的性能に主眼をおいており、現時点では提供形態にこだわりがないからです(組み込み用途など、応用性を考えるならば、2021年現在ではC言語で記述したほうが分がありそうですが、そうしていないことも同様の理由です)。ただし、AFLのpersistent modeを実装する予定はありますし、それによって同様の性能を得られると予想されています。

あるいは、VUzzerやNEZHAに至っては、オリジナルの実装とfuzzufの実装を比較することは、ほとんど不可能です:

  • VUzzerのオリジナルの実装はPython2で行われているため、C++とフェアに比較することは困難です。また、時間の経過と共にコンパイラの実装が変化した結果、VUzzerのテイントエンジンは最近のバイナリに対して正しくテイントできなくなってきています。
  • NEZHAは、ファジング対象のバイナリ(OpenSSLやLibreSSLなど)に手を加えることで実現(実験)されているアルゴリズムで、そもそもオリジナルの実装では、「任意のバイナリに対して適用可能な手法」とは言えない状態です。かといって、我々がSSLライブラリに対してのみ実装を行っても大して意味がありません。

これらのアルゴリズムについては、「何を持ってそのアルゴリズムが完成したと言えるのか」から吟味する必要があり、現時点では結論が出ていません。

こういった点で、fuzzufはまだアルファ版と呼ぶのがふさわしい状態ですが、今後ベンチマークを充実させつつ、対応できるものについては対応していく予定です。

今後の予定

今後、fuzzufは以下のような取り組みに着手する予定です。

アルゴリズムの拡充

現時点で実装する予定のあるアルゴリズムは以下のとおりです:

  •  IJON[IJON]
  •  MOpt[MOpt]
  •  Eclipser[Eclipser]
  •  QSYM[QSYM]
  •  DIE[DIE]

この内、IJONについては既に実装がほとんど完了しており、数ヶ月以内に取り込まれる予定です。

これらのアルゴリズムは、VUzzerやNEZHAと同様に、それぞれごとに事情が異なるため、「何をもって実装を完成した」とするかが難しい側面があります。例えば、Eclipserは、v1.0とv2.0で異なる設計となっており、どちらかを実装するのか(あるいはどちらも実装するのか)を議論しなければなりません。こういった議論についても、皆さんから意見をもらいたいと考えています。

Executorの汎化と拡充

現在、fuzzufは以下のExecutor(ファジングの対象となるバイナリを実行するクラス)を搭載しています:

  • NativeLinuxExecutor
  • PinToolExecutor
  • QEMUExecutor
  • CoreSightExecutor
  • PolyTrackerExecutor

ただし、この内、QEMUExecutorおよびCoreSightExecutorを利用しているファジングアルゴリズムは今の所存在しておらず、簡易的なテストコード上で使われているだけにとどまっています。

更にいえば、本来、各Executorクラスは交換可能性(interchangeability)を持っているべきですが、今のfuzzufではそうなっていません。言い換えると、各ファジングアルゴリズムはこれらのExecutorのうち1種類を固定で利用しています。本来、例えばEdge Coverageさえあれば動作するファザーは、PinToolExecutorやQEMUExecutor、および(計装ツールでEdge Coverageを計装されたバイナリとともに)NativeLinuxExecutorすべてと互換性を持っているはずです。これについては、Executorクラス群をリファクタリングすることで対応します。

また、現時点では実装がない、以下のExecutorについても、今後実装する予定です:

  • WindowsExecutor(WINNIE[WINNIE]などを参考に実装されるでしょう)
  • AndroidExecutor
  • その他、FridaやDynamoRIOといった動的計装ツールを用いたExecutor

ベンチマークの拡充

我々は、fuzzuf上に実装したアルゴリズムに、ベンチマークテストを十分に行っていません。現時点で実装されているアルゴリズムのほとんどは、比較的クラシカルなアルゴリズムであるため、既存の実装と比べて劇的に性能が良いということはありえないのですが、依然としてオリジナルのアルゴリズムと比べて性能低下が起きていないかなどを確認するためには、情報は多ければ多いほどいいでしょう。どのようなベンチマークを実施してほしいかについても、外部からの意見を募集しています。

その他の予定については、TODO.mdを確認してみてください。

想定ユースケースとコントリビューション

fuzzufは、全てのファジングの研究者にとって有用なフレームワークになることを目指しています。fuzzufベースで新しいファジングアルゴリズムを提唱、開発してもらえることを願っています。

また、あなたの実装したファジングアルゴリズムが新規性を持っていて、論文などの成果によってそれが認められたなら、ぜひPRを送ってfuzzufへマージさせてください。他の研究者にとって、比較や再現、改造がしやすくなるからです。

将来的には、様々なファジングアルゴリズムの理論的な性能を、fuzzuf上だけで比較できるようにしたいと考えています。このために、我々は今後も既存の有名なファジングアルゴリズムをfuzzuf上で実装していきます。これに関しても、外部からコントリビューションしていただけると非常に助かります。

おわりに

わたしたちRicerca Securityは、オフェンシブセキュリティの新たな価値を開拓するべく、今後も研究開発や脆弱性診断、ペネトレーションテストを積極的に進めてまいります。システムセキュリティやコンピュータサイエンスに対する理解や最新の研究にキャッチアップするスキルのある方、ペネトレーションテストや脆弱性診断といった実務経験に富んだ方を募集しています。ご興味のある方は採用情報をご覧ください。

謝辞

本研究は防衛装備庁令和2年度安全保障技術研究推進制度委託事業 (JPJ004596) の一環として実施したものです。

参考文献

[AFL]: Michal Zalewski. “american fuzzy lop” https://lcamtuf.coredump.cx/afl/

[libFuzzer]: “libFuzzer – a library for coverage-guided fuzz testing.” https://llvm.org/docs/LibFuzzer.html

[AFLFast]: Marcel Böhme, Van-Thuan Pham, and Abhik Roychoudhury. 2016. Coverage-based Greybox Fuzzing as Markov Chain. In Proceedings of the 23rd ACM Conference on Computer and Communications Security (CCS’16).

[VUzzer]:
Sanjay Rawat, Vivek Jain, Ashish Kumar, Lucian Cojocar, Cristiano Giuffrida, and Herbert Bos. 2017. VUzzer: Application-aware Evolutionary Fuzzing. In the Network and Distribution System Security (NDSS’17).

[MOpt]: Chenyang Lyu, Shouling Ji, Chao Zhang, Yuwei Li, Wei-Han Lee, Yu Song, and Raheem Beyah. 2019. MOpt: Optimized Mutation Scheduling for Fuzzers. In Proceedings of the 28th USENIX Security Symposium (Security’19).

[NEZHA]: Theofilos Petsios, Adrian Tang, Salvatore Stolfo, Angelos D. Keromytis, and Suman Jana. 2017. NEZHA: Efficient Domain-Independent Differential Testing. In Proceedings of the 38th IEEE Symposium on Security and Privacy (S&P’17).

[Eclipser]: Jaeseung Choi, Joonun Jang, Choongwoo Han, and Sang K. Cha. 2019. Grey-box Concolic Testing on Binary Code. In Proceedings of the 41st ACM/IEEE International Conference on Software Engineering (ICSE’19).

[QSYM]: Insu Yun, Sangho Lee, Meng Xu, Yeongjin Jang, and Taesoo Kim. 2018. QSYM : A Practical Concolic Execution Engine Tailored for Hybrid Fuzzing. In Proceedings of the 27th USENIX Security Symposium (Security’18).

[IJON]: Cornelius Aschermann, Sergej Schumilo, Ali Abbasi, and Thorsten Holz. 2020. IJON: Exploring Deep State Spaces via Fuzzing. In Proceedings of the 41st IEEE Symposium on Security and Privacy (S&P’20).

[AFL++]: Andrea Fioraldi, Dominik Maier, Heiko Eißfeldt, and Marc Heuse. 2020. AFL++: Combining Incremental Steps of Fuzzing Research. In Proceedings of the 14th USENIX Workshop on Offensive Technologies (WOOT’20).

[libAFL]: “LibAFL, the fuzzer library.” https://github.com/AFLplusplus/LibAFL

[FOT]: Hongxu Chen, Yuekang Li, Bihuan Chen, Yinxing Xue, and Yang Liu. 2018. FOT: A Versatile, Configurable, Extensible Fuzzing Framework. In Proceedings of the 26th ACM Joint Meeting on European Software Engineering Conference and Symposium on the Foundations of Software Engineering (ESEC/FSE’18).

[DIE]: Soyeon Park, Wen Xu, Insu Yun, Daehee Jang, and Taesoo Kim. 2020. Fuzzing JavaScript Engines with Aspect-preserving Mutation. In Proceedings of the 41st IEEE Symposium on Security and Privacy (S&P’20).

[WINNIE]: Jinho Jung, Stephen Tong, Hong Hu, Jungwon Lim, Yonghwi Jin, and Taesoo Kim. 2021. WINNIE: Fuzzing Windows Applications with Harness Synthesis and Fast Cloning. In the Network and Distribution System Security (NDSS’21).

[CollAFL]: Shuitao Gan, Chao Zhang, Xiaojun Qin, Xuwen Tu, Kang Li, Zhongyu Pei, and Zuoning Chen. 2018. CollAFL: Path Sensitive Fuzzing. In Proceedings of the 39th IEEE Symposium on Security and Privacy (S&P’18).


文章来源: https://ricercasecurity.blogspot.com/2021/12/fuzzuf-fuzzing-unification-framework_36.html
如有侵权请联系:admin#unsafe.sh