コンテンツにスキップ

上級

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

logo google-logo aflpp-logo

Google の AFL インストゥルメンテーションまたはさらに拡張された AFL++ インストゥルメンテーション付きでターゲットをコンパイルする必要がありますか? このレッスンでは、AFL/AFL++ インストゥルメンテーション付きで C/C++ ターゲットをコンパイルし、結果のバイナリを Mayhem でテストする方法を順を追って説明します。

学習時間の目安: 15 分

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

  1. AFL インストゥルメンテーション付き C ターゲットをコンパイルし、ファジングする。
  2. AFL++ インストゥルメンテーション付き C ターゲットをコンパイルし、ファジングする。
  3. AFL インストゥルメンテーション付き C++ ターゲットをコンパイルし、ファジングする。

レッスンを駆け足で

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

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

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

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

以下が必要です。

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

ワン クリック テスト

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

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

c-afl-gcc-run

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

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

ファイル: c-afl-gcc.tgz

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>

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

int main(int argc, char *argv[])
{
  FILE *f;
  char buf[12];

  if(argc != 2){
    fprintf(stderr, "Must supply a text file\n");
    return -1;
  }
  f = fopen(argv[1], "r");
  if(f == NULL){
    fprintf(stderr, "Could not open %s\n", argv[1]);
    return -1;
  }
  if(fgets(buf, sizeof(buf), f) == NULL){
    fprintf(stderr, "Could not read from %s\n", argv[1]);
    return -1;
  }
  mayhemit(buf);
  return 0;
}

Note

すでに c-uninstrumented ターゲットをよく知っている場合、ソース コードがまったく同じであることに気づくでしょう。では、C-AFL ターゲットの違いは何でしょうか? 違いは、プログラムのコンパイル方法にあります (少なくとも AFL の場合)。

関連する Dockerfile を見てみると、次の操作があります。

1
2
3
4
5
6
7
8
FROM fuzzers/afl:2.52
COPY mayhemit.c .
RUN afl-gcc mayhemit.c -o /mayhemit
RUN mkdir /testsuite && echo seed > /testsuite/seed

# Set to fuzz!
ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
CMD ["/mayhemit", "@@"]
  • 行 1: 新規 Docker コンテナーのベース イメージとして fuzzers/afl:2.52 イメージがインポートされています。
  • 行 2: Docker コンテナーに mayhemit.c ソース コードがコピーされています。
  • 行 3: afl-gcc C コンパイラによって mayhemit.c がコンパイルされ、AFL インストゥルメンテーションが付加されて mayhemit 実行ファイルになります。
  • 行 8: Docker コンテナーのデフォルト実行ファイルとして /mayhemit @@ コマンドが設定されています。

Note

行 7 のエントリポイント コードは、コンテナーが実行されるとき、(デバッグ目的で) AFL が Docker コンテナー内でローカルに mayhemit 実行ファイルをファジングできるようにするためにあります。Docker コンテナーをビルドしたら、docker run -it <container> コマンドを実行して動作を確認します。 Mayhem でエントリポイント スクリプトを実行する方法の詳細については、「Mayhem での Docker エントリポイントのサポート」を参照してください。

あとは、これまでと同じように、Docker イメージをビルドして Docker レジストリにプッシュするだけです。

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

最後に、<DOCKERHUB_USERNAME>/c-afl-gcc Docker イメージを検索し、Mayhem UI で新規ランの作成フローに従って、Mayhem で 新しい Docker イメージのファジングを実行します。

最後に、forallsecure/c-afl-gcc Docker イメージを検索し、Mayhem UI で新規ランの作成フローに従って、Mayhem で 新しい Docker イメージのファジングを実行します。

ランの Mayhemfile が次のようになっていることを確認します。

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

よくできました! AFL インストゥルメンテーション付き C ターゲットのコンパイルとテストが成功しました。

c-afl-gcc-run

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

ファイル: c-aflpp-gcc.tgz

今度は、前に使用した mayhemit.c ソース コードを AFL++ インストゥルメンテーション付きでコンパイルします。ソース コードは同じであるため、afl-g++-fast コンパイラを使用するだけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>

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

int main(int argc, char *argv[])
{
  FILE *f;
  char buf[12];

  if(argc != 2){
    fprintf(stderr, "Must supply a text file\n");
    return -1;
  }
  f = fopen(argv[1], "r");
  if(f == NULL){
    fprintf(stderr, "Could not open %s\n", argv[1]);
    return -1;
  }
  if(fgets(buf, sizeof(buf), f) == NULL){
    fprintf(stderr, "Could not read from %s\n", argv[1]);
    return -1;
  }
  mayhemit(buf);
  return 0;
}

