コンテンツにスキップ

ブロック、関数、行カバレッジの解析

他のテストの場合と同様に、ファジングの次のステップに関してよりよい決定を下すことができるよう、カバーされたコードだけでなく、カバーされていないコードを把握することが重要です。

ファジングが外部ソースまたは信頼されていないソースからの入力の解析または入力の処理を行うコードをカバーしているかを確認することは、ベスト プラクティスの 1 つでしょう。さらに、cyclomatic complexity が高い関数を徹底的にテストするべきです。複雑性が高い関数にはコード パスが多く、説明するのが困難だったり、予期しないコーナー ケースが含まれる可能性が高くなったりするからです。

ターゲット アプリケーションがどのように入力を処理しているかを理解し、その結果としてどの関数がカバーされ、どの関数がカバーされていないか、あるいはカバーされるべきかを把握することで、ファジングに関して、より多くの情報を得たうえで (結果的によりよい) 決定を下すことができます。

Info

カバレッジの仕組みの詳細については、用語集でテスト カバレッジの項目を参照してください。

前提条件

カバレッジ ファイルを解析するには、まず、完了済みの Mayhem ランのカバレッジ ファイルをダウンロードする必要があることを思い出してください。そのため、「Mayhem CLI からのカバレッジの実行とダウンロード」で取得した testme のカバレッジ結果を使用します。

ファイル: coverage.tgz

Tip

UI からダウンロードすると、<target_name>_coverage のような名前のディレクトリを含む coverage.tgz アーカイブがダウンロードされます。一方、Mayhem CLI で mayhem sync または mayhem download を使用すると、Mayhemfile およびテスト ディレクトリと同じ親ディレクトリに直接結果が配置されます。

ダウンロードが完了すると、カバレッジ ディレクトリには最大で次の 3 つのファイルがあります: block_coverage.drcovfunc_coverage.jsonline_coverage.lcov

各ファイルはどれも、個別の入力またはテスト スイート内の個別のテスト ケースから得られたすべてのコード カバレッジの集約を表していますが、それぞれ異なる観点—つまりブロック、関数、ソース コード レベルでカバレッジを集約しています。

Note

Mayhem が振る舞いを区別するために内部的に使用するエッジ カバレッジは、ブロック カバレッジとは異なります。この 2 つは関連していますが、別個のカバレッジ メトリクスです。

コンパイル済みバイナリ testme にアクセスできる必要もあります。そのため、2.10 チュートリアル Docker イメージを使用します。

docker pull forallsecure/tutorial:2.10
docker run -ti --privileged --rm forallsecure/tutorial:2.10

それでは、3 種類のファイルを使用してカバレッジを解析する方法を見てみましょう。

ブロック カバレッジ

基本ブロックとは、単一の入口と単一の出口を持つ最小のコード単位です。block_coverage.drcovは、基本ブロック カバレッジをパッケージ化されたバイナリ フォーマットで表現しており、(追加の) プラグインを使用してデータの可視化または操作が可能な Binary Ninja (bncov)、IDA Pro (lighthouse)、Ghidra (Dragon Dance) などのバイナリ解析ツールで使用するためのものです。

Tip

ターゲットのソース コードがあり、デバッグ シンボル付きでターゲットをコンパイルできる場合、行カバレッジ ファイルのほうが扱いやすいでしょう。

Binary Ninja および bncov を使用したブロック カバレッジの解析

block_coverage.drcov 出力ファイルを生成元のバイナリ ターゲットに重ねると、Binary Ninja の bncov プラグインを使用してブロック カバレッジを可視化できます。

Warning

Binary Ninja を使用してブロック カバレッジを可視化するには、Mayhem ランで生成された block_coverage.drcov 出力ファイルのほかに、コンパイル済みのターゲット アプリケーションを Binary Ninja アプリケーションにインポートする必要があります。そのため、Docker を使用している場合は、Docker コンテナーから Binary Ninja を実行できるホスト OS にファイルをコピー (docker cp) します。

それでは、Binary Ninja を開いてコンパイル済みの testme アプリケーションをインポートしましょう。

binary-ninja

インポートが完了すると、コンパイル済みの testme アプリケーションを構成する個々のコード ブロックを視覚化できます。

Note

逆アセンブリ グラフを表示するには、Binary Ninja のライセンスを購入する必要があります。

binary-ninja-disassembly

最後に、bncov プラグインをインストールして testme アプリケーションの block_coverage.drcov ファイルをインポートすると、カバーされた個々のブロックおよび実行されたエッジ (パス) が表示されます。

Note

bncov をインストールする最も簡単な方法は、Binary Ninja のプラグイン マネージャーを使用することです。インストールが完了したら、[Tools] メニューに移動して bncov の利用を開始します。

binary-ninja-bncov

結果として、bncov プラグインとともに Binary Ninja を使用して、個々のブロック カバレッジを解析できるようになりました。

