プラットフォームのセキュリティのための制御フロー ガード
制御フロー ガードとは
制御フロー ガード (CFG) は、メモリ破損の脆弱性に対処するために作成されたプラットフォームのセキュリティ機能で、、高度に最適化されています。 アプリケーションがコードを実行できる場所に厳しい制限を設けることで、バッファ オーバーフローなどの脆弱性を利用して任意のコードを実行する悪用がはるかに困難になります。 CFG は、/GS (バッファー セキュリティ チェック)、データ実行防止 (DEP)、アドレス空間のレイアウト ランダム化 (ASLR) など、以前の悪用緩和テクノロジを拡張します。
CFG を使用すると、次のことができます。
- メモリの破損やランサムウェア攻撃を防ぎます。
- 攻撃面を減らすために、サーバーの機能を特定の時点で必要なもののみに制限します。
- バッファー オーバーフローなどの脆弱性を利用して任意のコードを悪用することを困難にします。
この機能は Microsoft Visual Studio で使用でき、CFG 対応バージョンの Windows (クライアント上の Windows 10 と Windows 11、サーバー側の Windows Server 2019 以降) で実行されます。
開発者は、アプリケーションに対して CFG を有効にすることを強くお勧めします。 CFG が有効になっていて、CFG が有効になっていないコードが正常に実行されるため、コードのすべての部分に対して CFG を有効にする必要はありません。 ただし、すべてのコードに対して CFG を有効にできない場合、保護にギャップが生じる可能性があります。 さらに、CFG が有効になっているコードは、CFG に対応していないバージョンの Windows でも正常に動作するため、そうしたバージョンと完全に互換性があります。
CFG を有効にする方法
ほとんどの場合、ソース コードを変更する必要はありません。 Visual Studio プロジェクトにオプションを追加するだけで、コンパイラとリンカーによって CFG は有効になります。
最も簡単な方法は、プロジェクト |プロパティ | 構成プロパティ | C/C++ | コード生成に移動して、[制御フロー ガード] で [はい (/guard:cf)] を選択することです。
または、/guard:cf をプロジェクト | プロパティ | 構成プロパティ | C/C++ | コマンド ライン | その他のオプション (コンパイラの場合) に追加し、/guard:cf をプロジェクト | プロパティ | 構成プロパティ | リンカー | コマンド ライン | その他のオプション (リンカーの場合) に追加します。
詳細については、「/guard (制御フロー ガードを有効にする)」を参照してください。
コマンド ラインからプロジェクトをビルドする場合、同じオプションを追加できます。 たとえば、test.cpp というプロジェクトをコンパイルする場合は、cl /guard:cf test.cpp /link /guard:cf を使用します。
メモリ管理 API の SetProcessValidCallTargets を使用して、CFG によって有効とみなされる icall ターゲット アドレスのセットを動的に制御するオプションもあります。 同じ API を使用して、ページが CFG に無効なターゲットか有効なターゲットかを指定できます。 VirtualProtect 関数と VirtualAlloc 関数は、既定で、実行可能ページとコミット済みページの指定された領域を有効な間接呼び出しターゲットとして扱います。 Just-in-Time コンパイラを実装する場合など、この動作をオーバーライドできます。その方法は、メモリ保護定数で詳しく説明されているように、VirtualAlloc を呼び出すときに PAGE_TARGETS_INVALID を指定するか、VirtualProtect を呼び出すときに PAGE_TARGETS_NO_UPDATE を指定します。
バイナリが制御フロー ガード下にあることを確認する方法
dumpbin ツール (Visual Studio のインストールに含まれる) を、/headers オプションと /loadconfig オプション (dumpbin /headers /loadconfig test.exe) により Visual Studio コマンド プロンプトから実行します。 CFG 下のバイナリの出力では、ヘッダー値に "Guard" が含まれていること、および読み取り構成値に "CF をインストルメント化済み" と "FID テーブルが存在" が含まれていることを示す必要があります。
CFG の実際のしくみ
ソフトウェアの脆弱性は、実行中のプログラムに、ありそうもない、異常な、または極端なデータを提供することによって悪用されることがよくあります。 たとえば、攻撃者は、予想よりも多くの入力をプログラムに提供してバッファー オーバーフローの脆弱性を悪用し、応答を保持するためにプログラムで予約されている領域をオーバーランさせる可能性があります。 これにより、関数ポインターを保持している隣接するメモリが破損する可能性があります。 プログラムがこの関数を使用して呼び出すと、攻撃者が指定した意図しない場所にジャンプする場合があります。
ただし、CFG のコンパイルとランタイム サポートの強力な組み合わせにより、間接呼び出し命令が実行できる場所を厳密に制限する制御フローの整合性が実装されます。
コンパイラは、次を行います。
- コンパイル済みコードに軽量のセキュリティ チェックを追加します。
- 間接呼び出しの有効なターゲットであるアプリケーション内の関数セットを識別します。
Windows カーネルによって提供されるランタイム サポート:
- 有効な間接呼び出しターゲットを識別する状態を効率的に維持します。
- 間接呼び出し先が有効であることを確認するロジックを実装します。
この階層構造を図解すると以下のとおりです。
実行時に CFG チェックが失敗した場合、Windows はプログラムを直ちに終了して、無効なアドレスを間接的に呼び出そうとする悪用を中断します。