All Articles

A PART OF ANTI-VIRUS【2 章 ファイルシステムミニフィルタドライバー入門】

本書では、公式の Windows Driver Samples リポジトリ1 で公開されている Scanner File System Minifilter Driver のサンプルコードをベースに、 Windows 用 AntiVirus ソフトウェアのリアルタイムファイルスキャンのしくみを解説します。

しかし、多くの方にとって Windows の「ファイルシステムミニフィルタドライバー」という名称は聞き慣れないものだと思います。

そこで、本章では Scanner のサンプルコードを読み進める前の準備として、 Windows のファイルシステムミニフィルタドライバーの概要を整理し、解説することにします。

なお、ファイルシステムミニフィルタドライバーに関する情報を本格的にまとめようとすると、それだけで 100 ページ以上のボリュームになります。

そのため、本書ではあくまで Scanner のソースコードを読む際の前提となる概要情報のみを扱うこととし、 ファイルシステムミニフィルタドライバーやフィルタマネージャーなどに関する入門的な情報を網羅した解説は行いません。

ファイルシステムフィルタドライバーに関する詳しい情報については、Microsoft により公開されているドキュメントの情報を参照してください。


URL:ファイルシステムフィルタドライバーについて

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/about-file-system-filter-drivers


もくじ

ファイルシステムミニフィルタドライバーとは

Windows ではカーネルモードで動作するファイルシステムフィルタドライバーを使用することで、 ファイル I/O の操作の監視や変更、フィルタリングなどを行うことができます。2

Windows の I/O モデルでは「レガシーファイルシステムフィルタドライバー」と 「ミニフィルタドライバー」の 2 つのファイルシステムフィルタドライバーを利用できます。3

これらの 2 つのうち、レガシーファイルシステムフィルタドライバーは 実装時に考慮する必要がある点が非常に複雑なため開発が難しいことで知られています。

また、レガシーファイルシステムフィルタドライバーは、 ミニフィルタドライバーと比較してシステムの稼働中にアンロードできない点などの複数のデメリットがあり、使用が推奨されていません。4

一方で、本書で扱うミニフィルタドライバーもファイルシステムフィルタドライバーの 1 つですが、 こちらはレガシーファイルシステムフィルタドライバーとしてシステムにロードされている「ファイルシステムフィルタマネージャー(FltMgr.sys)」 のクライアントとして動作します。

ここで登場するファイルシステムフィルタマネージャーは、ファイルシステムフィルタドライバーが必要とする機能を実装するためにシステムが提供しているカーネルドライバです。5

ミニフィルタドライバーは、ファイルシステムフィルタマネージャーを通して間接的に I/O 操作をフィルタリングすることができるため、 レガシーファイルシステムフィルタドライバーと比較して、より効率的かつ安全にファイルシステムフィルタドライバーを実装できます。

ファイルシステムフィルタマネージャーへのミニフィルタドライバーの登録

ミニフィルタドライバーは、ファイルシステムフィルタマネージャーがインターフェースとして提供している機能のサポートを受けることで、 I/O 要求に対する処理をより簡単かつ効率的に実装することが可能です。

ミニフィルタドライバーは、このインターフェースを使用して IRP(I/O Request Packets) ベースの I/O 操作をフィルタリングできます。

ミニフィルタドライバーはレガシーファイルシステムフィルタドライバーとは異なり、 標準の IRP ディスパッチ関数すべての要求に対する処理を実装する必要はなく、 フィルタリングを行いたい I/O 要求に対してのみコールバックルーチンを定義できます。

またその際、ミニフィルタドライバーは、対象の I/O 要求に対して 「Preoperation Callback Routine」と「Postoperation Callback Routine」をそれぞれ登録することができます。

これによって、ミニフィルタドライバーは I/O 操作の要求を受け取るタイミングと、 I/O 操作の完了通知を受け取るタイミングのそれぞれにコールバック関数を設定できます。

以下は、公開されているドキュメントから引用したファイルシステムフィルタマネージャーとミニフィルタドライバーを使用する場合の I/O スタックモデルの図です。7

I/O スタックモデル(公式ドキュメントより引用)

ミニフィルタドライバーの階層(Altitude)

上記の図からわかる通り、ファイルシステムフィルタマネージャーは、Windows の I/O マネージャーとファイルシステムドライバー(ファイルシステム形式を管理し、ファイルに対する抽象化された I/O 要求をディスクストレージなどに対する明確な要求として発行するドライバー)の間を繋ぐようにロードされています。

この時フィルタマネージャーに登録されているミニフィルタドライバーにはそれぞれ Altitude という値がセットされています。

この値は、I/O スタック内でミニフィルタドライバーが IRP(I/O Request Packets) を受け取り、返却する順序を明確に定義するために使用されます。