Ghidra および Dragon Dance を使用したブロック カバレッジの解析

Ghidra および Dragon Dance プラグインを使用すると、テスト対象のアプリケーションのバイナリ コード カバレッジを視覚化および操作して、アプリケーションの実装やコード カバレッジの動作をより深く理解することができます。

Warning

Ghidra を使用してブロック カバレッジを可視化するには、Mayhem ランで生成された block_coverage.drcov 出力ファイルのほかに、コンパイル済みのターゲット アプリケーションを Ghidra アプリケーションにインポートする必要があります。そのため、Docker を使用している場合は、Docker コンテナーから Ghidra を実行できるホスト OS にファイルをコピー (docker cp) します。

次に、 Ghidra および Dragon Dance プラグインをダウンロードしてインストールする必要があります。

# Download Ghidra 
wget https://ghidra-sre.org/ghidra_9.1.2_PUBLIC_20200212.zip
unzip ghidra_9.1.2_PUBLIC_20200212.zip

# Download Dragon Dance 0.2.2
git clone https://github.com/0ffffffffh/dragondance.git
cd dragondance
git checkout v0.2.2

Dragon Dance プラグインをビルドするには、利用しているオペレーティング システム (OS) 向けの Java Development Kit (JDK) および互換性のあるバージョンの gradle も必要です。

Note

この演習では、Java 15.0.2、Gradle 6.8.2、Ghidra 9.1.2、Dragon Dance 0.2.2 を使用します。

次に、dragondance-master フォルダーに移動し、次のコマンドを実行して Ghidra インストール ディレクトリ内に Dragon Dance プラグインをビルドします。結果のパッケージは dragondance-master/dist ディレクトリの下に配置されます。

gradle -PGHIDRA_INSTALL_DIR=<path_to_ghidra_install>/ghidra_9.1.2_PUBLIC

Ghidra インストール ディレクトリに移動し、次のコマンドを実行してアプリケーションを開きます。

./GhidraRun

すでに Dragon Dance プラグインをビルドしてあるので、あとは利用可能なプラグインとして Ghidra にインポートするだけです。[File] > [Install Extensions] をクリックし、dragondance-master/dist フォルダーに移動して .zip ファイルを選択し、Dragon Dance プラグインを追加します。

import-dragon-dance

次に、新規プロジェクト testme_coverage を作成し、コンパイル済みの testme アプリケーションをインポートします。ファイルは testme-pkg/root/root/tutorial/testme/v1/testme にあります。

Note

testme バイナリを Ghidra にインポートしたとき、警告が表示される場合がありますが、単に無視して通常どおり進めます。

ghidra-testme

インポートされた testme ファイルをダブルクリックすると、testme バイナリの逆アセンブルされたビューの表示が開始されます。左側の Symbol Tree ペインに移動し、Functions フォルダーの下にある testme を選択します。すると、testme バイナリの testme 関数を構成するアセンブリ コードが表示されます。

ghidra-testme-diassembly

[Window] > [Dragon Dance] をクリックし、testme のカバレッジ結果から block_coverage.drcov ファイルをインポートします。

Warning

Ghidra で Dragon Dance を使用するには、初回の使用時に Dragon Dance を初期化する必要がある場合があります。公式ドキュメントの「launching Dragon Dance」を参照してください。

さらに、より複雑なバイナリの場合、Ghidra はすべてのコード領域を適切に識別できるとは限らず、コードとして識別された領域の外にあるブロック アドレスに遭遇すると、ユーザーにプロンプトを表示します。その場合、適切なアクションは、カバレッジ ファイルをグラウンドトゥルースとみなし、ブロックをコードとして修正/マークするよう指示することです。

dragon-dance-window

Dragon Dance でブロック カバレッジ アイテムを右クリックし、[Switch To] をクリックします。すると、testme の逆アセンブリ ビューに testme のカバレッジ結果が重ねて表示されます。

ghidra-testme-diassembly-coverage

これで終わりです。testme のカバーされた部分およびカバーされていない部分を参照できるようになりました。

関数カバレッジ

func_coverage.json カバレッジ ファイルは、ターゲット バイナリに含まれる関数の基本的情報や各関数のブロック カバレッジ情報を表すプレーンテキストの JSON ファイルです。

Note

開発者が必要に応じてデータを解析できるよう、このファイルは JSON フォーマットで提供されます。たとえば、カスタム スクリプトでの自動レポート生成などに関数カバレッジ データを利用できます。

func_coverage.json ファイルのキー/値ペアの定義は次のとおりです。

  • address: 関数のアドレスです。
  • name: 関数の名前です。
  • complexity: cyclomatic complexity です。
  • callers: 現在の関数から呼び出されている他の関数です。
  • callees: 現在の関数を呼び出している他の関数です。
  • all_blocks: 関数に対応するすべてのブロックです。
  • covered_blocks: カバーされた関数に対応するすべてのブロックです。
  • called: 値は True または False です。関数がカバーされたかどうかを表します。

Note

すべてのアドレスは 10 進数であり、関数の開始アドレス (具体的には、関数の最初の命令の先頭バイトのアドレス。これは最初のブロックの開始アドレスでもあります) を表していることに注目してください。

次は、関数の出力のサンプルです。

[
   {
        "address": 1078889,
        "name": "http_parser_init",
        "complexity": 3,
        "callers": [
            1053267
        ],
        "callees": [
            1052768
        ],
        "all_blocks": [
            1078889,
            1078981,
            1078987,
            1078994,
            1079001,
            1079006
        ],
        "covered_blocks": [
            1078889,
            1079001
        ],
        "called": true
    },
    ...
]

関数カバレッジ解析用カスタム アプリケーションの作成

ニーズに合わせてどのように func_coverage.json を利用できるかの簡単な例として、Python を使用して Pandas DataFrame としてデータをインポートするケースが挙げられます。

Pandas は、広く利用されている堅牢なデータ解析ライブラリであり、DataFrame というテーブルに似た構造を利用し、行と列によってレコードを表現します。func_coverage.json を Pandas Dataframe にインポートすると、データ解析機能をフルに活用して詳しく関数カバレッジを解析できます。

func-analysis-app

Note

ネストされたオブジェクトがあり、データを非正規化する必要がある場合があります。

行カバレッジ

行カバレッジは、基本ブロックを起源となるソース コード行にマッピングすることで得られます。

line_coverage.lcov カバレッジ ファイルには、行カバレッジ情報が LCOV フォーマットで含まれており、ファイルのどの行がカバーされたかを表します。そのため、.lcov ファイルは元のソース ディレクトリおよびファイルとともに処理する必要があります。そうでなければ、ファイル パスやソース コードのバージョンが変わったり、修正された場合、lcov レポートと情報が一致しなかったり、情報が不足したりします。

Note

.lcov ファイルは、ターゲットにデバッグ シンボルが含まれている場合にだけ作成されます。デバッグ シンボルが含まれていない場合、行カバレッジ情報を自動生成できません。

.lcov フォーマット (ツールによっては .info の場合もあります) のファイルは、LCOV など多数のツールに取り込んで、見やすいカバレッジ レポートを生成したり、他の IDE、プラグイン、サードパーティ製ツールと統合して追加のカバレッジ情報を表示するのに使用できます。

genhtml を使用した行カバレッジ レポートの生成

.lcov ファイルが生成された元の環境でファイルを処理することがベスト プラクティスです。そのため、Docker コンテナーに lcov をインストールし、genhtml ユーティリティを章して行カバレッジ レポートを生成します。

まず、lcov をインストールする必要があります。

apt-get update -y
apt-get install -y lcov

次に、line_coverage.lcov ファイルに対して genhtml ユーティリティを実行し、testme アプリケーションの行カバレッジ レポートを生成します。

Note

genhtml コマンドの形式は次のとおりです: genhtml <file> --output-directory <directory_name>

lcov-genhtml

その後、生成された HTML ファイルを Docker コンテナーからホスト OS に移動し、ローカルな HTTP サーバーを実行して行カバレッジ レポートを表示します。

Note

別の方法として、ポート フォワーディングによって Docker コンテナーのネットワークを構成し、コンテナー内で HTTP サーバーを立ち上げてホスト OS 経由で接続できます。

docker-cp-genthml

コード カバレッジ レポートには、集約された行カバレッジ結果が表示されるほか、ドリル ダウンしてカバーされた個々の行を視覚的に参照することもできます。0 は未カバー、1 はカバー済みを表します。

lcov-report

見てのとおり、ターゲット アプリケーションの行コード カバレッジを視覚化すると、非常に有効で読みやすくなる可能性があります。

まとめ

block_coverage.drcovfunc_coverage.jsonline_coverage.lcov の 3 種類のカバレッジ ファイルを Mayhem からダウンロードし、解析できます。それぞれブロック、関数、ソース コード レベルという異なる観点から集約されたカバレッジを表します。

最後に、コード カバレッジのパーセント値を見て終わりではありません。ターゲットが部分的にしかカバーされていない場合、関数の未テストの部分は、関数に到達した入力値のバラエティが不足しているのか、それとも単にその部分は通常の条件下では実行されないのか (メモリ不足やネットワーク エラーを処理するコードなど) を検討する必要があります。関数が 100% カバーされた場合でも、ゼロ除算や NULL ポインター間接参照などの起こりうるバグ条件が存在する可能性があります。ファジングで適切な量のコードをカバーできたかどうかという個々の判断の問題であり、さらにファジングを行うことで得られる改善のコストと、セキュリティまたは信頼性への潜在的な影響を比較検討する必要があります。

カバレッジ ファイルの解析方法を知り、結果の妥当性を考慮することで、Mayhem のターゲット アプリケーションに関してより適切にファジングの方針を決定することができます。