コンテンツにスキップ

エキスパート

Mayhem とテスト ドライバー

このレッスンでは、プログラム全体の特定の機能をテストする際、テスト ドライバーがどのような働きをするかについて順を追って説明します。

用語に注意してください。

ファジングになじみのあるユーザーは、ドライバーではなく「ファジング ハーネス」という用語を使用しているかもしれません。明確にしておくと、Mayhem で「テスト ドライバー」というとき、それは「ファジング ハーネス」と同じです。


学習時間の目安: 10 分

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

  1. テスト ドライバーが重要である理由を理解する。
  2. テスト ドライバーとは何であるかを説明する。
  3. サンプル テスト ドライバーを順を追って理解する。
  4. ハートブリード脆弱性を分析する。

テスト ドライバーを作成する理由

テスト ドライバー、 つまりテストしたい機能を呼び出す簡単なプログラムを作成し、より対象を絞ったファジングを行う必要があるケースは多いでしょう。たとえば、大規模な Web サーバー アプリケーションがあるが、テストしたいのは関連する HTTP リクエスト パーサーだけである場合、テスト ドライバーを作成すると、Web サーバー アプリケーション全体ではなく、HTTP リクエスト パーサーだけをファジングできます。

さらに、アプリケーションが Mayhem とのプログラム適合性の条件を満たさない場合も、テスト ドライバーを作成して Mayhem に適合させることができます。たとえば、特定のハードウェアをモック化して除外するテスト ドライバーを作成すると、ファジングが不可能だった (そして一般的にテストが困難な) アプリケーションを解析可能にできます。

Note

このセクションは、ユーザーがすでにこれまでのチュートリアルを終え、Mayhem および Mayhem CLI の基礎を知っていることを前提としています。

Mayhem とテスト ドライバー

テスト ドライバーは、バイナリへの新しいエントリポイントを作成します。テスト ドライバーは任意のコード セクションのまわりに、より汎用的になるようにも、対象を絞るためにも、ユーザーのニーズに応じて作成できます。そのため、対象とする機能を特定できたら、その特定のコード領域に解析を集中するよう Mayhem に方向を指示できます。

テスト ドライバーを作成すると、Mayhem はコードの該当部分だけに集中するので、バグの発見にかかる時間が短縮されます。

対象となる機能の例として、次のようなものが挙げられます。

  • ユーザーが触れるコード パス
  • 重要なプロシージャ
  • 開発者から見て以前からバグが多いコード

テスト ドライバーは、初期化やテストのさまたげになる機能をスキップするのにも役立ちます。たとえば、ターゲットは、テストしたい機能を実行する前にユーザー名とパスワードによるログインを要求する場合があるでしょう。テスト ドライバーを使用すると、初期化やさまたげになる機能があっても、スキップすることができます。

サンプル テスト ドライバーを理解する

それでは、サンプル テスト ドライバーがどのように動作するかを順を追って見てみましょう。

1
2
3
4
5
6
7
8
int SwordHarness(uint8_t *Data, size_t Size)
{
  char cmd = Data[0];
  uint8_t *nuke_data = Data + 1;
  if(cmd == LAUNCH_NUKE || cmd == CONFIGURE_NUKE)
    process_nuke_cmd(cmd, nuke_data);
  return 0;
}

このサンプル テスト ドライバーでは、SwordHarness 関数が (ファイルなどから) 入力を読み取り、テスト対象の関数 process_nuke_cmd を呼び出します。関数パラメーター *Data がファザーによって提供された入力ファイルであり、Size が入力のサイズであるとします。

Note

関数を呼び出すのに必要な不変条件のバッファーがある場合、テスト ドライバーがバッファーを初期化する必要があります。

SwordHarness 関数は *Data の先頭バイトを cmd 変数に入力し、残りのバイトを nuke_data として使用します。cmdLAUNCH_NUKE または CONFIGURE_NUKE が設定された場合、process_nuke_cmdcmd および nuke_data 変数を使用します。これはテスト ドライバーであるため、入力はコード カバレッジを最大化するよう、変更が加えられることが期待されます。

現実的なユース ケース

2014 年、2 つのセキュリティ研究チームがそれぞれ OpenSSL ライブラリのファジングを開始し、ハートブリード脆弱性と呼ばれる脆弱性を見つけました。これは、Web 上の多数のセキュア通信に利用されていた OpenSSL 暗号化ソフトウェアのセキュリティ欠陥でした。

Tip

ハートブリード脆弱性の詳細については、Mayhem のブログを参照してください。

次のサンプルでは、ハートブリード脆弱性を再現するテスト ドライバーを調べてみます。先ほどのサンプル テスト ドライバーと同様に、*Data および Size パラメーターは HeartBleed 関数へのファジング入力を表します。

ドライバーは、多くのライブラリ関数に必要な OpenSSL のコンテキストをセットアップし、テスト ドライバーに与えられたデータを使用してハンドシェークを実行しているのがわかります。行 413 がこれに該当します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void HeartBleed(const uint8_t *Data, size_t Size)
{
  //initialization
  SSL_CTX *sctx = Init();
  SSL *server = SSL_new(sctx);
  BIO *sinbio = BIO_new(BIO_s_mem());
  BIO *soutbio = BIO_new(BIO_s_mem());

  //send one message
  SSL_set_bio(server, sinbio, soutbio);
  SSL_set_accept_state(server);
  BIO_write(sinbio, Data, Size);
  SSL_do_handshake(server);
  SSL_free(server);
  SSL_CTX_free(sctx);
}