フィルタマネージャーが I/O 操作要求を受け取った場合、「Altitude がより大きな値を持つミニフィルタドライバーから順(降順)」に「Preoperation Callback Routine」が処理されます。

逆に、フィルタマネージャーが I/O 操作の完了通知を受け取った場合、要求時とは逆順、 つまり「Altitude がより小さい値を持つミニフィルタドライバーから順(昇順)」に「Postoperation Callback Routine」が処理されます。8

ミニフィルタドライバーに割り当てられる Altitude は、そのフィルタの種類ごとに一定の範囲内で決定されます。

このような Altitude の範囲を決定するためのフィルタの種類のことを「Load Order Group(ロード順序グループ)」と呼びます。

Load Order Group で定義されているフィルタの種類と Altitude の範囲の対応については以下のドキュメントで公開されています。


URL:ミニフィルタドライバー用のロード順序グループと高度

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/load-order-groups-and-altitudes-for-minifilter-drivers


例えば、Microsoft Defender ウイルス対策のような AntiVirus ソフトウェアが使用するミニフィルタドライバーは、 「FSFilter AntiVirus」の Load Order Group に該当しており、割り当てられる Altitude は 320000 から 329999 までの範囲から決定されます。

実際に Microsoft Defender ウイルス対策をアンインストールしていない端末で fltmc コマンドを実行すると、 WdFilter の Altitude(階層) として、328010 が登録されていることを確認できます。

これは、「FSFilter AntiVirus」の Load Order Group で定義された範囲内の Altitude です。

WdFilter の Altitude

なお、Windows に正式にインストールする必要があるミニフィルタドライバーの場合、 そのミニフィルタドライバーが使用する Load Order Group の割り当てを Microsoft に申請する必要があります。9

Microsoft に Altitude を割り当てられたフィルタの一覧については以下の URL で公開されており、年に 1 ~ 2 回程度更新されます。


URL:割り当てられたフィルター高度

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/allocated-altitudes


ミニフィルタドライバーの Altitude の登録にこのような制限がある理由の 1 つは、 そのミニフィルタドライバーが本来行うべき操作が意図せずに保証されない状況の発生を回避するためです。10

例えば、データのスキャンと暗号化機能を持つミニフィルタドライバーがそれぞれ存在している場合、 データの書き込み要求が発生した際に暗号化機能を持つミニフィルタドライバーがスキャナーよりも先に呼び出された結果、 スキャンの前にデータが暗号化されてしまうことでミニフィルタドライバーのスキャンに問題が発生する可能性があります。

上記のような問題の発生を回避して各ミニフィルタドライバーの機能を適切に利用することが、 Load Order Group によってミニフィルタドライバーの用途別に使用可能な Altitude の範囲が制限されている重要な目的の 1 つといえます。

小さなミニフィルタドライバーをつくる

本章では、3 章以降で Scanner のソースコードを読む準備として、シンプルなミニフィルタドライバーを実装してみることにします。

まずは 1 章でダウンロードしたサンプルコードリポジトリ内のファイルを展開し、 [filesys]>[miniFilter]>[nullFilter] のフォルダ内にある nullFilter.sln を Visual Studio で開きます。


URL:Windows-driver-samples

https://github.com/Microsoft/Windows-driver-samples


ここで使用する nullFilter は、自身をフィルタマネージャーに登録する機能だけを持つシンプルなミニフィルタドライバーです。

nullFilter にはコールバック関数が登録されていないため、その名前の通り何のフィルタリングも行いません。

まずは、ドライバーのロード時に最初に呼び出される DriverEntry 関数のコードを見てみましょう。

status = FltRegisterFilter(
    DriverObject,
    &FilterRegistration,
    &NullFilterData.FilterHandle 
);

FLT_ASSERT( 
    NT_SUCCESS( status ) 
);

if (NT_SUCCESS( status )) {

    status = FltStartFiltering( 
        NullFilterData.FilterHandle 
    );

    if (!NT_SUCCESS( status )) {
        FltUnregisterFilter( 
            NullFilterData.FilterHandle 
        );
    }
}

本章では、この DriverEntry 関数の中で実行している以下の 3 つの関数に着目します。

  • FltRegisterFilter
  • FltStartFiltering
  • FltUnregisterFilter

FltRegisterFilter 関数でミニフィルタドライバーを登録する

最初に呼び出される FltRegisterFilter 関数11FLT_REGISTRATION 構造体12 へのポインタを引数としてミニフィルタドライバーの登録を行う関数です。

この関数は、以下の通り 3 つの引数を受けとります。

