本書では、公式の 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
もくじ
- ファイルシステムミニフィルタドライバーとは
- ファイルシステムフィルタマネージャーへのミニフィルタドライバーの登録
- ミニフィルタドライバーの階層(Altitude)
- DbgPrint を使用する
- IRP のルーチンと対応するコールバック関数を登録する
- ファイルにコンテキストを付与する
- ミニフィルタドライバーの情報をデバッガから参照する
- 本章のまとめ
ファイルシステムミニフィルタドライバーとは
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
ミニフィルタドライバーの階層(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:ミニフィルタドライバー用のロード順序グループと高度
例えば、Microsoft Defender ウイルス対策のような AntiVirus ソフトウェアが使用するミニフィルタドライバーは、 「FSFilter AntiVirus」の Load Order Group に該当しており、割り当てられる Altitude は 320000 から 329999 までの範囲から決定されます。
実際に Microsoft Defender ウイルス対策をアンインストールしていない端末で fltmc コマンドを実行すると、 WdFilter の Altitude(階層) として、328010 が登録されていることを確認できます。
これは、「FSFilter AntiVirus」の Load Order Group で定義された範囲内の 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 関数11 は FLT_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
コマンドでアンロードを試みた際にエラーが発生して失敗するようになることを確認できます。
続けて、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 をインストールしたバージョンを指定します。
次に、[Inf2Cat]>[General] の設定を開き、[Use Local Time] の設定を有効化します。
これは、システム時刻を日本のタイムゾーンに設定している環境でソリューションをビルドする際に 22.9.7: DriverVer set to a date in the future (postdated DriverVer not allowed)
というエラーでドライバーのテスト署名に失敗する問題を回避するための設定です。
最低限のプロパティの設定変更が完了したら、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 ファイル内で定義している通り C:\Windows\System32\drivers
直下に NullFilter.sys ファイルがコピーされ、
サービスレジストリに nullFilter ミニフィルタドライバーに関する情報が登録されたことを確認できます。
このサービスレジストリキーには、INF ファイルの定義に従ってドライバーの種類や名前、 実行イメージのパスや起動設定など、いくつかの情報が表示されます。
例えば、[Instance] キー内にはミニフィルタドライバーをボリュームにアタッチしたインスタンスに関する設定が書き込まれており、 ここから登録されている Altitude の設定を確認することができます。
nullFilter のインストールが完了したら、fltmc19 コマンドを使用してミニフィルタドライバーのロードを行います。
まずは管理者権限でコマンドプロンプトを起動し、fltmc コマンドを実行してみましょう。
すでにインストールは完了しているものの、この時点ではまたフィルタマネージャーには登録していないため、 nullFilter はロードされておらず、一覧に表示されないことを確認できます。
そこで、続けて fltmc load nullFilter
コマンドを実行します。
これで、インストールした nullFilter がフィルタマネージャーに登録され、ミニフィルタドライバーとしてロードされたことを確認できます。
この時、Altitude にはインストール時に登録されたレジストリで設定した通り 370020 が割り当てられてました。
続けて、fltmc attach
コマンドを使用して、ロードした nullFilter ミニフィルタドライバーのインスタンスをボリュームにアタッチしてみます。
ボリューム名については、fltmc volumes
コマンドで確認することができます。
これで 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] のチェックを有効化します。
これで、fltmc コマンドでミニフィルタドライバーのインスタンスのデタッチやアンロードを行うことで、 確かに NullUnload 関数や NullQueryTeardown 関数に追記した 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
要求と対応するファイル名をデバッグ出力できるようになったことを確認できます。
ファイルにコンテキストを付与する
最後に、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
要求が発生する度にコンテキストに登録したテキストを参照できるようになりました。
ミニフィルタドライバーの情報をデバッガから参照する
本章ではここまでに、nullFilter ミニフィルタドライバーをベースにコールバック関数とファイルへのコンテキストのアタッチを実装しました。
最後に、仮想マシンにカーネルデバッガをアタッチしてミニフィルタドライバーの情報を参照することに挑戦します。
ミニフィルタドライバーをインストールした仮想マシンにカーネルデバッガをアタッチしたら、まずは !fltkd.filters
を実行します。
!fltkd
拡張機能31 はミニフィルタドライバーのデバッグに使用可能なデバッガ拡張機能です。
!fltkd.filters
コマンドを実行すると、システムにロードされているミニフィルタドライバーとその Altitude、そしてボリュームにアタッチされているインスタンスの情報を参照することができます。
さらに、!fltkd.filters
コマンドで特定した nullFilter のアドレスを使用して !fltkd.filter <ミニフィルタドライバーのアドレス>
を実行することで、対象のミニフィルタドライバーの構成情報をダンプすることができます。
また、同じく !fltkd.filters
コマンドで特定したインスタンスのアドレスを使用して !fltkd.instance <インスタンスのアドレス>
を実行することで、インスタンスをアタッチしているボリュームなどの情報をダンプすることができます。
インスタンスがアタッチしているボリュームの情報は !fltkd.volumes
や !fltkd.volume <ボリュームのアドレス>
コマンドでも確認できます。
本章のまとめ
本章では、nullFilter ミニフィルタドライバーのサンプルコードを通して、 Windows のファイルシステムミニフィルタドライバーの登録や実装に関する入門的な情報を解説しました。
次章では、Scanner のサンプルコードを通して Windows 用 AntiVirus ソフトウェアの実装について解説していきます。
本書のもくじ
- まえがき
- 1 章 本書で使用する環境のセットアップ
- 2 章 ファイルシステムミニフィルタドライバー入門
- 3 章 Scanner のサンプルコードを読む
- 4 章 WinDbg で Scanner をカーネルデバッグする
-
Windows Driver Samples - scanner: https://github.com/microsoft/Windows-driver-samples/tree/main/filesys/miniFilter/scanner
↩ -
ファイルシステムフィルタドライバーについて https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/about-file-system-filter-drivers
↩ -
インサイド Windows 第 7 版 下 P.653,654 (Andrea Allievi, Alex Ionescu 著 / 山内 和朗 訳 / 日系 BP 社 / 2022 年)
↩ -
フィルタマネージャーモデルの利点 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/advantages-of-the-filter-manager-model
↩ -
インサイド Windows 第 7 版 下 P.654 (Andrea Allievi, Alex Ionescu 著 / 山内 和朗 訳 / 日系 BP 社 / 2022 年)
↩ -
フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts
↩ -
フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts
↩ -
フィルター高度識別子を要求する https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/minifilter-altitude-request
↩ -
Windows Kernel Programming, Second Edition P.388 (Pavel Yosifovich 著 / Independently published / 2023 年)
↩ -
FltRegisterFilter 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltregisterfilter
↩ -
↩FLT_REGISTRATION
構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltregistration -
フィルタマネージャーの概念 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts
↩ -
FltDetachVolume 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltdetachvolume
↩ -
FilterDetach 関数 https://learn.microsoft.com/ja-jp/windows/win32/api/fltuser/nf-fltuser-filterdetach
↩ -
FltStartFiltering 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltstartfiltering
↩ -
FltUnregisterFilter 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltunregisterfilter
↩ -
ドライバーパッケージのコンポーネント https://learn.microsoft.com/ja-jp/windows-hardware/drivers/install/components-of-a-driver-package
↩ -
Fltmc.exe コマンド https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools
↩ -
DbgPrintEx 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/wdm/nf-wdm-dbgprintex
↩ -
↩FLT_OPERATION_REGISTRATION
構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltoperation_registration -
FltGetFileNameInformation 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilenameinformation
↩ -
FltParseFileNameInformation 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltparsefilenameinformation
↩ -
↩FLT_FILE_NAME_INFORMATION
構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltfilenameinformation -
Windows Kernel Programming, Second Edition P.398 (Pavel Yosifovich 著 / Independently published / 2023 年)
↩ -
ミニフィルタコンテキストについて https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/managing-contexts-in-a-minifilter-driver
↩ -
↩FLT_CONTEXT_REGISTRATION
構造体 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltcontext_registration -
FltGetFileContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilecontext
↩ -
FltAllocateContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltallocatecontext
↩ -
FltSetFileContext 関数 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltsetfilecontext
↩ -
!fltkd 拡張機能 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools
↩