AFLによる高速のセキュリティ指向のファジング

静的コード分析など、開発プロセスで多くの人が聞いたことがあり、成功したものもあります。これは、コードの品質を制御するための、効果的で比較的高速で、しばしば便利な方法です。

すでに静的コード分析を使用している人にとっては、テスト段階で動的分析を試すことは興味深いかもしれません。 これらのメソッドの違いについては十分に書かれていますが、実行プロセス中に静的分析はコードを実行せずに(たとえば、コンパイル段階で)、動的にそれぞれ実行されることを思い出します。 セキュリティの観点からコンパイルされたコードを分析する場合、動的分析はしばしばファジングを意味します。 ファジングの利点は、誤検知がほぼ完全にないことです。これは、静的アナライザーを使用する場合に非常に一般的です。



「ファジングは、無効なデータ、意図しないデータ、またはランダムなデータをプログラムの入力に供給するテスト手法です。」©Habrahabr









最近、Michal Zalewskiの原作者であるAmerican Fuzzy Lopのファザーは大きな名声を得ました。

主な違いは、コンパイル段階でのコードのインストルメンテーション、パフォーマンス、および実際のアプリケーションへのフォーカスです。 AFLはSMTソルバーを使用しません。つまり、常に効率的であるとは限りませんが、リソースへの要求が少なく、より高速に動作するはずです。

今日は、このツールの使用方法を正確に説明すると同時に、小規模な実験を行って、その作業の結果をいくつかの静的分析ツールの結果と比較します。



そのため、ファザーの使用を開始するには、ファザーが実際に機能するかどうか、そして何をどのようにフェーズするかを理解する必要があります。

検証のために、人気のlibcurlライブラリの明らかに脆弱なバージョン、7.34.0を使用しました。

このバージョンには、 CVE-2015-3145で説明されているsanitize_cookie_path()関数に脆弱性が含まれています。



出血目



関数は入力データを誤って処理し、二重引用符またはヌルバイトで構成されるパスを渡すと、libcurlはnew_path配列の負のポインターにヌルバイトを割り当て、ヒープ上のメモリを破損します。



最初に、静的アナライザーがこの脆弱性にどのように応答するかをテストします。


CoverityPVS StudioClang Static Analyzerが手元にありました。



clangでは、これは次のように実行できます。



$ cd curl $ mkdir build-clang $ cd build-clang $ cmake -DCMAKE_C_COMPILER=/path/to/clang/ccc-analyzer -DCMAKE_CXX_COMPILER=/path/to/clang/ccc-analyzer -DCMAKE_BUILD_TYPE=release ../ $ scan-build -o html make
      
      







次に、 htmlディレクトリで分析結果を取得します。







Coverityはコンパイラー呼び出しをインターセプトし、アナライザーは次のパラメーターで起動されました。



 $ cov-analyze --dir cov --all --security --enable-constraint-fpp --enable-single-virtual --enable-fnptr --enable-callgraph-metrics -j 2 --inherit-taint-from-unions --override-worker-limit
      
      











PVS StudioにはWindowsが必要です。試用版では問題のあるファイルの名前は表示されませんが、行とエラーの種類は既にわかっているため、バイナリ評価にはこれで十分です。 PVSはモニターモードで実行され、アセンブリを簡単にするために、 build-libcurl-windowsスクリプトが使用されました。





(PVS Studio出力はすべて提供されません)



どの静的アナライザーも問題を発見しませんでした。



では、ファジングプロセスを開始する方法を見てみましょう。


まず、AFLをダウンロードして組み立てます。



 $ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz $ tar xvfz afl-latest.tgz $ cd afl-1.83b $ make $ cd llvm_mode $ make
      
      







通常、fazzerは新しいプロセスでアプリケーションを起動し、プロセスがクラッシュした場合にテストデータをSTDINまたは一時ファイルを使用してフィードします。AFLはこれに気付き、送信されたデータをcrashsディレクトリに書き込みます。 ファジングを成功させるための重要なポイントは、 Address Sanitizerを使用したアプリケーションのアセンブリです。そのため、1バイトの動的メモリを上書きしても、アプリケーションがクラッシュすることが保証されます。 ASANについては何も書きません。なぜなら これは何度説明されており、長い間成功裏に適用されてきました