NTSTATUS FLTAPI FltRegisterFilter(
  [in]  PDRIVER_OBJECT         Driver,
  [in]  const FLT_REGISTRATION *Registration,
  [out] PFLT_FILTER            *RetFilter
);

1 つ目のメンバである Driver には、ミニフィルタドライバーのドライバオブジェクトへのポインタとして、 DriverEntry 関数の引数として受け渡されたポインタがそのまま使用されます。

そして、2 つ目の引数で FLT_REGISTRATION 構造体へのポインタを受け取り、 RetFilter で受け取ったポインタが指すアドレスに登録したミニフィルタドライバーのポインタを格納します。

この時引数として渡される FLT_REGISTRATION 構造体は、ミニフィルタドライバーの登録に必要な情報を持つ構造体です。

nullFilter が FltRegisterFilter 関数を実行する際に引数として与えられる FLT_REGISTRATION 構造体は以下の通り定義されます。

CONST FLT_REGISTRATION FilterRegistration = {

 sizeof( 
    FLT_REGISTRATION 
 ),                       //  Size
 FLT_REGISTRATION_VERSION,//  Version
 0,                       //  Flags

 NULL,                    //  Context
 NULL,                    //  Operation callbacks

 NullUnload,              //  FilterUnload

 NULL,                    //  InstanceSetup
 NullQueryTeardown,       //  InstanceQueryTeardown
 NULL,                    //  InstanceTeardownStart
 NULL,                    //  InstanceTeardownComplete

 NULL,                    //  GenerateFileName
 NULL,                    //  GenerateDestinationFileName
 NULL                     //  NormalizeNameComponent

};

FLT_REGISTRATION 構造体ではまず、FLT_REGISTRATION 構造体自身のサイズと、 ミニフィルタドライバーのターゲットとなる Windows のバージョン情報(リビジョンレベル)を指定します。

多くの場合、ミニフィルタドライバーの登録時にフィルタマネージャーに連携するバージョン情報は、 fltKernel.h 内で定義されている FLT_REGISTRATION_VERSION を使用します。

FLT_REGISTRATION 構造体にはさらに、ミニフィルタドライバーが使用するコンテキストの配列である ContextRegistration や、 IRP のルーチンと対応するコールバック関数を登録する OperationRegistration などが含まれます。

しかし、本章で使用する nullFilter ではこれらの項目は登録しないため、コンテキストやコールバック関数の登録については後述することにします。

続く FilterUnload には、ミニフィルタドライバーの FilterUnloadCallback ルーチンとして登録するコードへのポインタが登録されます。

FilterUnloadCallback ルーチンはミニフィルタドライバーをアンロードする際に呼び出されるもので、 この FilterUnloadCallback が NULL の場合にはミニフィルタドライバーをアンロードすることができなくなります。

nullFilter の場合は NullUnload 関数という、ミニフィルタドライバーの登録を解除する FltUnregisterFilter 関数を呼び出すコードが定義されています。

実際に nullFilter の FLT_REGISTRATION 構造体定義を変更して FilterUnloadCallback の値を NULL にしたコードからミニフィルタドライバーをビルドすると、 fltmc unload コマンドでアンロードを試みた際にエラーが発生して失敗するようになることを確認できます。

FilterUnloadCallback を NULL にした場合

続けて、FLT_REGISTRATION 構造体では InstanceSetupCallback ルーチンや InstanceQueryTeardownCallback ルーチンなど、 ミニフィルタドライバーのインスタンスに関するコールバック関数を登録します。

インスタンスについては本章では詳しくは扱いませんが、 特定の Altitude で特定のボリューム(固定ディスクなどのストレージデバイスで、ディレクトリやファイルを保存するためにフォーマットされたもの)に アタッチされたミニフィルタドライバーを、ミニフィルタドライバーのインスタンスと呼びます。13

nullFilter で定義される FLT_REGISTRATION 構造体では、 InstanceQueryTeardownCallback ルーチンのみが登録されており、 他の設定値は NULL が指定されています。

InstanceQueryTeardownCallback は、 ミニフィルタドライバーのインスタンスをデタッチする際に呼び出されるルーチンです。

ミニフィルタドライバーのインスタンスは、FltDetachVolume 関数14 や FilterDetach 関数15 によってボリュームからデタッチすることができますが、 InstanceQueryTeardownCallback を利用するとインスタンスのデタッチ操作を検証することなどができるようになります。

FltStartFiltering 関数でミニフィルタドライバーの処理を開始する

FltRegisterFilter 関数によるミニフィルタドライバーの登録が完了したら、 FltStartFiltering 関数16 を使用してミニフィルタドライバーのフィルタ処理を開始します。

この関数は、引数として FltRegisterFilter 関数で登録したミニフィルタドライバーへのポインタを受け取ります。

