コンテンツにスキップ

上級

LibFuzzer を使用してインストゥルメントされた C/C++ ターゲット

logo llvm-logo

libFuzzer インストゥルメンテーション付きで C/C++ ターゲットをコンパイルする必要がありますか? このレッスンでは、Mayhem で C/C++ libFuzzer ターゲットをコンパイルし、テストする方法を手順を追って説明します。

学習時間の目安: 15 分

このレッスンを終了すると、以下のことができるようになります。

  1. 不適切な入力検証の欠陥がある C libFuzzer ターゲットをコンパイルし、ファジングする。
  2. 不適切な入力検証の欠陥がある C++ libFuzzer ターゲットをコンパイルし、ファジングする。

レッスンを駆け足で

始める前に前提条件を確認します。

  1. c-libfuzzer.tgz をダウンロードし、c-libfuzzer Docker イメージをビルドし、指定された Docker レジストリにプッシュします。

    docker build -t <DOCKERHUB_USERNAME>/c-libfuzzer .
    docker push <DOCKERHUB_USERNAME>/c-libfuzzer
    
    docker build -t $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer .
    docker push $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer
    
  2. Mayhem UI または Mayhem CLI で次の Mayhemfile を使用して c-libfuzzer Docker イメージに対して Mayhem ランを実行します。

    1
    2
    3
    4
    5
    6
    7
    image: <DOCKERHUB_USERNAME>/c-libfuzzer:latest
    duration: 90
    project: mayhem-examples
    target: c-libfuzzer
    cmds:
      - cmd: /mayhemit
        libfuzzer: true
    
    1
    2
    3
    4
    5
    6
    7
    image: $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer:latest
    duration: 90
    project: mayhem-examples
    target: c-libfuzzer
    cmds:
      - cmd: /mayhemit
        libfuzzer: true
    

以下が必要です。

  • Docker がインストールされていること
  • 有効なインターネット接続 (Docker Hub ベース イメージをプルするため)

ワン クリック テスト

次のボタンをクリックし、Create New Run フローの最後で Start Run をクリック して AFL インストゥルメンテーション付き C ターゲットのテストを開始します。Mayhemfile はすでに用意されているため、何も設定する必要はありません。

Mayhem ランが始まると、次のようなラン ページが表示されます。

c-libfuzzer-run

すばらしい! Mayhem が libFuzzer を使用してインストゥルメントされた C ターゲットをファジングできることを確認したので、次に、たった今 Mayhem ランを実行した libFuzzer ターゲットをコンパイルし、テストする方法を順を追って説明します。

LibFuzzer インストゥルメンテーション付き C ターゲットのコンパイルとテスト

ファイル: c-libfuzzer.tgz

上記の c-libfuzzer.tgz をダウンロードして展開し、次の mayhemit.c のソース コードを見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

int mayhemit(char *buf, unsigned len)
{
  if (len >= 3)
    if(buf[0] == 'b')
      if(buf[1] == 'u')
        if(buf[2] == 'g') {
          printf("You've got it!");
          abort(); // Defect: SIGABRT.
        }
  return 0;
}

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
  mayhemit((char *) Data, Size);
  return 0;
}

すぐに、ソース コードにいくつか追加のヘッダー ファイル (stddef.h および stdint.h ) があることに気づくでしょう。

さらに、ソース コードは通常の main 関数ではなく、LLVMFuzzerTestOneInput 関数を使用してバイト配列を受け取り、ターゲット関数 mayhemit のファジングに使用しています。入力が "bug" の場合、プログラムは不適切な入力検証のエラーでクラッシュします。

次に、関連する Dockerfile を調べます。

1
2
3
4
5
6
7
FROM fuzzers/libfuzzer:12.0
COPY mayhemit.c .
RUN clang-12 -fsanitize=fuzzer,address mayhemit.c -o /mayhemit 