テストを生成するには、いわゆる コーパス-アプリケーションが処理するテストデータのセット.curlの場合、これらはWebサーバーからの有効なHTTP応答です。



既知の脆弱性の場合、2つの方法があります。疑わしいと思われる特定の機能のファジングと、アプリケーション全体のファジングです。

最初のケースでは、アプリケーションに最小限のラッパーを作成する必要があります。



 int main(int argc, char **argv) { unsigned char buf[2048]; char *res = NULL; assert(argc == 2); FILE *f = fopen(argv[1], "rb"); assert(f); size_t len = fread(buf, 1, sizeof(buf), f); buf[len] = 0x00; if (len == 0 || strlen(buf) == 0) { return 0; } printf("read = %zu\n", len); printf("in = %s\n", buf); /* call the code which smell */ res = sanitize_cookie_path(buf); if (res) { printf("res = %s\n", res); free(res); } return 0; }
      
      







次に、ラッパーをインスツルメントする必要があります。ラッパーはAFLでアセンブルします



 $ afl-clang-fast -g -fsanitize=address path_san.c -o path_san
      
      







入力ディレクトリに、「/ xxx /」などの適切なURIを持つファイルを1つだけ配置します。

そして、AFLを実行します。



 $ AFL_USE_ASAN=1 /path/to/afl/afl-fuzz -m none -i inputs -o out ./path_san @@
      
      







-m noneパラメーターはメモリ制限を無効にし、 @@はファジング中に一時ファイルの名前に置き換えられます。このパラメーターが設定されていない場合、テストデータはSTDINに送信されます。 起動直後、AFLはクラッシュを検出し、 out / crashesディレクトリにテストエントリを生成します。



大きなプロジェクトでユーザー入力を処理する個々の関数のファジング戦略は、特にユニットテストがコード用にすでに記述されている場合、アプリケーション全体をファジングするよりも効果的です。

ただし、アプリケーション全体をファジングできると便利な場合があります。例として同じcurlを使用してこれを行う方法を見てみましょう。



知っているように、curlはソケットを介してサーバーとやり取りしますが、ファザーはこれを行う方法を知りません。つまり、ファザーからcurlにデータを転送する方法を学ぶ必要があります。

これを行うには、connect関数を置き換えて、新しい接続を作成する代わりに、connectの結果がstdinハンドルを返すようにします。

動的ライブラリのLD_PRELOADを介してこれを行うことができます。幸いなことに、これは記述する必要はありません-preenyを使用できます。



preenyとcurlをビルドしましょう:



 $ git clone https://github.com/zardus/preeny $ cd preeny && make ... $ cd curl $ mkdir build $ export CMAKE_C_FLAGS="-g -fsanitize=address" $ cmake -DCMAKE_C_COMPILER=/path/to/afl-clang-fast -DCMAKE_CXX_COMPILER=/path/to/afl-clang-fast -DCMAKE_BUILD_TYPE=release ../ $ make
      
      







収集したバイナリを1つのディレクトリに配置し、その隣に入力ディレクトリを作成し、その中にHTTPサーバーレスポンスを含むファイルを作成します(カバレッジを向上させるには、いくつか作成する方が良いです)。



例:



 HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1 Connection: close Set-Cookie: xx=xxx; path=xx; domain=xxx.com; httponly; secure; 1
      
      







その後、アプリケーションのあるディレクトリに戻り、AFLを実行します。



 $ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" /path/to/afl/afl-fuzz -m none -i inputs -o out ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null
      
      







ここでLD_PRELOADはパスをSOに設定します。これにより、接続機能が置き換えられます。



カールオプション:









AFLは数分で、アプリケーションのクラッシュにつながる最初のテストデータを見つけます。



これで、この入力でアプリケーションが本当にクラッシュすることを確認できます。



 $ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null < out/crashes/id:000010,sig:06,src:000000,op:havoc,rep:2
      
      











そこで、AFLを使用してアプリケーションをテストする方法を学びました。

テスト中にファジングを使用する場合、カバレッジが良好な最速かつ最も効果的なファザーでさえ、コードアナライザーを置き換えず、補足するだけであることを理解することが重要です。



関連リンクとソース:






All Articles