NTSTATUS FLTAPI FltStartFiltering(
  [in] PFLT_FILTER Filter
);

FltRegisterFilter 関数は、登録したミニフィルタドライバーがボリュームにアタッチし、 I/O 要求をフィルタする準備が整っていることをフィルタマネージャーに通知します。

この要求が完了すると、フィルタマネージャーは登録されたミニフィルタドライバーをアクティブなフィルタとして扱い、I/O 要求のフィルタリングを開始できます。

なお、nullFilter の DriverEntry 内では、FltRegisterFilter 関数の処理に失敗した場合、 FltUnregisterFilter 関数17 によりミニフィルタドライバーのアンロードを行います。

FltUnregisterFilter 関数が実行された場合、登録したミニフィルタドライバーのコールバック関数はすべて解除され、設定したコンテキストも削除されます。

また、ミニフィルタドライバーのインスタンスごとに InstanceTeardownStartCallback ルーチンと InstanceTeardownCompleteCallback ルーチンをそれぞれ実行します。

nullFilter のビルドとインストール

本章ではここまで nullFilter サンプルドライバーがフィルタマネージャーにミニフィルタドライバーを登録する処理について確認しました。

次は、実際に nullFilter サンプルドライバーを Visual Studio でビルドし、ミニフィルタドライバーを仮想マシンにロードしてみます。

まずは、1 章でダウンロードした以下のサンプルコードリポジトリを展開し、 [filesys]>[miniFilter]>[nullFilter] のフォルダ内にある nullFilter.sln を Visual Studio で開きます。


URL:Windows-driver-samples

https://github.com/Microsoft/Windows-driver-samples


nullFilter のソリューションファイルを Visual Studio のソリューションエクスプローラで開いたら、 表示されるプロジェクトを右クリックしてプロパティを開きます。

プロパティファイルの参照

プロジェクトのプロパティを開いたら、[Windows SDK バージョン] のプルダウンから、Visual Studio のセットアップ時に SDK と WDK をインストールしたバージョンを指定します。

SDK バージョンを指定する

次に、[Inf2Cat]>[General] の設定を開き、[Use Local Time] の設定を有効化します。

これは、システム時刻を日本のタイムゾーンに設定している環境でソリューションをビルドする際に 22.9.7: DriverVer set to a date in the future (postdated DriverVer not allowed) というエラーでドライバーのテスト署名に失敗する問題を回避するための設定です。

Inf2Cat の User Local Time 設定

最低限のプロパティの設定変更が完了したら、Visual Studio 上部のメニュー内の [ビルド] から [ソリューションのビルド] をクリックすることで nullFilter のビルドに成功するはずです。

nullFilter のビルドに成功した場合、ビルドされたファイルは既定で nullFilter.sln ファイルと同じフォルダからの相対パスが ./x64/Debug/nullFilter となるフォルダに配置されます。 (アクティブな構成が Debug 以外の場合や ARM プラットフォーム向けにビルドを行った場合には作成されるフォルダ名は異なります)

ソリューションのビルド後、上記のフォルダに以下の 3 つのファイルが生成されていることを確認します。

  • nullFilter.sys
  • nullFilter.inf
  • nullFilter.cat

これらのファイルは、いずれもドライバーパッケージのコンポーネント18 として必要なファイルです。

まず、nullFilter.sys はミニフィルタドライバー本体となる PE ファイルです。

そして、nullFilter.inf はドライバーのインストールに必要な構成情報が記述されているファイルです。

INF ファイルの中には、ドライバーがサポートしている OS のバージョン情報、セットアップやインストールに関する構成情報、使用するカタログファイル(.cat ファイル)の名前などが含まれます。

最後の nullFilter.cat は、ドライバーパッケージ内のファイルの暗号化ハッシュを記述したファイルです。

Windows はこのカタログファイル内の暗号化ハッシュ情報を使用し、ドライバーファイルが改ざんされていないことを確認します。

なお、カタログファイル自身が改ざんされていないことを保証するため、 ドライバーファイルと同じくカタログファイルもデジタル署名されている必要があります。

これらのファイルが生成されていることを確認したら、そのファイルをセットアップした仮想マシンにコピーします。

続けて、コピーしたファイルのうち nullFilter.inf を右クリックして [インストール] を実行します。

INF ファイルを使用してインストールを行う

[インストール] を実行しても画面上の変化は特に発生しませんが、正常にインストールが完了している場合、INF ファイル内で定義している通り C:\Windows\System32\drivers 直下に NullFilter.sys ファイルがコピーされ、 サービスレジストリに nullFilter ミニフィルタドライバーに関する情報が登録されたことを確認できます。

インストール後のサービスレジストリ