# Set to fuzz!
ENTRYPOINT []
CMD /mayhemit
  • 行 1: libFuzzer 依存関係にアクセスするため、fuzzers/libfuzzer:12.0 ベース イメージがインポートされています。
  • 行 2: Docker コンテナーに mayhemit.c ソース コードがコピーされています。
  • 行 3: fsanitize パラメーターを指定して clang コンパイラを使用し、mayhemit 実行ファイルをコンパイルして libFuzzer ライブラリにリンクしています。
  • 行 7: Docker コンテナーのデフォルト実行ファイルとして /mayhemit 実行ファイルが設定されています。

Info

clang および -fsanitize の使用に関する詳細については、libFuzzer usage の公式ドキュメントを参照してください。

次に、docker build および docker push コマンドを使用して、Docker イメージをビルドして Docker Hub レジストリにプッシュする必要があります。

次に、docker build および docker push コマンドを使用して、Docker イメージをビルドして Mayhem サーバーにプッシュする必要があります。$MAYHEM_DOCKER_REGISTRY は、プライベートな Mayhem Docker レジストリの URL を表します。

docker build -t <DOCKERHUB_USERNAME>/c-libfuzzer .
docker push <DOCKERHUB_USERNAME>/c-libfuzzer
docker build -t $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer .
docker push $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer

情報

mayhem login コマンドを使用して内部的な Mayhem Docker レジストリの URL を検索し、次のコマンドを使用して DOCKER_REGISTRY 環境変数を設定できます:

export DOCKER_REGISTRY=tutorial.forallsecure.com:5000
ここでは、サンプルの Docker レジストリ URL を設定していますが、DOCKER_REGISTRY 環境変数に自身の Mayhem Docker レジストリ URL を設定する必要があります。

新しく作成した Docker イメージをパブリックな Docker Hub レジストリに正常にプッシュしたら、Mayhem UI から新規ランを作成し、<DOCKERHUB_USERNAME>/c-libfuzzer Docker イメージを検索します。Mayhemfile が次のようになっていることを確認します。

新しく作成した Docker イメージをプライベートな Mayhem Docker レジストリに正常にプッシュしたら、Mayhem UI から新規ランを作成し、forallsecure/c-libfuzzer Docker イメージを検索します。Mayhemfile が次のようになっていることを確認します。

1
2
3
4
5
6
7
image: <DOCKERHUB_USERNAME>/c-libfuzzer:latest
duration: 90
project: mayhem-examples
target: c-libfuzzer
cmds:
  - cmd: /mayhemit
    libfuzzer: true
1
2
3
4
5
6
7
image: $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer:latest
duration: 90
project: mayhem-examples
target: c-libfuzzer
cmds:
  - cmd: /mayhemit
    libfuzzer: true

新規ランの作成フローの最後に到達するまで [Next] をクリックし、[Start Run] をクリックして Mayhem ランを実行します。次のようなラン ページが表示されます。

c-libfuzzer-run

おめでとうございます! Mayhem で C libFuzzer ターゲットのファジングが成功しました。

現実的な演習: LibFuzzer インストゥルメンテーション付き C++ ターゲットのコンパイルとテスト

libFuzzer インストゥルメンテーション付き C ターゲットのコンパイルおよびテスト手順がわかったので、次に、同じことを境界外欠陥がある libFuzzer インストゥルメンテーション付き C++ ターゲットに対して行うことができるか、やってみましょう。

ファイル: mayhemit-out-of-bounds-unsolved.zip