c-aflpp-gcc フォルダーに移動し、c-aflpp-gcc ターゲットの Dockerfile を見てみましょう。

c-aflpp-gcc ターゲットをビルドするプロセスは c-afl-gcc ターゲットのビルドとほとんど同じであることがわかるでしょう。違いは、行 1 で AFL++ 依存関係を含む fuzzers/aflplusplus:3.12c ベース イメージをインポートしていることと、行 3afl-gcc-fast コンパイラを使用して mayhemit.c ソース コードをコンパイルしていることです。

1
2
3
4
5
6
7
8
FROM fuzzers/aflplusplus:3.12c
COPY mayhemit.c .
RUN afl-gcc-fast mayhemit.c -o /mayhemit 
RUN mkdir /testsuite && echo seed > /testsuite/seed

# Set to fuzz!
ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
CMD ["/mayhemit", "@@"]

次に、mayhemit/c-aflpp-gcc Docker イメージをビルドし、Mayhem Docker レジストリにプッシュする必要があります。

次に、mayhemit/c-aflpp-gcc Docker イメージをビルドし、Mayhem Docker レジストリにプッシュする必要があります。

docker build -t <DOCKERHUB_USERNAME>/c-aflpp-gcc .
docker push <DOCKERHUB_USERNAME>/c-aflpp-gcc
docker build -t $DOCKER_REGISTRY/forallsecure/c-aflpp-gcc .
docker push $DOCKER_REGISTRY/forallsecure/c-aflpp-gcc

その後、新規 Mayhem ランのソースとして <DOCKERHUB_USERNAME>/c-aflpp-gcc Docker イメージを選択し、Mayhemfile が次のようになっていることを確認します。

その後、新規 Mayhem ランのソースとして forallsecure/c-aflpp-gcc Docker イメージを選択し、Mayhemfile が次のようになっていることを確認します。

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

最後に、Mayhem UI で新規 <DOCKERHUB_USERNAME>/c-aflpp-gcc Docker イメージをファジングします。

最後に、Mayhem UI で新規 forallsecure/c-aflpp-gcc Docker イメージをファジングします。

c-aflpp-gcc-run

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

ファイル: cpp-afl-gcc.tgz

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

手順

  • 上記の cpp-afl-gcc.tgz をダウンロードし、cpp-afl-gcc フォルダーを展開します。
  • cpp-afl-gcc フォルダーに移動し、<DOCKERHUB_USERNAME>/cpp-afl-gcc ターゲットをビルドして Docker Hub にプッシュします。
  • 最後に、Mayhem UI から、新しくアップロードした <DOCKERHUB_USERNAME>/cpp-afl-gc Docker イメージをファジングします。

  • 上記の cpp-afl-gcc.tgz をダウンロードし、cpp-afl-gcc フォルダーを展開します。

  • cpp-afl-gcc フォルダーに移動し、forallsecure/cpp-afl-gcc ターゲットをビルドして Mayhem にプッシュします。
  • 最後に、Mayhem UI から、新しくアップロードした forallsecure/cpp-afl-gc Docker イメージをテストします。

🔍 確認 AFL インストゥルメンテーション付き C++ ターゲットのコンパイルとテスト

解答

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string.h>

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

int main(int argc, char *argv[])
{
  FILE *f;
  char buf[12];

  if(argc != 2){
    fprintf(stderr, "Must supply a text file\n");
    return -1;
  }
  f = fopen(argv[1], "r");
  if(f == NULL){
    fprintf(stderr, "Could not open %s\n", argv[1]);
    return -1;
  }
  if(fgets(buf, sizeof(buf), f) == NULL){
    fprintf(stderr, "Could not read from %s\n", argv[1]);
    return -1;
  }
  mayhemit(buf);
  return 0;
}

ソース コードはこれまでの c-afl-gcc および c-aflpp-gcc ターゲットとほとんど同じですが、C++ の iostream ヘッダー ファイルを使用している点が異なります。

対応する Dockerfile を見ると、AFL インストゥルメンテーション付きで mayhemit.cpp ソース ファイルをコンパイルするには、afl-g++ コンパイラを使用する必要があることがわかります。

1
2
3
4
5
6
7
8
FROM fuzzers/afl:2.52
COPY mayhemit.cpp .
RUN afl-g++ mayhemit.cpp -o /mayhemit
RUN mkdir /testsuite && echo seed > /testsuite/seed

# Set to fuzz!
ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
CMD ["/mayhemit", "@@"]

次に、Docker イメージをビルドしてパブリックな Docker Hub レジストリにプッシュします。

次に、Docker イメージをビルドして Mayhem Docker レジストリにプッシュします。

docker build -t <DOCKERHUB_USERNAME>/cpp-afl-gcc .
docker push <DOCKERHUB_USERNAME>/cpp-afl-gcc
docker build -t $MAYHEM_DOCKER_REGISTRY/mayhemit/cpp-afl-gcc .
docker push $MAYHEM_DOCKER_REGISTRY/mayhemit/cpp-afl-gcc

Docker Hub にプッシュしたら、Mayhem UI を使用して新規ランを作成し、ソースとして <DOCKERHUB_USERNAME>/cpp-afl-gcc Docker イメージを指定します。Mayhemfile は次のようになります。

Mayhem Docker レジストリにプッシュしたら、Mayhem UI を使用して新規ランを作成し、ソースとして forallsecure/cpp-afl-gcc Docker イメージを指定します。Mayhemfile は次のようになります。

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

最後に、Mayhem ランを開始すると、次のようなラン ページが表示されます。よくできました!

cpp-afl-gcc-run

✏️ まとめと振り返り

このレッスンでは、AFL/AFL++ インストゥルメンテーション付きで C/C++ ターゲットをコンパイルし、Mayhem でファジングする方法を説明しました。


学習内容

1. AFL インストゥルメンテーション付き C ターゲットのコンパイルとテスト。
  • ソース コードには次の欠陥が含まれているはずです:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    FROM fuzzers/afl:2.52
    COPY mayhemit.c .
    RUN afl-gcc mayhemit.c -o /mayhemit
    RUN mkdir /testsuite && echo seed > /testsuite/seed
    
    # Set to fuzz!
    ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
    CMD ["/mayhemit", "@@"]
    

    1
    2
    3
    4
    5
    6
    7
    image: <DOCKERHUB_USERNAME>/c-afl-gcc:latest
    duration: 90
    project: mayhem-examples
    target: c-afl-gcc
    cmds:
      - cmd: /mayhemit @@
        afl: true
    
    1
    2
    3
    4
    5
    6
    7
    image: $MAYHEM_DOCKER_REGISTRY/forallsecure/c-afl-gcc:latest
    duration: 90
    project: mayhem-examples
    target: c-afl-gcc
    cmds:
      - cmd: /mayhemit @@
        afl: true
    
2. AFL++ インストゥルメンテーション付き C ターゲットのコンパイルとテスト。
  • ソース コードには次の欠陥が含まれているはずです:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    FROM fuzzers/aflplusplus:3.12c
    COPY mayhemit.c .
    RUN afl-gcc-fast mayhemit.c -o /mayhemit 
    RUN mkdir /testsuite && echo seed > /testsuite/seed
    
    # Set to fuzz!
    ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
    CMD ["/mayhemit", "@@"]
    

    1
    2
    3
    4
    5
    6
    7
    image: <DOCKERHUB_USERNAME>/c-aflpp-gcc:latest
    duration: 90
    project: mayhem-examples
    target: c-aflpp-gcc
    cmds:
      - cmd: /AFLplusplus/mayhemit @@
        afl: true
    
    1
    2
    3
    4
    5
    6
    7
    image: $MAYHEM_DOCKER_REGISTRY/forallsecure/c-aflpp-gcc:latest
    duration: 90
    project: mayhem-examples
    target: c-aflpp-gcc
    cmds:
      - cmd: /AFLplusplus/mayhemit @@
        afl: true
    
3. AFL インストゥルメンテーション付き C++ ターゲットのコンパイルとテスト。
  • ソース コードには次の欠陥が含まれているはずです:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    FROM fuzzers/afl:2.52
    COPY mayhemit.cpp .
    RUN afl-g++ mayhemit.cpp -o /mayhemit
    RUN mkdir /testsuite && echo seed > /testsuite/seed
    
    # Set to fuzz!
    ENTRYPOINT ["afl-fuzz", "-i", "/testsuite", "-o", "/out"]
    CMD ["/mayhemit", "@@"]
    

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