このサービスレジストリキーには、INF ファイルの定義に従ってドライバーの種類や名前、 実行イメージのパスや起動設定など、いくつかの情報が表示されます。

例えば、[Instance] キー内にはミニフィルタドライバーをボリュームにアタッチしたインスタンスに関する設定が書き込まれており、 ここから登録されている Altitude の設定を確認することができます。

インスタンスキーの情報

nullFilter のインストールが完了したら、fltmc19 コマンドを使用してミニフィルタドライバーのロードを行います。

まずは管理者権限でコマンドプロンプトを起動し、fltmc コマンドを実行してみましょう。

すでにインストールは完了しているものの、この時点ではまたフィルタマネージャーには登録していないため、 nullFilter はロードされておらず、一覧に表示されないことを確認できます。

fltmc コマンドによるロードされているフィルタの確認

そこで、続けて fltmc load nullFilter コマンドを実行します。

これで、インストールした nullFilter がフィルタマネージャーに登録され、ミニフィルタドライバーとしてロードされたことを確認できます。

この時、Altitude にはインストール時に登録されたレジストリで設定した通り 370020 が割り当てられてました。

nullFilter をロードする

続けて、fltmc attach コマンドを使用して、ロードした nullFilter ミニフィルタドライバーのインスタンスをボリュームにアタッチしてみます。

ボリューム名については、fltmc volumes コマンドで確認することができます。

nullFilter をインスタンスにアタッチする

これで nullFilter をフィルタマネージャーに登録してインスタンスにアタッチすることができました。

最後に fltmc unload nullFilter コマンドを実行してミニフィルタドライバーをアンロードします。

これでもう一度 fltmc コマンドを実行すると、nullFilter が一覧から削除されることを確認できます。

DbgPrint を使用する

本章ではここまで nullFilter ミニフィルタドライバーのサンプルコードの実装を確認しました。

しかし、nullFilter は名前の通り本当に何のフィルタリングも行いません。

そこで、ミニフィルタドライバーについて理解を深めるため、 本章では nullFilter ミニフィルタドライバーのソースコードを少しずつ書き換えつつ、仮想マシンでその動作を確認していくことにします。

まずは、nullFilter で定義されている NullUnload 関数と NullQueryTeardown 関数の 2 つのコールバック関数に DbgPrintEx20 関数を追記し、 ミニフィルタドライバーのインスタンスのデタッチとアンロードの際にコールバック関数が実行されることを確かめてみましょう。

使用するコードは以下のリポジトリの 01-add-debug-comment ブランチにて公開しています。


URL:MFLab 01-add-debug-comment

https://github.com/kash1064/MFLab/tree/01-add-debug-comment


このコードからもう一度ミニフィルタドライバーをビルドし、仮想マシンに再インストールします。

この時、ビルド構成は Release ではなく Debug を使用する必要があります。

ミニフィルタドライバーの再インストールが完了したら、続いて仮想マシン内で Sysinternals Suite に同梱されている Dbgview.exe を起動し、 [Capture] から [Capture Kernel] と [Enable Verbose Kernel Output] のチェックを有効化します。

Dbgview のキャプチャ設定を変更する

これで、fltmc コマンドでミニフィルタドライバーのインスタンスのデタッチやアンロードを行うことで、 確かに NullUnload 関数や NullQueryTeardown 関数に追記した DbgPrintEx 関数によるデバッグ出力が行われることを確認することができます。

DbgPrintEx によりコールバック関数の動作を確認する

IRP のルーチンと対応するコールバック関数を登録する

DbgPrintEx 関数によるデバッグ出力によって nullFilter が登録していたコールバック関数が実行されていることを確認できました。

次は、nullFilter に IRP のルーチンと対応するコールバック関数を登録することで、実際に I/O 要求をフィルタリングするコードを追加してみます。

IRP のルーチンと対応するコールバック関数を登録するには、フィルタマネージャーに登録する FLT_REGISTRATION 構造体の OperationRegistration に FLT_OPERATION_REGISTRATION 構造体の配列へのポインタを追加する必要があります。

今回は、以下のように IRP_MJ_CREATE 要求と対応するコールバック関数を登録するための FLT_OPERATION_REGISTRATION 構造体を配列 Callbacks に登録することとします。

以下の構造体配列の中では、IRP_MJ_CREATE 要求と対応するコールバック関数として、FuncPreCreate 関数と FuncPostCreate 関数を指定しています。

前述した通り、この FuncPreCreate 関数は IRP_MJ_CREATE 要求をミニフィルタドライバーが受け取ったタイミングで実行され、 逆に FuncPostCreate 関数はその I/O 要求の完了通知をミニフィルタドライバーが受け取ったタイミングで実行されます。