手順

  • mayhemit.c ソース コードを変更し、インデックス境界外欠陥を追加します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int mayhemit(char *buf, unsigned len)
{
  if (len >= 3 && len < 5)
    if(buf[0] == 'b')
      if(buf[1] == 'u')
        if(buf[2] == 'g') {
          printf("You've got it!");
          return buf[10];
        }
  return 0;
}
  • docker build コマンドを使用して Dockerfile を再ビルドし、結果の Docker イメージを <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds としてタグ付けします。
  • docker push コマンドを使用してパブリックな Docker Hub レジストリに <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをプッシュします。
  • Mayhem UI または Mayhem CLI を使用して <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをファジングします。Mayhemfile が適切に設定されていることを確認します。
  • docker build コマンドを使用して Dockerfile を再ビルドし、結果の Docker イメージを cpp-libfuzzer-mayhemit-out-of-bounds としてタグ付けします。
  • docker push コマンドを使用してプライベートな Mayhem Docker レジストリに cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをプッシュします。
  • Mayhem UI または Mayhem CLI を使用して cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをテストします。Mayhemfile が適切に設定されていることを確認します。

🔍 確認LibFuzzer C++ ターゲットのコンパイルとテスト

解答

模範解答: mayhemit-out-of-bounds-solved.zip

結果を確認しましょう。まず、cpp-libfuzzer-mayhemit-out-of-bounds ターゲットのソース コードを見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

int mayhemit(char *buf, unsigned len)
{
  if (len >= 3 && len < 5)
    if(buf[0] == 'b')
      if(buf[1] == 'u')
        if(buf[2] == 'g') {
          printf("You've got it!");
          return buf[10];
        }
  return 0;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
  mayhemit((char *) Data, Size);
  return 0;
}

c-libfuzzer ターゲットのソース コードと比較したとき、extern "C" キーワードが使用されているという違いがあることに気づいたかもしれません。extern "C" キーワードは、C++ の関数名に C リンケージを持たせ、クライアントの C コードが関数の宣言だけを含む C 互換ヘッダー ファイルを使用して関数にリンク (使用) できるようにします。

いっぽう、cpp-libfuzzer-mayhemit-out-of-bounds ターゲットに対応する Dockerfile には、バイナリをコンパイルするには、clang++ C++ コンパイラと fsanitize パラメーターを組み合わせて mayhemit 実行ファイルをコンパイルし、libFuzzer ライブラリにリンクするよう指定されています。

1
2
3
4
5
6
7
FROM fuzzers/libfuzzer:12.0
COPY mayhemit.cpp .
RUN clang++-12 -fsanitize=fuzzer,address -fno-inline mayhemit.cpp -o /mayhemit 

# Set to fuzz!
ENTRYPOINT []
CMD /mayhemit

次に、結果の cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをビルドし、タグを付け、Docker Hub レジストリにプッシュする必要がああります。

次に、結果の cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージをビルドし、タグを付け、Mayhem Docker レジストリにプッシュする必要がああります。

docker build -f Dockerfile -t <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds .
docker push <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds
docker build -f Dockerfile -t $MAYHEM_DOCKER_REGISTRY/cpp-libfuzzer-mayhemit-out-of-bounds .
docker push $MAYHEM_DOCKER_REGISTRY/cpp-libfuzzer-mayhemit-out-of-bounds

別の方法として、付属の Makefil を使用し、MAYHEM_DOCKER_REGISTRY` 環境変数を設定して次のコマンドを実行することで、簡単に結果の Docker イメージをビルドし、プッシュすることもできます。

make build
make push

最後に、Mayhem UI または Mayhem CLI を使用して、アップロードされた <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージに対して Mayhem ランを実行します。Mayhemfile は次のようになっているはずです。

最後に、Mayhem UI または Mayhem CLI を使用して、アップロードされた cpp-libfuzzer-mayhemit-out-of-bounds Docker イメージに対して Mayhem ランを実行します。Mayhemfile は次のようになっているはずです。

1
2
3
4
5
6
7
image: <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds:latest
duration: 90
project: mayhem-examples
target: cpp-libfuzzer-mayhemit-out-of-bounds
cmds:
  - cmd: /mayhemit
    libfuzzer: true
1
2
3
4
5
6
7
image: $MAYHEM_DOCKER_REGISTRY/cpp-libfuzzer-mayhemit-out-of-bounds:latest
duration: 90
project: mayhem-examples
target: cpp-libfuzzer-mayhemit-out-of-bounds
cmds:
  - cmd: /mayhemit
    libfuzzer: true

最終的なラン ページは次のように表示されるはずです。

cpp-mayhemit-out-of-bounds-run

おめでとうございます! Mayhem がインデックス境界外の欠陥を発見しました。スクラッチから libFuzzer C++ ターゲットをビルドし、Mayhem を使用してバグを検出できました。

✏️ まとめと振り返り

このレッスンでは、Mayhem で libFuzzer を使用してインストゥルメントされた C/C++ ターゲットをコンパイルし、テストする方法を学びました。


学習内容

1. 不適切な入力検証の欠陥がある C libFuzzer ターゲットをコンパイルし、テストする。
  • ソース コードには次の欠陥が含まれているはずです:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    int mayhemit(char *buf, unsigned len)
    {
      if (len >= 3)
        if(buf[0] == 'b')
          if(buf[1] == 'u')
            if(buf[2] == 'g') {
              printf("You've got it!");
              abort(); // Defect: SIGABRT.
            }
      return 0;
    }
    

  • C libFuzzer ターゲットをファジングするには、次の Dockerfile および Mayhemfile を使用して C プログラムを含む Docker イメージをビルドし、Mayhem でファジングを実行します。

    1
    2
    3
    4
    5
    6
    7
    FROM fuzzers/libfuzzer:12.0
    COPY mayhemit.c .
    RUN clang-12 -fsanitize=fuzzer,address mayhemit.c -o /mayhemit 
    
    # Set to fuzz!
    ENTRYPOINT []
    CMD /mayhemit
    

    1
    2
    3
    4
    5
    6
    7
    image: <DOCKERHUB_USERNAME>/c-libfuzzer:latest
    duration: 90
    project: mayhem-examples
    target: c-libfuzzer
    cmds:
      - cmd: /mayhemit
        libfuzzer: true
    
    1
    2
    3
    4
    5
    6
    7
    image: $MAYHEM_DOCKER_REGISTRY/forallsecure/c-libfuzzer:latest
    duration: 90
    project: mayhem-examples
    target: c-libfuzzer
    cmds:
      - cmd: /mayhemit
        libfuzzer: true
    
2. 境界外の欠陥がある C++ libFuzzer ターゲットをコンパイルし、ファジングする。
  • ソース コードには次の欠陥が含まれているはずです:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    int mayhemit(char *buf, unsigned len)
    {
      if (len >= 3 && len < 5)
        if(buf[0] == 'b')
          if(buf[1] == 'u')
            if(buf[2] == 'g') {
              printf("You've got it!");
              return buf[10];
            }
      return 0;
    }
    

  • C++ libFuzzer ターゲットをファジングするには、次の Dockerfile および Mayhemfile を使用して C++ プログラムを含む Docker イメージをビルドし、Mayhem でファジングを実行します。

    1
    2
    3
    4
    5
    6
    7
    FROM fuzzers/libfuzzer:12.0
    COPY mayhemit.cpp .
    RUN clang++-12 -fsanitize=fuzzer,address -fno-inline mayhemit.cpp -o /mayhemit 
    
    # Set to fuzz!
    ENTRYPOINT []
    CMD /mayhemit
    

    1
    2
    3
    4
    5
    6
    7
    image: <DOCKERHUB_USERNAME>/cpp-libfuzzer-mayhemit-out-of-bounds:latest
    duration: 90
    project: mayhem-examples
    target: cpp-libfuzzer-mayhemit-out-of-bounds
    cmds:
      - cmd: /mayhemit
        libfuzzer: true
    
    1
    2
    3
    4
    5
    6
    7
    image: $MAYHEM_DOCKER_REGISTRY/cpp-libfuzzer-mayhemit-out-of-bounds:latest
    duration: 90
    project: mayhem-examples
    target: mayhemit-out-of-bounds
    cmds:
      - cmd: /mayhemit
        libfuzzer: true