品質の高いテスト ドライバーを作成する

テスト ドライバーのパフォーマンスを改善し、解析の効率を最大化するため、テスト ドライバーの作成時に考慮するべきベスト プラクティスがいくつかあります。

  1. 確定的にする: 各繰り返しでグローバルな状態を変更しないようにします。
  2. 遅延の原因になる処理を避ける: データベース、ネットワーク接続、その他の外部コンポーネントをモック化します。
  3. テスト対象コードを分離する: テストしたいコードまたは重要なパスに必要なコードだけを実行します。
  4. ディスクの読み取り/書き込みを避ける: 解析が遅くなり、確定性が低くなります。
  5. テスト ドライバーがすばやく継続的に進捗していることを確認する: テスト ドライバーの進捗をモニターし、パフォーマンスを低下させるペイン ポイントがないことを確認します。

次の図は、サンプル ターゲット アプリケーションのコード カバレッジを視覚的に比較したものです。ベスト プラクティスを考慮せずに作成されたテスト ドライバーと、ベスト プラクティスを考慮して作成されたテスト ドライバーを比較すると、後者はよりパフォーマンスが高く、ターゲット アプリケーションに関して、より高いコード カバレッジを達成していることがわかります。

bad-harness.png

*ベスト プラクティスを考慮せずに作成されたハーネス*

good-harness.png

*ベスト プラクティスを考慮して作成されたハーネス*

✏️ まとめと振り返り

このレッスンでは、テスト ドライバーを作成するべき理由、Mayhem がどのようにテスト ドライバーを使用してターゲット アプリケーション全体のうち特定の機能をファジングするか、またテスト ドライバーを作成する際に気を付けるべきベスト プラクティスについて学びました。


学習内容

1.テスト ドライバーが重要である理由を理解する。
  • テスト ドライバー、 つまりテストしたい機能を呼び出す簡単なプログラムを作成し、より対象を絞ったファジングを行う必要があるケースは多いでしょう。たとえば、大規模な Web サーバー アプリケーションがあるが、テストしたいのは関連する HTTP リクエスト パーサーだけである場合、テスト ドライバーを作成して Web サーバー アプリケーション全体ではなく、HTTP リクエスト パーサーだけをファジングできます。
  • アプリケーションが Mayhem とのプログラム適合性 の条件を満たさない場合も、テスト ドライバーを作成して Mayhem に適合させることができます。たとえば、特定のハードウェアをモック化して除外するテスト ドライバーを作成すると、ファジングが不可能だった (そして一般的にテストが困難な) アプリケーションを解析可能にできます。
2.テスト ドライバーとは何かを説明する。
  • テスト ドライバーは、バイナリへの新しいエントリポイントを作成します。テスト ドライバーは任意のコード セクションのまわりに、より汎用的になるようにも、対象を絞るためにも、ユーザーのニーズに応じて作成できます。そのため、対象とする機能を特定できたら、その特定のコード領域に解析を集中するよう Mayhem に方向を指示できます。
3.サンプル テスト ドライバーをひととおり見る。
  • このサンプル テスト ドライバーでは、SwordHarness 関数が (ファイルなどから) 入力を読み取り、テスト対象の関数 process_nuke_cmd を呼び出します。関数パラメーター *Data がファザーによって提供された入力ファイルであり、Size が入力のサイズであるとします。* SwordHarness 関数は *Data の先頭バイトを cmd 変数に入力し、残りのバイトを nuke_data として使用します。cmdLAUNCH_NUKE または CONFIGURE_NUKE が設定された場合、process_nuke_cmdcmd および nuke_data 変数を使用します。これはテスト ドライバーであるため、入力はコード カバレッジを最大化するよう、変更が加えられることが期待されます。

    1
    2
    3
    4
    5
    6
    7
    8
    int SwordHarness(uint8_t *Data, size_t Size)
    {
        char cmd = Data[0];
        uint8_t *nuke_data = Data + 1;
        if(cmd == LAUNCH_NUKE || cmd == CONFIGURE_NUKE)
            process_nuke_cmd(cmd, nuke_data);
        return 0;
    }
    
4.ハードビート脆弱性を分析する。
  • 2014 年、2 つのセキュリティ研究チームがそれぞれ OpenSSL ライブラリのファジングを開始し、ハートブリード脆弱性と呼ばれるものを見つけました。これは、Web 上の多数のセキュア通信に利用されていた OpenSSL 暗号化ソフトウェアのセキュリティ欠陥でした。
  • ドライバーは、多くのライブラリ関数に必要な OpenSSL のコンテキストをセットアップし、テスト ドライバーに与えられたデータを使用してハンドシェークを実行しているのがわかります。行 4 と 13 がこれに該当します。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    void HeartBleed(const uint8_t *Data, size_t Size)
    {
        //initialization
        SSL_CTX *sctx = Init();
        SSL *server = SSL_new(sctx);
        BIO *sinbio = BIO_new(BIO_s_mem());
        BIO *soutbio = BIO_new(BIO_s_mem());
    
        //send one message
        SSL_set_bio(server, sinbio, soutbio);
        SSL_set_accept_state(server);
        BIO_write(sinbio, Data, Size);
        SSL_do_handshake(server);
        SSL_free(server);
        SSL_CTX_free(sctx);
    }