const FLT_OPERATION_REGISTRATION Callbacks[] = {

    { IRP_MJ_CREATE,
      0,
      FuncPreCreate,
      FuncPostCreate},

    { IRP_MJ_OPERATION_END }
};

ちなみに { IRP_MJ_OPERATION_END }FLT_REGISTRATION 構造体の OperationRegistration に渡す構造体配列を定義する際の配列の末尾に必ずセットする必要がある要素です。21

以下は、IRP_MJ_CREATE 要求と対応するコールバック関数として登録した FuncPreCreate 関数と FuncPostCreate 関数のコードです。

今回は単純なテストのため、どちらの関数も受け取った I/O 要求の操作対象のファイル名をデバッグ出力する処理のみを実装しています。

FLT_PREOP_CALLBACK_STATUS
FuncPreCreate(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    PAGED_CODE();

    NTSTATUS status;
    PFLT_FILE_NAME_INFORMATION nameInfo;

    status = FltGetFileNameInformation(
        Data,
        FLT_FILE_NAME_NORMALIZED |
        FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo
    );

    if (!NT_SUCCESS(status)) {

        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    FltParseFileNameInformation(nameInfo);

    DbgPrintEx(
        DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
        "FuncPreCreate %wZ.\n",
        &nameInfo->FinalComponent
    );

    FltReleaseFileNameInformation(nameInfo);

    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

FLT_POSTOP_CALLBACK_STATUS
FuncPostCreate(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _In_opt_ PVOID CompletionContext,
    _In_ FLT_POST_OPERATION_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);

    NTSTATUS status;
    PFLT_FILE_NAME_INFORMATION nameInfo;

    status = FltGetFileNameInformation(Data,
        FLT_FILE_NAME_NORMALIZED |
        FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo
    );

    if (!NT_SUCCESS(status)) {

        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    FltParseFileNameInformation(nameInfo);

    DbgPrintEx(
        DPFLTR_IHVDRIVER_ID, 
        DPFLTR_INFO_LEVEL, 
        "FuncPostCreate %wZ.\n", 
        &nameInfo->FinalComponent
    );

    FltReleaseFileNameInformation(nameInfo);

    return FLT_POSTOP_FINISHED_PROCESSING;
}

なお、上記の関数内の実装の通り、ミニフィルタドライバーは FltGetFileNameInformation 関数22 と FltParseFileNameInformation 関数23 を使用して要求と対応するファイルの情報をクエリ・解析することで、 FLT_FILE_NAME_INFORMATION 構造体24 を使用してファイル名を含む情報にアクセスできるようになります。25

この変更を含むソースコードは以下のリポジトリの 02-add-irp-callback-routine ブランチにて公開しています。


URL:MFLab 02-add-irp-callback-routine

https://github.com/kash1064/MFLab/tree/02-add-irp-callback-routine


このコードをビルドし、前項と同じ手順でインストールしたミニフィルタドライバーをボリュームにアタッチします。

コールバック関数を実装してしまったのですでに nullFilter とは呼べない状況ですが、ドライバー名やファイル名は変更していません。

そのため、インストールやボリュームへのアタッチは前項までと同じコマンドを使用できます。

更新したミニフィルタドライバーボリュームにアタッチすると、以下のように FuncPreCreate 関数と FuncPostCreate 関数のそれぞれが呼び出され、 IRP_MJ_CREATE 要求と対応するファイル名をデバッグ出力できるようになったことを確認できます。

FuncPreCreate 関数と FuncPostCreate 関数によるデバッグ出力

ファイルにコンテキストを付与する

最後に、nullFilter ミニフィルタドライバーにコンテキストを付与する機能を追加してみます。

コンテキストとは、ミニフィルタドライバーがフィルタマネージャーオブジェクトにアタッチすることができる構造体です。26

この構造体情報にはそのオブジェクトの生存期間中は一貫してアクセスが可能なため、I/O 操作全体で利用することができます。

AntiVirus ソフトウェアのミニフィルタドライバーがファイルやストリームなどにコンテキストを割り当てる理由の 1 つは「パフォーマンスの向上」です。

前述の通りコンテキストはアタッチした対象が存在する限り一貫してアクセスが可能ですので、 例えば Preoperation Callback Routine で計算した何らかの情報をコンテキストに保存し、 I/O 要求の完了後に呼び出される Postoperation Callback Routine で参照して計算を効率化するといった実装が可能です。

ミニフィルタドライバーがオブジェクトにコンテキストをアタッチするためにはまず、 FLT_REGISTRATION 構造体の ContextRegistration に FLT_CONTEXT_REGISTRATION 構造体27 の配列へのポインタを設定する必要があります。

この構造体は以下の通り定義されており、コンテキストをアタッチする対象(ファイルやボリュームなど)の指定やコンテキストのサイズ、 またコンテキストの割り当てやクリーンアップ時のコールバック関数を設定します。

typedef struct _FLT_CONTEXT_REGISTRATION {
  FLT_CONTEXT_TYPE               ContextType;
  FLT_CONTEXT_REGISTRATION_FLAGS Flags;
  PFLT_CONTEXT_CLEANUP_CALLBACK  ContextCleanupCallback;
  SIZE_T                         Size;
  ULONG                          PoolTag;
  PFLT_CONTEXT_ALLOCATE_CALLBACK ContextAllocateCallback;
  PFLT_CONTEXT_FREE_CALLBACK     ContextFreeCallback;
  PVOID                          Reserved1;
} FLT_CONTEXT_REGISTRATION, *PFLT_CONTEXT_REGISTRATION;

今回はテストのために以下の構造体を追加することにしました。

typedef struct _DATA_CONTEXT {

    UNICODE_STRING message;

} DATA_CONTEXT, * PDATA_CONTEXT;

const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {

    { FLT_FILE_CONTEXT,
      0,
      NULL,
      sizeof(DATA_CONTEXT),
      'galF' },

    { FLT_CONTEXT_END }
};

上記では、まず、コンテキスト用に message というメンバを持つ DATA_CONTEXT 構造体を独自に定義しています。

続けて定義している ContextRegistration は、FLT_CONTEXT_REGISTRATION 構造体の配列です。

この配列には、ContextType を FLT_FILE_CONTEXT とする構造体と { FLT_CONTEXT_END } の 2 つの要素を追加しています。 ({ FLT_CONTEXT_END } は、FLT_CONTEXT_REGISTRATION 構造体の配列を定義する際に末尾の要素に追加する必要があります。)

ContextType を FLT_FILE_CONTEXT とする構造体では、コンテキストの種類に加えて、 ミニフィルタドライバーが作成するコンテキストのサイズと、コンテキストに使用するプールタグを定義しています。

ここで定義した構造体配列は、前項と同じく FilterRegistration を通してフィルタマネージャーに登録されます。

実際に特定のファイルにコンテキストを割り当てる処理は、I/O 要求の完了後に呼び出される FuncPostCreate で実装することにします。

今回作成した FuncPostCreate 関数の中では、FltGetFileContext 関数28 を使用して対象のファイルに設定されたコンテキストの取得を試みます。

status = FltGetFileContext(
    Data->Iopb->TargetInstance,
    Data->Iopb->TargetFileObject,
    &dataContext
);

FltGetFileContext 関数は、対象のファイルにコンテキストが割り当てられていない場合、STATUS_NOT_FOUND などのエラーコードを返します。

FuncPostCreate 関数ではこの関数の戻り値を検証し、エラーを返した場合には対象のファイルに新規にコンテキストを割り当てる処理に進みます。

逆に、FltGetFileContext 関数でファイルに割り当てられているコンテキストの取得に成功した場合には、新規にコンテキストを割り当てる処理をすべてスキップします。

コンテキストが割り当てられていないファイルにコンテキストを割り当てる場合は、FltAllocateContext 関数29 と FltSetFileContext 関数30 を使用します。

status = FltAllocateContext(
    NullFilterData.FilterHandle,
    FLT_FILE_CONTEXT,
    sizeof(DATA_CONTEXT),
    PagedPool,
    &dataContext
);

// 事前に dataContext を変更しておく
status = FltSetFileContext(
    Data->Iopb->TargetInstance,
    Data->Iopb->TargetFileObject,
    FLT_SET_CONTEXT_KEEP_IF_EXISTS,
    dataContext,
    NULL
);

FltAllocateContext 関数は、指定した対象(今回は FLT_FILE_CONTEXT を使用)にコンテキストを割り当てることができる関数です。

この時割り当てられるコンテキストは、FLT_CONTEXT_REGISTRATION 構造体で指定したプールタグを使用して 指定のプール(PagedPool など)から確保された領域に保存されます。

そして、FltSetFileContext 関数は、FltAllocateContext 関数で割り当てたコンテキストへのポインタを引数として、対象のファイルにコンテキストを設定する関数です。

今回のプログラムでは、これらの関数を利用して IRP_MJ_CREATE 要求と対応するファイル名が test.txt の場合のみコンテキストに File is test.txt!! というテキストを書き込み、それ以外の場合には Not valid file. というテキストを書き込む操作を行います。

作成したコードは以下のリポジトリの 03-add-file-context ブランチにて公開しています。


URL:MFLab 03-add-file-context

https://github.com/kash1064/MFLab/tree/03-add-file-context


このリポジトリからダウンロードしたコードをビルドし、仮想マシンに再インストールを行った後に test.txt というファイルを作成すると、 ファイルコンテキストが割り当てられ、その後同ファイルに対して IRP_MJ_CREATE 要求が発生する度にコンテキストに登録したテキストを参照できるようになりました。

test.txt にコンテキストをアタッチする

ミニフィルタドライバーの情報をデバッガから参照する

本章ではここまでに、nullFilter ミニフィルタドライバーをベースにコールバック関数とファイルへのコンテキストのアタッチを実装しました。

最後に、仮想マシンにカーネルデバッガをアタッチしてミニフィルタドライバーの情報を参照することに挑戦します。

ミニフィルタドライバーをインストールした仮想マシンにカーネルデバッガをアタッチしたら、まずは !fltkd.filters を実行します。

!fltkd 拡張機能31 はミニフィルタドライバーのデバッグに使用可能なデバッガ拡張機能です。

!fltkd.filters コマンドを実行すると、システムにロードされているミニフィルタドライバーとその Altitude、そしてボリュームにアタッチされているインスタンスの情報を参照することができます。

!fltkd.filters の実行結果

さらに、!fltkd.filters コマンドで特定した nullFilter のアドレスを使用して !fltkd.filter <ミニフィルタドライバーのアドレス> を実行することで、対象のミニフィルタドライバーの構成情報をダンプすることができます。

!fltkd.filter の実行結果

また、同じく !fltkd.filters コマンドで特定したインスタンスのアドレスを使用して !fltkd.instance <インスタンスのアドレス> を実行することで、インスタンスをアタッチしているボリュームなどの情報をダンプすることができます。

!fltkd.instance の実行結果

インスタンスがアタッチしているボリュームの情報は !fltkd.volumes!fltkd.volume <ボリュームのアドレス> コマンドでも確認できます。

!fltkd.volumes の実行結果

本章のまとめ

本章では、nullFilter ミニフィルタドライバーのサンプルコードを通して、 Windows のファイルシステムミニフィルタドライバーの登録や実装に関する入門的な情報を解説しました。

次章では、Scanner のサンプルコードを通して Windows 用 AntiVirus ソフトウェアの実装について解説していきます。

本書のもくじ


  1. Windows Driver Samples - scanner: https://github.com/microsoft/Windows-driver-samples/tree/main/filesys/miniFilter/scanner

  2. ファイルシステムフィルタドライバーについて https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/about-file-system-filter-drivers

  3. インサイド Windows 第 7 版 下 P.653,654 (Andrea Allievi, Alex Ionescu 著 / 山内 和朗 訳 / 日系 BP 社 / 2022 年)

  4. フィルタマネージャーモデルの利点 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/advantages-of-the-filter-manager-model

  5. インサイド Windows 第 7 版 下 P.654 (Andrea Allievi, Alex Ionescu 著 / 山内 和朗 訳 / 日系 BP 社 / 2022 年)

  6. フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  7. フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  8. フィルター高度識別子を要求する https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/minifilter-altitude-request

  9. Windows Kernel Programming, Second Edition P.388 (Pavel Yosifovich 著 / Independently published / 2023 年)

  10. FltRegisterFilter 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltregisterfilter

  11. FLT_REGISTRATION 構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltregistration

  12. フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  13. FltDetachVolume 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltdetachvolume

  14. FilterDetach 関数 https://learn.microsoft.com/ja-jp/windows/win32/api/fltuser/nf-fltuser-filterdetach

  15. FltStartFiltering 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltstartfiltering

  16. FltUnregisterFilter 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltunregisterfilter

  17. ドライバーパッケージのコンポーネント https://learn.microsoft.com/ja-jp/windows-hardware/drivers/install/components-of-a-driver-package

  18. Fltmc.exe コマンド https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools

  19. DbgPrintEx 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/wdm/nf-wdm-dbgprintex

  20. FLT_OPERATION_REGISTRATION 構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltoperation_registration

  21. FltGetFileNameInformation 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilenameinformation

  22. FltParseFileNameInformation 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltparsefilenameinformation

  23. FLT_FILE_NAME_INFORMATION 構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltfilenameinformation

  24. Windows Kernel Programming, Second Edition P.398 (Pavel Yosifovich 著 / Independently published / 2023 年)

  25. ミニフィルタコンテキストについて https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/managing-contexts-in-a-minifilter-driver

  26. FLT_CONTEXT_REGISTRATION 構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltcontext_registration

  27. FltGetFileContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilecontext

  28. FltAllocateContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltallocatecontext

  29. FltSetFileContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltsetfilecontext

  30. !fltkd 拡張機能 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools