March 2017

Volume 32 Number 3

Visual Studio - ファイルの整合性確保を目的とした Visual Studio によるソース コード ファイルのハッシュ生成

Mike Lai | March 2017

コンパイルされるすべてのソフトウェア言語は、人間が判読できるコードから機械が判読できるコードに変換されます。これにより、ソフトウェア アシュアランスという課題が生まれます。 ソフトウェア プログラムの利用者は、自身のコンピューターで実行されているプログラムが、開発者が作成したのと同じソース コード ファイルからビルドされたものであるという確信を持てるでしょうか。 このような確信は必ずしも得られるわけではありません。専門家のレビューを受けたソース コード ファイルであっても、オープン ソース ソフトウェアのソース コードかもしれません。ソフトウェア アシュアランスの重要なポイントは、レビューを受けたソース コード ファイルと、実行可能ファイルに組み込まれたソース コード ファイルが同じものだと信頼できることです。 

特定のプログラミング言語 (C#、C++、Objective C、Java など) で記述された一連のソース コード ファイルは、コンパイルやリンクの過程で、特定のアーキテクチャ (x86、x64、ARM など) のコンピューターで実行するために、バイナリ形式の実行可能ファイルに変換されます。しかし、この変換は決定的なものにはならない場合があります。ソース コード ファイルの 2 つの異なるセットが、ビット単位で同じになる 2 つの実行可能ファイルに変換されることもあります。これが意図的に行われることもあります。ソース コード ファイルに含まれるホワイトスペースやテキスト コメントの量の差は、コンパイラが生成するバイナリ コードに影響すべきではありません。一方、ソース コード ファイルの 1 つのセットが、異なるコンパイル プロセスによって、異なる実行可能ファイルになる可能性もあります。いずれにせよ、問題になるのは確実性です。つまり、手元にあるファイルが希望するファイルだと確信できることです。

この問題に対処するため、Visual Studio コンパイラではコンパイル中にソース コード ファイルのハッシュを生成することができます。コンパイラのハッシュ値と検査対象のソース コード ファイルから生成されるハッシュ値が一致すれば、実行可能コードがそのソース コード ファイルから生成されていると確信できます。明らかに、これはユーザーにとっては安心材料です (実際、他のコンパイラ ベンダーが同様のアプローチをとっていればもっと便利です)。今回は、ハッシュ アルゴリズムを選択する Visual Studio の新しいスイッチと、こうしたハッシュが役立つシナリオ、Visual Studio を使ってソース コードのハッシュを生成する方法について説明します。

コンパイル時の強力なハッシュの生成

プログラム データベース (PDB) ファイルは、独立したデータ ファイルで、バイナリ実行可能ファイルをデバッグするために使用する情報が格納されます。マイクロソフトは、最近、強力な暗号アルゴリズムを使用するために、さまざまなコンパイラのファイル ハッシュ演算 (PDB ファイルに埋め込まれるソース ハッシュ値など) を更新しました。   

ネイティブ コード コンパイラ: Visual Studio 2015 のネイティブ C/C++ コンパイラ (cl.exe) には、ソース コード ファイルのハッシュを生成する際に異なるハッシュ アルゴリズムを選択できる新しいスイッチ /ZH:{MD5|SHA_256} が用意されました。スイッチの既定値は MD5 です。MD5 は衝突が発生しやすいことが知られていますが、ハッシュ値を生成する際の計算負荷が大きくないことから、既定値になっています。この新しいスイッチにより、コンパイラは MD5 よりも暗号として強力な SHA-256 オプションを実装します。

ソース コード ファイルの SHA-256 ハッシュが、バイナリ実行可能ファイルの PDB ファイルに格納されている SHA-256 ハッシュと一致すれば、同じソース コード ファイルから実行可能ファイルがコンパイルされたことを確信できます。これにより、すべての関係者がこのバイナリ実行可能ファイルを信頼できるようになります。実際、バイナリ実行可能ファイルの PDB ファイルに格納された一連の SHA-256 ハッシュ値はまとまって、そのバイナリ実行可能ファイルの「出生証明書」での ID になります。それは、これらの ID が、バイナリ実行可能ファイルを「生み出した」コンパイラによって登録されるためです。  

Debug Interface Access SDK (bit.ly/2gBqKDo) を使用すると、デバッグ情報出力ツール「cvdump.exe」(ソース コードと共に bit.ly/2hAUhyy で入手可能になりました) など、シンプルなツールの作成が容易になります。cvdump.exe の -sf スイッチを使用すると、(ローカルのビルド コンピューターでフルパス名を使用した) モジュールの一覧と、それぞれの MD5 ハッシュまたは SHA-256 ハッシュが表示されます (図 1 のコマンド ウィンドウ参照)。

cvdump.exe を使用したモジュールとそのハッシュの表示
図 1 cvdump.exe を使用したモジュールとそのハッシュの表示

以前のバージョンの cvdump.exe を使用して同じ PDB ファイルを表示すると、「SHA_256」の代わりに「0x3」というテキストが表示されます。この 0x3 という値は、SHA_256 の列挙値で、最新の cvdump.exe ではこの値を解釈する方法が把握されています。これは、Debug Interface Access SDK の IDiaSourceFile::get_checksumType メソッドが返す列挙値と同じです。

マネージ コード コンパイラ: Visual Studio 2015 のマネージ コード C# コンパイラ csc.exe は、既定では、SHA-1 暗号化アルゴリズムを使用してソース ファイルのチェックサム ハッシュ値を計算し、PDB ファイルに格納します。ただし、csc.exe は「/checksumalgorithm」という新しいオプション スイッチをサポートするようになり、SHA-256 アルゴリズムを指定できるようになります。SHA-256 アルゴリズムに切り替えるには、現在のディレクトリにあるすべての C# ファイルのコンパイルにこのオプションを使用し、ソース ファイルの一覧や SHA-256 ハッシュ値などのデバッグ情報を PDB ファイルに配置します。

csc /checksumalgorithm:SHA256 /debug+ *.cs

.NET コンパイラ プラットフォーム ("Roslyn") のオープン ソース プロジェクトの一環として、github.com/dotnet/roslyn から csc.exe を入手できます。ソース ファイルのデバッグ チェックサム アルゴリズム SHA-256 のコマンドライン セレクターのサポートは、bit.ly/2hd3rF3 のファイルで確認できます。

Visual Studio 2015 csc.exe は、Microsoft .NET Framework 4 以上の実行可能ファイルとしか互換性がありません。Microsoft .NET Framework 4 よりも前の実行可能ファイルのビルドに使用された他の Visual Studio 2015 .NET Framework コンパイラは、/checksumalgorithm スイッチをサポートしません。

マネージ コード PDB ファイルは、ネイティブ コード PDB ファイルと異なる方法でデータが格納されます。Debug Interface Access SDK を使用する代わりに、Microsoft DiaSymReader 相互運用性インターフェイスとユーティリティを使用しても、マネージ コードの PDB ファイルを読み取ることができます。Microsoft DiaSymReader は、NuGet パッケージとして bit.ly/2hrLZJb から入手可能です。   

Roslyn プロジェクトには、pdb2xml.exe というユーティリティが含まれています。pdb2xml.exe とそのソースは bit.ly/2h2h596 で確認できます。このユーティリティは、PDB のコンテンツを XML 形式で表示します。たとえば、図 2 のセグメントは、マネージ コードの実行可能ファイルのコンパに使用されている C# ソース コード ファイルの一覧を示しています。  

マネージ コード PDB の XML 形式での表示
図 2 マネージ コード PDB の XML 形式での表示

checkSumAlgorithmId フィールドの GUID 「8829d00f-11b8-4213-878b-770e8597ac16」は、checkSum フィールドの値が、name フィールドで参照されるファイルの SHA-256 ハッシュ値であることを示しています。この GUID は、「移植可能な PDB 形式仕様 v0.1」(bit.ly/2hVYfEX、英語) で定義されています。  

SHA-256 のコンパイラ サポート

以下の Visual Studio 2015 コンパイラは、ソース コード ファイルの SHA-256 ハッシュ生成オプションをサポートします。      

  • cl.exe /ZH:SHA_256
  • ml.exe /ZH:SHA_256
  • ml64.exe /ZH:SHA_256
  • armasm.exe -gh:SHA_256
  • armasm64.exe -gh:SHA_256
  • csc.exe /checksumalgorithm:SHA256

これらのコンパイラは、Visual Studio 2015 の [開発者コマンド プロンプト for VS2015] コマンド ウィンドウから利用できます。

Windows プラットフォームをターゲットにしないコンパイラは、一般にデバッグ情報の保存に PDB ファイルを使用しません。このようなコンパイラは、通常コンパイルの実行中に同時に 2 つの実行可能ファイルを生成します。一方はデバッグ情報が含まれるファイルで、もう一方はデバッグ情報が取り除かれたファイルです (bit.ly/2hIfvx6、英語)。デバッグ情報が含まれる実行可能ファイルには、デバッグ情報がすべて格納され、デバッグ情報が取り除かれる実行可能ファイルには詳細なバッグ情報は一切含まれません。このデバッグ情報が含まれる実行可能ファイルは、処理したソース コード ファイルの SHA-256 ハッシュ値を格納するのに適しているかもしれません。Windows プラットフォームをターゲットにしないコンパイラの作成者と連絡を取り、これらのコンパイラを使用する Office for Android、Office for iOS、Office for Mac などの Windows ベースではないソフトウェアでも Windows ベースのソフトウェアと同様のメリットを得られるように、コンパイラごとにどのアプローチが最も適しているかを明らかにすることを計画しています。

ユースケース シナリオ   

ここからは、ソース ファイルのハッシュ値が役に立つシナリオをいくつか見ていくことにしましょう。       

移植可能な実行可能 (PE) バイナリ ファイルのインデックス付きソース ファイルの取得: Ssindex.cmd スクリプト (bit.ly/2haI0D6) は、PDB ファイルへの格納を目的として、ソース管理にチェックインされたソース ファイルの (インデックス付き) 一覧を、各ファイルのバージョン情報と共に作成するユーティリティです。PDB ファイルにこのバージョン管理情報が含まれている場合、-h オプションを付けて srctool ユーティリティ (bit.ly/2hs3WXY) を使用すると、この情報を表示できます。インデックス付きソース ファイルのハッシュ値も PDB ファイルに埋め込まれます。そのため、サポート技術情報の記事 3195907「インデックス付きソース ファイルを取得する方法」(bit.ly/2hs8q0u、英語) で説明されているように、これらのハッシュ値は、取得中にソース ファイルの信頼性を確認するのに使用できます。 具体的には、ハッシュ値が一致しなければ、PE と PDB ペアの生成中、またはソース管理システム内で問題が生じている可能性があります。この場合、さらに詳しい調査が必要になることがあります。一方、ハッシュ値が一致したら、取得したインデックス付きソース ファイルが PE と PDB ペアのコンパイルに使用された確率がかなり高くなります。        

ソース ファイル静的アナライザーによって生成されたハッシュ値との照合: マイクロソフト セキュリティ開発ライフサイクル (Security Development Lifecycle: SDL) の実装フェーズ (bit.ly/­29qEfVd、英語) において推奨されているように、今日ではソフトウェアの品質評価に自動ツールを使用するのが一般的です。特に、ソース ファイル静的アナライザーは、ターゲットのソース コード ファイルをスキャンして、ソフトウェア品質のさまざまな側面を評価するのに使われます。このような静的アナライザーは通常、ターゲットのソース コード ファイルのスキャン時に、リアルタイムに対応する結果を生成します。静的アナライザーはソース コード ファイルを個別にスキャンするため、スキャンされているソース コード ファイルごとに強力なハッシュ (SHA-256) を生成する絶好の機会を提供します。実際のところ、bit.ly/2ibkbwz のオープン ソース プロジェクトで提案されている Static Analysis Results Interchange Format (SARIF) は、静的アナライザーの静的分析結果内に特定の場所を提供します。この場所に、スキャンされたターゲットのソース コード ファイルとその SHA-256 ハッシュ値が生成されます。 

PE ファイルで、以下の情報を利用できるとします。

  1. コンパイラによって生成された、対応する PDB ファイルのコンパイル済みソース ファイルのハッシュ一覧。
  2. 静的アナライザーによって生成された、対応する静的分析結果のスキャン済みソース ファイルのハッシュ一覧。

このシナリオでは、2 つのハッシュ一覧が一致するかどうかを確認して検証できます。一致している場合、ソース ファイルはなんらかの品質評価を目的に静的アナライザーによってスキャンされているため、そのソース ファイルを再スキャンする必要はありません。以前は、ファイル ハッシュ一覧がなかったため、静的アナライザーが正しく評価を行ったことを確かめるためだけに、再スキャンが必要になることがありました。  

ソフトウェア更新プログラムや修正プログラムの開発プロセスにおける迅速なサニティ チェック: 静的アナライザーによってリリース済み製品のソース ファイルに見つかった品質の問題を解決するためにソフトウェア更新プログラムをリリースする必要がある場合、静的アナライザーは保留中の更新プログラムのソース コード ファイルに品質の問題が存在しないことをレポートしなければなりません。このレポートでは、少なくとも、更新プログラムが品質の本来の問題の解決策として効果があることを確認することになります。つまり、ソフトウェア更新プログラムの目的を検証することになります。必要に応じて、開発者またはセキュリティ レビューアが、次の手順に従って、すばやく検証を実施します。 

  1. 静的アナライザーの最初のレポートで、対象となる品質の問題が特定されていることを確認します。
  2. 静的アナライザーの最初のレポートに、品質の問題を生じているソース ファイルのハッシュ値が含まれていることを確認します。
  3. 静的アナライザーの最初のレポートに含まれるファイル ハッシュ値と、リリース済み製品バージョンのソース ファイルのハッシュ値を照合します。
  4. 同じ静的アナライザーを使用して更新プログラムのソース コード ファイルをスキャンした結果作成された更新後のアナライザー レポートを取得します。
  5. 以前に検出された品質の問題が、更新プログラムの静的アナライザー レポートに存在しないことを確認します。
  6. 静的アナライザーの更新後のレポートに含まれるファイル ハッシュ値と、更新プログラムのソース ファイルのハッシュ値を照合します。

上記の検証手順中、本来のリリース済み製品または更新プログラムのどちらかの実際のソース コード ファイルにアクセスできる必要はありません。 

ソフトウェアの 2 つのバージョン間のソース コードの差分作成: ソース コードの全セットのレビューにはおそらく時間がかかります。ただし、ソース コードに変更を加えた後であれば、ソース コードをすべてレビューする必要はありません。このような場合は、ソース コードの差分だけのレビューを依頼できます。前回レビューしてから変更していない部分を繰り返し分析することに合理的根拠はないため、こように依頼しても問題ありません。      

以前は、ソース コード ファイルの強力な暗号的ハッシュ値がなかったため、正確性のある差分サブセットを作成するのは困難でした。差分サブセットを用意できたとしても、専門家にとっては、その差分サブセットが正確に作成されたとは確信できなかったでしょう。ですが、このようなことはなくなります。ソース コード ファイルの強力な暗号的ハッシュ値があれば、以下の手順に従って差分サブセットを作成できます。

  1. 本来の製品バージョンのすべてのソース コード ファイルのハッシュ値のプール (ここでは Pool X) を取得します。
  2. 差分サブセットを作成する新バージョンの製品のソース コード セットを含むファイル ディレクトリの正確なコピー (ここでは Dir A) を作成します。
  3. 差分ファイル サブセットのみを保持する最終的なファイル フォルダーの出力先 (ここでは Dir B) を準備します。
  4. Dir A のすべてのファイルを調べます。
  5.         a. ファイルのハッシュ値が Pool X のハッシュ値と一致する場合、何もしないで、次のファイルに移動します。
  6.         b. ファイルのハッシュ値が Pool X のハッシュ値と一致しない場合、次のファイルに移動する前にそのファイルを Dir B にコピーします。
  7. Dir B のすべてのファイルのハッシュ値が、新しい製品バージョンのソース コード ファイル内の対応するハッシュ値と一致することを検証します。  
  8. Dir B のコンテンツを、新しい製品バージョンの差分ソース ファイル サブセットにします。     

ハッシュの生成

ここからは、Visual Studio コンパイラを使用してファイルのハッシュを生成する方法を見ていきます。そこで、Visual Studio のオンライン ドキュメント (bit.ly/2haPupF) の “Hello, World” アプリケーションの作成例を使って、以下を実行します。

  1. 出力 PDB ファイルの中で、コンパイル済みのソース ファイルのハッシュ値を探す場所を示します。
  2. certutil (bit.ly/2hIrnPR) ツールを使用してソース ファイルのハッシュ値を計算し、PDB ファイルで見つかったハッシュ値と照合します。       

まず、Visual Studio 2015 の [プロジェクト] フォルダーで、新しい Win32HelloWorld アプリケーション プロジェクトを作成します。この Win32HelloWorld プロジェクトには、図 3 に示すように C++ ソース ファイルの Win32HelloWorld.cpp が 1 つあるだけです。

Win32HelloWorld.cpp
図 3 Win32HelloWorld.cpp

Win32HelloWorld.cpp には main 関数が含まれていて、この関数で “Hello” テキストが表示されます。

Win32HelloWorld プロジェクトをビルドすると、W32HelloWorld.exe ファイルと W32HelloWorld.pdb ファイルが Visual Studio 2015\Projects\W32HelloWorld\x64\Debug フォルダー出力されます。

cvdump ツールに -sf オプションを指定して、W32HelloWorld.pdb ファイルに対して使用すると、図 4 の出力に示すように、Win32HelloWorld.cpp ファイルとその MD5 ハッシュ値が表示されます。

Win32HelloWorld.cpp と その MD5 ハッシュ値を表示する cvdump 出力
図 4 Win32HelloWorld.cpp と その MD5 ハッシュ値を表示する cvdump 出力

MD5 が Visual Studio 2015 コンパイラ cl.exe の既定のアルゴリズムになるため、ハッシュ値は MD5 になります。ソース ファイルのハッシュ アルゴリズムを SHA-256 に変更するには、cl.exe に /ZH:SHA_256 オプションを指定します。これを行うには、Win32HelloWorld プロジェクトの [プロパティ ページ] の [追加オプション] で、「/ZH:SHA_256」を追加します (図 5 参照)。

ソース ファイルのハッシュ アルゴリズムの SHA-256 への切り替え
図 5 ソース ファイルのハッシュ アルゴリズムの SHA-256 への切り替え

Visual Studio でリビルドすると、Visual Studio 2015\Projects\W32HelloWorld\x64\Debug フォルダーに、新しい W32HelloWorld.exe と W32HelloWorld.pdb という PE と PDB のペアが表示されます。cvdump ツールに -sf オプションを指定して、新しい W32Hello­World.pdb ファイルに対して使用すると、図 6 の出力に示すように、Win32HelloWorld.cpp ファイルとその SHA-256 ハッシュ値が表示されます。

Win32HelloWorld.cpp とその SHA-256 ハッシュ値を表示する cvdump
図 6 Win32HelloWorld.cpp とその SHA-256 ハッシュ値を表示する cvdump

ここで Visual Studio 2015\Projects\W32HelloWorld\W32HelloWorld フォルダーの W32HelloWorld.cpp ファイルに戻ると、SHA-256 ハッシュ値を確認できるようになります。SHA-256 の場合、certutil ツールに -hashfile 動詞を指定して、Win32HelloWorld.cpp ファイルに対して使用すると、図 7 に示すように SHA-256 ハッシュ値が表示されます。

certutil による SHA-256 ハッシュ値の表示
図 7 certutil による SHA-256 ハッシュ値の表示

明らかに、この値は、W32Hello­World.pdb ファイルに記録されている SHA-256 値と一致します。これは、予想どおり、W32HelloWorld.exe アプリケーションのコンパイルに、間違いなく Win32HelloWorld.cpp ファイルが使用されていることが示されます。

ネイティブ コードとマネージ コードの PE ファイルと PDB ファイルのペアを操作するための関連する公開ツールの詳細については、サポート技術情報の記事 3195907「インデックス付きソース ファイルを取得する方法」(bit.ly/2hs8q0u、英語) をご覧ください。

まとめ

今回の説明で、ソース コード ファイルと、そのソース コード ファイルを使ってコンパイルされた PE ファイルを厳密に結び付ける潜在的メリットを感じていただければさいわいです。コンパイラが、新たに利用可能になった強力なハッシュ アルゴリズム SHA-256 を使用して、コンパイル中にソース コード ファイルのハッシュ生成することで、強固な結び付きを生み出すことができます。ソース コード ファイルに対してコンパイラが生成した実際のハッシュ値は、実行可能ファイルをコンパイルするのに使用されるソース コード ファイルの文字通り一意識別子になります。

この一意識別子の値を把握したら、ソフトウェア開発のライフサイクルのさまざまな手法で、特定の実行可能ファイルと強力に結び付けられているソース コード ファイルの追跡、処理、管理にこの一意識別子を使用できます。その結果、実行可能ファイルに対するエンド ユーザーの信頼性が高まります。


Mike Lai は、マイクロソフトに勤務してちょうど 20 年目を迎えます。彼は、機能面と技術面から多くの製品に貢献する機会を与えられたことを、マイクロソフトに感謝しています。また、信頼できるコンピューティングを提供する現在の管理ソリューションに対して、彼のアイデアが成熟するまで待ってくれたこと、リリース済みの製品への段階的な取り組み、情報通信技術セキュリティ組織への参加のサポートに感謝しています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Scott Field、Mike Grimm、Sue Hotelling、Ariel Netz、Richard Ward、および Roy Williams に心より感謝いたします。