前の章では、WFP フィルターを登録して通信のブロックや許可を行うシンプルなユーザーモードプログラムを解説しました。
続いて、Antivirus や EDR などのソフトウェアでも広く利用されている WFP Callout ドライバーについて解説を行います。
WFP の Callout ドライバーについては、公式の Windows-driver-samples リポジトリの ./network/trans/ 配下にいくつかのサンプルプログラムが公開されています。
特に、このリポジトリ内で公開されている inspect サンプルは WFP Callout ドライバーによるトラフィックインスペクションの基礎的な機能を実装しており、 Antivirus や EDR による WFP の利用方法に近しいサンプルの 1 つと言えます。
しかし、このサンプルプログラムはおよそ 3,000 行程度のコードで実装されており、 WFP の Callout ドライバーの概要から順を追って解説するには少々複雑です。
そこで、本章では上記の公式のサンプルコードを参考に作成した、 Callout ドライバーによる基本的なトラフィックの処理を理解するための最小限の機能を持つシンプルなカーネルドライバーのサンプルを使用します。
本章で解説するサンプルプログラムについても以下のリポジトリからダウンロードすることができます。
URL: https://github.com/kash1064/book06-wfp-samples
もくじ
サンプルプログラムの概要
本章で解説するサンプルプログラムは、WFP の Callout を理解するために作成した比較的シンプルな Callout ドライバーです。
このカーネルドライバーは以下の機能を実装しており、特に Antivirus や EDR において重要な WFP Callout の機能をシンプルに実装しています。
- カーネルモードの WFP エンジンへの Callout 関数の登録と、ALE レイヤーに Callout を呼び出すためのフィルターの追加
- システムに常駐するユーザーモードプログラム側での判定結果によるトラフィックのブロック
- 特定のアプリケーションに関連するトラフィックのブロック対象からの除外
なお、1. の操作のうち Callout 関数の登録についてはカーネルドライバー側で行う必要がありますが、 Callout を呼び出すためのフィルターの追加についてはカーネルモードとユーザーモードのどちらからでも行うことができます。1
そのため、一般的にはフィルターの登録はユーザーモードプログラム側で行われることが多いですが、 今回のサンプルプログラムではカーネルモードで稼働するドライバー側でフィルターの追加も実装しています。
サンプルプログラムを実行する
このサンプルプログラムをビルドすると以下のファイルが生成されるため、まずはこれらのファイルをすべてテスト署名モードを有効化した仮想マシンにコピーします。
- KernelWFPCalloutDriver.sys
- KernelWFPCalloutDriver.cer
- UserModeCalloutSample.exe
続いて、コピーした KernelWFPCalloutDriver.sys を KernelWFPCalloutDriver としてシステムに登録します。
カーネルドライバーのインストールは sc コマンドなどを使用して行うことも可能ですが、 今回は OSR Driver Loader を使用して以下のようにサービスの登録と実行を行います。
KernelWFPCalloutDriver の登録と実行が完了したら、続いてユーザーモードプログラムである UserModeCalloutSample.exe を実行します。
この時、実行時コマンドライン引数として permit もしくは block を使用することで、Callout を使用してフィルタリングしたトラフィックを許可するかブロックするかを指定できます。
UserModeCalloutSample.exe [permit|block]実際にこのサンプルプログラムを実行してみると、UserModeCalloutSample.exe が Callout ドライバーに指示したトラフィックの処理内容を確認することができます。
また、このサンプルプログラム内では名前解決などに必要な一部のプロセスや、Brave ブラウザーによる通信を許可するように定義されているため、 block を指定して UserModeCalloutSample.exe を実行した場合には、Microsoft Edge などのブロック対象のアプリケーションの通信はブロックされるものの、 Brave ブラウザーなどの許可されたアプリケーションの通信は成功することを確認できます。
サンプルプログラムの実装について
ドライバーを登録する
まず、このカーネルドライバーは WDM ドライバーとして実装しており、初期化を行う DriverEntry 関数内では主に以下の操作を行います。
- デバイス名 (
SampleWfpCallout) やスピンロック、セマフォ、リストなどのドライバー内で使用するオブジェクトの初期化 - いくつかの必要な IRP ハンドラの初期化
- デバイスオブジェクトの初期化とシンボリックリンクの登録
- WFP Callout フィルターの登録
今回のサンプルプログラムでは Callout によるトラフィックのブロック判定をシステムに常駐するユーザーモードプログラムに委譲します。
このため、DriverEntry 関数内では IRP_MJ_DEVICE_CONTROL と対応する IRP ハンドラ関数として、
ユーザーモードプログラムとカーネルドライバー間の情報連携を行うための DispatchDeviceControl 関数を登録しています。
また、この中で呼び出される RegisterCalloutAndFilter 関数内では、Callout 関数や WFP フィルターの登録を行います。
RegisterCalloutAndFilter 関数内で行う処理の大部分は、2 章で使用したサンプルプログラムによるフィルターの登録操作とほぼ同じであり、 エンジンへのセッションを開いてトランザクションを開始し、WFP のサブレイヤーやフィルターの登録を行った後にトランザクションをコミットしています。
しかし、このドライバーは Callout フィルターの登録を行うため、2 章で使用したサンプルプログラムとは異なり、 FwpsCalloutRegister3 関数や FwpmCalloutAdd 関数による Callout の登録を行います。
これらの操作の詳細については次の項で解説します。
Callout 関数の登録
サンプルドライバー側で実装されている RegisterCalloutAndFilter 関数では、まず初めに FwpsCalloutRegister3 関数を使用して Callout 関数の登録を行います。2
FWPS_CALLOUT3 callout = {};
callout.calloutKey = CALLOUT_KEY;
callout.classifyFn = (FWPS_CALLOUT_CLASSIFY_FN3)SampleClassify;
callout.notifyFn = (FWPS_CALLOUT_NOTIFY_FN3)SampleNotify;
callout.flowDeleteFn = SampleFlowDelete;
status = FwpsCalloutRegister3(
deviceObject,
&callout,
&gCalloutId
);FwpsCalloutRegister3 関数は、FWPS_CALLOUT3 構造体3の関数ポインターを Callout 関数として登録します。
FWPS_CALLOUT3 構造体には、Callout ドライバーが関数ポインターを登録するために必要な情報が定義されています。
この構造体は以下のように定義されています。
typedef struct FWPS_CALLOUT3_ {
GUID calloutKey;
UINT32 flags;
FWPS_CALLOUT_CLASSIFY_FN3 classifyFn;
FWPS_CALLOUT_NOTIFY_FN3 notifyFn;
FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0 flowDeleteFn;
} FWPS_CALLOUT3;このパラメーターのうち classifyFn と notifyFn は、それぞれ登録する関数へのポインターと対応しており、FWPS_CALLOUT_CLASSIFY_FN3 コールバック関数型4および FWPS_CALLOUT_NOTIFY_FN3 コールバック関数型5へのポインターという形で渡されます。
今回のコードの場合は、SampleClassify 関数という、このドライバーのコード内で定義している関数を classifyFn に、また SampleNotify 関数を notifyFn にセットしています。
FwpsCalloutRegister3 関数が登録する関数ポインターに関する情報が定義された FWPS_CALLOUT3 構造体には、
Callout で処理されるネットワークデータが存在する場合に呼び出される Callout 関数である classifyFn (FWPS_CALLOUT_CLASSIFY_FN3) と、
Callout に関するイベントを通知するために呼び出される notifyFn (FWPS_CALLOUT_NOTIFY_FN3) の 2 種類の関数ポインターが含まれます。
classifyFn に登録する分類処理用の Callout 関数
このサンプルプログラムでは、分類処理用の Callout 関数として SampleClassify 関数を実装しています。
分類処理用の Callout 関数は、Callout によってネットワークデータの処理を行う関数で、 フィルターエンジンからいくつかのデータとともに呼び出しが行われます。6
この分類処理用の Callout 関数の呼び出し時には、FWPS_INCOMING_VALUES0 構造体や FWPS_INCOMING_METADATA_VALUES0 構造体などの情報がパラメーターとして与えられます。
まず、分類処理用の Callout 関数に渡される FWPS_INCOMING_VALUES0 構造体7は、
実データである FWPS_INCOMING_VALUE0 構造体8を含む形で以下のように定義されています。
typedef struct FWPS_INCOMING_VALUE0_ {
FWP_VALUE0 value;
} FWPS_INCOMING_VALUE0;
typedef struct FWPS_INCOMING_VALUES0_ {
UINT16 layerId;
UINT32 valueCount;
FWPS_INCOMING_VALUE0 *incomingValue;
} FWPS_INCOMING_VALUES0;FWPS_INCOMING_VALUES0 構造体の incomingValue は、実際のデータ (FWP_VALUE0) を含む FWPS_INCOMING_VALUE0 構造体の配列へのポインターであり、
valueCount は incomingValue として与えられた配列内の要素の数です。
また、同じく分類処理用の Callout 関数にパラメーターとして渡される FWPS_INCOMING_METADATA_VALUES0 構造体には、
フィルターエンジンから Callout 関数に与えられた各種メタデータが含まれます。
このサンプルプログラムでは FWPM_LAYER_ALE_AUTH_CONNECT_V4 レイヤーにフィルターを登録しており、
Callout 関数が使用するランタイムフィルターレイヤー9は FWPS_LAYER_ALE_AUTH_CONNECT_V4 になります。10
そのため、分類処理用の Callout 関数にパラメーターとして渡される FWPS_INCOMING_VALUES0 構造体 (incomingValue) の各データには、
以下のように FWPS_FIELDS_ALE_AUTH_CONNECT_V4 列挙型11の値をデータフィールド識別子として使用してアクセスすることができます。12
remoteAddressV4 = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32;
remotePort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT].value.uint16;
localPort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT].value.uint16;
ipProtocol = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL].value.uint8;
aleFlags = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_FLAGS].value.uint32;分類処理のために呼び出された SampleClassify 関数では、さらにパラメーターとして受け取った FWPS_INCOMING_METADATA_VALUES0 構造体 (inMetaValues) に含まれるいくつかのメタデータの評価を行います。
例えば以下では、構造体に設定されているメタデータ値を示すメタデータフィールド識別子 (currentMetadataValues) を参照し、
FWPS_METADATA_FIELD_PROCESS_ID のビットの有無からプロセス ID の情報を取得可能であるか確認し、
取得可能な場合はそのプロセス ID の情報を processId として保存しています。13
if ((inMetaValues->currentMetadataValues & FWPS_METADATA_FIELD_PROCESS_ID) != 0)
{
processId = inMetaValues->processId;
}その後、分類処理用の Callout 関数である SampleClassify 関数では、 Callout 関数に通知されたネットワークトラフィックが「ALE フローの再認証」が要求されたトラフィックであるかを評価し、 再認証要求ではない場合には FwpsPendOperation0 関数によるトラフィック処理の保留を行います。
一方で、通知されたネットワークトラフィックが再認証要求されたものである場合には、 ユーザーモードプログラム側の判定結果を元にトラフィックをブロックもしくは許可します。
このような Callout 関数によるユーザーモードプログラムとの連携については、後の項で解説します。
notifyFn に登録するイベント通知処理用の Callout 関数
このサンプルプログラムでは、イベント通知用の Callout 関数として SampleNotify 関数を実装しています。
イベント通知用の Callout 関数は、前項で解説した分類処理用の Callout 関数とは異なり、フィルターの追加や削除などのイベントが発生した場合に呼び出されます。14
このサンプルプログラムではイベント通知用の Callout である SampleNotify 関数は以下の通り非常にシンプルに実装しており、
発生したイベントの種類に関する情報を含む FWPS_CALLOUT_NOTIFY_TYPE 列挙型の値 (notifyType) をパラメーターとして受け取り、
その結果を元にイベントの発生をデバッグメッセージとして出力します。
switch (notifyType)
{
case FWPS_CALLOUT_NOTIFY_ADD_FILTER:
DbgPrintEx(
DPFLTR_IHVDRIVER_ID,
DPFLTR_INFO_LEVEL,
"Sample callout: filter added\n"
);
break;
case FWPS_CALLOUT_NOTIFY_DELETE_FILTER:
DbgPrintEx(
DPFLTR_IHVDRIVER_ID,
DPFLTR_INFO_LEVEL,
"Sample callout: filter deleted\n"
);
break;
default:
break;
}
return STATUS_SUCCESS;Callout ドライバーによりユーザーモードプログラムと連携してトラフィックを制御する
本章で解説するサンプルプログラムではユーザーモードプログラム側の判定結果を利用して Callout ドライバーによるトラフィックのブロックを行います。
この項では、このサンプルプログラムのコア機能である Callout ドライバーとユーザーモードプログラムの連携について詳しく解説します。
Callout ドライバーとユーザーモードプログラムの連携
WFP の Callout ドライバーは、classifyFn に登録された分類処理用の Callout 関数を使用し、 Callout 関数が受け取った各パラメーター内の情報をユーザーモードプログラムに転送し、 ユーザーモードプログラム側の判定結果を元にネットワークトラフィックのブロックや許可を行うことができます。
このような操作は多くの場合非同期的な処理により行われますが、使用するレイヤーにより実現の方法が異なります。
今回のサンプルプログラムでは ALE Connect レイヤーで処理を行うため、まず FwpsPendOperation0 関数を classifyFn として登録されている関数から呼び出して操作を中断し、 その後ユーザーモードプログラム側の処理が可能になったタイミングで FwpsCompleteOperation0 関数を呼び出して分類処理を完了します。15
FwpsPendOperation0 関数16は、ユーザーモードプログラムなどによる別の操作の完了を待つ必要のある分類処理を一時中断するために Callout 関数により呼び出されます。
この関数は以下の通り定義されており、分類処理用の Callout 関数の呼び出し時にパラメーターとして渡される
FWPS_INCOMING_METADATA_VALUES0 構造体に含まれる completionHandle を引数として呼び出され、
この中断した操作と対応するハンドルを completionContext として保存します。
この completionContext に保持されたハンドルは、FwpsCompleteOperation0 関数の呼び出し時に使用します。
NTSTATUS FwpsPendOperation0(
[in] HANDLE completionHandle,
[out] HANDLE *completionContext
);FwpsCompleteOperation0 関数17は、中断されていた操作を再開するために呼び出される関数で、 分類処理の中断時に保存した completionContext をパラメーターとして受け取ります。
void FwpsCompleteOperation0(
[in] HANDLE completionContext,
[in, optional] PNET_BUFFER_LIST netBufferList
);サンプルプログラムにおける分類処理の中断処理
このサンプルプログラムのカーネルドライバー側では、分類処理用の Callout 関数として登録されている SampleClassify 関数内で以下のように分類処理の中断が行われます。
FWPS_INCOMING_VALUES0構造体 (inFixedValues) に含まれるフィルタリング条件フラグ18を参照し、FWP_CONDITION_FLAG_IS_REAUTHORIZEフラグの有無を確認する (新しい接続かどうかを確認する)- 対象が新しい接続の場合、FwpsPendOperation0 関数を呼び出して処理を中断する
- 接続要求に関する情報をユーザーモードプログラムとの連携のために、非ページプールに割り当てたメモリ領域 pending に一時保存する
具体的には、FwpsPendOperation0 の呼び出しから pending への情報の保存については以下のコードで実装しています。
status = FwpsPendOperation0(
(HANDLE)inMetaValues->completionHandle,
&completionContext
);
/* 省略 */
PENDING_CLASSIFY* pending = \
(PENDING_CLASSIFY*)ExAllocatePool2(
POOL_FLAG_NON_PAGED,
sizeof(PENDING_CLASSIFY),
'pfCW'
);
/* 省略 */
RtlZeroMemory(pending, sizeof(*pending));
pending->RequestId = (UINT64)InterlockedIncrement64(&gNextRequestId);
pending->CompletionContext = completionContext;
pending->ProcessId = processId;
pending->RemoteAddressV4 = remoteAddressV4;
pending->RemotePort = remotePort;
pending->LocalPort = localPort;
pending->IpProtocol = ipProtocol;
pending->Queued = TRUE;上記のコードで保存された情報は、安全な操作のためにスピンロックを確保した後、 InsertTailList 関数により各リストにエントリを追加します。
また、KeReleaseSemaphore 関数によりセマフォのカウントのインクリメントも行います。
KIRQL oldIrql;
KeAcquireSpinLock(&gPendingLock, &oldIrql);
InsertTailList(&gOutstandingList, &pending->OutstandingEntry);
InsertTailList(&gPendingQueue, &pending->QueueEntry);
KeReleaseSpinLock(&gPendingLock, oldIrql);
KeReleaseSemaphore(&gPendingSemaphore, IO_NO_INCREMENT, 1, FALSE);これらの情報は、ユーザーモードプログラムとの連携時に使用します。
SampleClassify 関数が初めての接続に対して以上の中断処理を実施したあと、
最後にブロックされたデータをイベントログへの記録や監査を行わずにサイレントに削除する FWPS_CLASSIFY_OUT_FLAG_ABSORB フラグを使用して FWP_ACTION_BLOCK を返します。
classifyOut->actionType = FWP_ACTION_BLOCK;
classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;これで、Callout 関数による接続の中断処理が完了します。
IOCTL の処理
このサンプルプログラムでは、IRP_MJ_DEVICE_CONTROL と対応する IRP ハンドラ関数として割り当てられた DispatchDeviceControl 関数を使用して、ユーザーモードプログラムとの連携を行います。
ここでは、ユーザーモードプログラムから DeviceIoControl 関数により送信された以下のいずれかの独自に定義された IOCTL を処理します。
IOCTL_WFP_GET_NEXT_REQUESTIOCTL_WFP_SUBMIT_DECISION
IOCTL_WFP_GET_NEXT_REQUEST は、ユーザーモードプログラムが Callout 関数により中断された接続要求に関する情報をカーネルドライバー側から取得するために使用します。
一方で、IOCTL_WFP_SUBMIT_DECISION は、ユーザーモードプログラムがカーネルドライバー側から取得したリクエストに対する判定結果を通知するために使用します。
IOCTL_WFP_GET_NEXT_REQUEST の要求は、ユーザーモードプログラム側から以下のコードで呼び出されます。
BOOL ok = DeviceIoControl(
device,
IOCTL_WFP_GET_NEXT_REQUEST,
nullptr,
0,
&request,
sizeof(request),
&bytesReturned,
nullptr);パラメーターとして渡される request は USER_CLASSIFY_REQUEST 構造体のオブジェクトであり、カーネルドライバー側から分類対象のトラフィックに関する情報を受け取るために使用します。
typedef struct _USER_CLASSIFY_REQUEST
{
unsigned long long RequestId;
unsigned long long ProcessId;
unsigned long RemoteAddressV4;
unsigned short RemotePort;
unsigned short Reserved;
} USER_CLASSIFY_REQUEST;カーネルドライバー側では、IOCTL_WFP_GET_NEXT_REQUEST の IOCTL 要求を受け、主に以下の操作を行います。
- ドライバーが停止中でないことを確認し、KeWaitForSingleObject 関数20で gPendingSemaphore がシグナル状態となり保留中のイベントが 1 件以上存在する状態であることを確認する
- 保留中のイベントが存在する場合、キューとして設定している gPendingQueue からイベントを 1 つ取り出し、その情報を
PENDING_CLASSIFY構造体として保存する - 最後に、ユーザーモードプログラムが DeviceIoControl 関数の呼び出し時に指定した出力バッファーに
USER_CLASSIFY_REQUEST構造体と対応する情報を保存する
上記の操作の内、特にキューから取得した情報をユーザーモードプログラムに渡す処理は主に以下のコードで行われます。
KIRQL oldIrql;
PENDING_CLASSIFY* pending = nullptr;
USER_CLASSIFY_REQUEST* request = (USER_CLASSIFY_REQUEST*)irp->AssociatedIrp.SystemBuffer;
KeAcquireSpinLock(&gPendingLock, &oldIrql);
if (!IsListEmpty(&gPendingQueue))
{
PLIST_ENTRY queueEntry = RemoveHeadList(&gPendingQueue);
pending = CONTAINING_RECORD(queueEntry, PENDING_CLASSIFY, QueueEntry);
pending->Queued = FALSE;
}
KeReleaseSpinLock(&gPendingLock, oldIrql);
if (pending == nullptr)
{
status = STATUS_RETRY;
}
else
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Sample callout: dispatch request id=%llu pid=%llu\n",
pending->RequestId,
pending->ProcessId);
request->RequestId = pending->RequestId;
request->ProcessId = pending->ProcessId;
request->RemoteAddressV4 = pending->RemoteAddressV4;
request->RemotePort = pending->RemotePort;
request->Reserved = 0;
information = sizeof(USER_CLASSIFY_REQUEST);
status = STATUS_SUCCESS;
}一方、IOCTL_WFP_SUBMIT_DECISION と対応する IOCTL 要求を受けた場合、カーネルドライバー側では主に以下の操作を行います。
- ユーザーモードプログラム側から連携されたトラフィックに対する判定結果を保存する
- ユーザーモードプログラムから受け取った情報に含まれる RequestId と一致するトラフィックの情報をリストから取得する
- 非ページプール領域にトラフィックの情報やその判定結果を含むエントリを割り当て、
FWP_CONDITION_FLAG_IS_REAUTHORIZEフラグが ALE フローの再認証要求が発生した場合に参照されるリストに追加する - 保留されている対象のトラフィックに関連するパラメーターを受け取り FwpsCompleteOperation0 関数を実行する
これらの操作は、主に以下のように実装されています。
DECISION_ENTRY* decisionEntry = \
(DECISION_ENTRY*)ExAllocatePool2(
POOL_FLAG_NON_PAGED,
sizeof(DECISION_ENTRY), 'dfCW'
);
/* 省略 */
RtlZeroMemory(decisionEntry, sizeof(*decisionEntry));
decisionEntry->ProcessId = pending->ProcessId;
decisionEntry->RemoteAddressV4 = pending->RemoteAddressV4;
decisionEntry->RemotePort = pending->RemotePort;
decisionEntry->LocalPort = pending->LocalPort;
decisionEntry->IpProtocol = pending->IpProtocol;
decisionEntry->Block = (decision->Block != 0) ? TRUE : FALSE;
KeAcquireSpinLock(&gPendingLock, &oldIrql);
InsertTailList(&gDecisionList, &decisionEntry->ListEntry);
KeReleaseSpinLock(&gPendingLock, oldIrql);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Sample callout: submit decision id=%llu block=%lu\n",
decision->RequestId,
decision->Block);
CompleteAndFreePending(pending);
status = STATUS_SUCCESS;
information = sizeof(USER_CLASSIFY_DECISION);これで、ユーザーモードプログラムとの情報連携が行われ、最終的な判定結果を含む情報が、FWP_CONDITION_FLAG_IS_REAUTHORIZE フラグが ALE フローの再認証要求が発生した場合に参照されるリストである gDecisionList に追加されます。
最後に、この判定結果を利用し、Callout 関数にてトラフィックのブロックもしくは許可を行います。
トラフィックの分類処理を行う
前述した通り、分類処理用の Callout 関数として登録されている SampleClassify 関数内では、 初めての接続要求を FwpsPendOperation0 関数により中断し、ユーザーモードプログラム側からの判定結果の受け取りを待機していました。
その後、ユーザーモードプログラム側から IOCTL_WFP_SUBMIT_DECISION と対応する IOCTL 要求を受け取ることで、
カーネルドライバーでは中断したトラフィックに対して FwpsCompleteOperation0 関数を呼び出します。
保留中のトラフィックに対して FwpsCompleteOperation0 関数が呼び出されると、ALE フローの再認証要求がトリガーされます。21
その後、この ALE フローの再認証要求に対して再度分類処理用の Callout 関数が呼び出され、今度は以下のコード部分が実行されます。
if ((aleFlags & FWP_CONDITION_FLAG_IS_REAUTHORIZE) != 0)
{
KIRQL oldIrql;
DECISION_ENTRY* decision = nullptr;
BOOLEAN block = TRUE;
KeAcquireSpinLock(&gPendingLock, &oldIrql);
decision = TakeDecisionLocked(processId, remoteAddressV4, remotePort, localPort, ipProtocol);
KeReleaseSpinLock(&gPendingLock, oldIrql);
if (decision != nullptr)
{
block = decision->Block;
ExFreePoolWithTag(decision, 'dfCW');
}
classifyOut->actionType = block ? FWP_ACTION_BLOCK : FWP_ACTION_PERMIT;
if (block)
{
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Sample callout: reauth decision pid=%llu action=%s\n",
processId,
block ? "BLOCK" : "PERMIT");
return;
}このコードの中では、ユーザーモードプログラム側から受け取った最終的な判定結果を含む情報がリストから取得され、
その結果に基づいて actionType に FWP_ACTION_BLOCK もしくは FWP_ACTION_PERMIT を設定することで、
Callout によるトラフィックのブロックもしくは許可を行っています。
まとめ
本章では、WFP Callout ドライバーを用いて、ALE レイヤーでトラフィックを制御する基本的な実装を解説しました。
本章で解説したユーザーモードプログラムとの連携によるトラフィックの分析などは、 Antivirus や EDR による WFP 活用の基礎となっています。
WFP の Callout は本章で扱った内容以外にも、ストリーム検査や再注入など多くの操作を実装できますが、 本章で解説したサンプルプログラムの実装はこれらを理解するための最小限の土台になると思います。
あとがき
最後までお読みいただき、ありがとうございました。
本書では、Windows Filtering Platform (WFP) の基本的なアーキテクチャから、 ユーザーモードでのフィルター登録、そして Callout ドライバーによるトラフィック制御までを、 段階的に解説してきました。
特に、Antivirus や EDR における実装の観点で重要になる「どのレイヤーで、どの情報を使って、どのように判定するか」という点について、 できるだけ実践的な形で理解できるように構成したつもりです。
一方で、WFP には本書で扱いきれなかったトピックも多く存在します。
例えば、より複雑なストリームインスペクションや再注入 (re-injection) の動作については、 Antivirus および EDR のコンテキストで WFP を語る上で、さらに深掘りする価値があると思います。
本書が、WFP を使ったセキュリティ機能の理解や実装を始める際の足がかりになれば幸いです。
本書のもくじ
-
Windows Kernel Programming, Second Edition P.493 (Pavel Yosifovich 著 / Independently published / 2023 年)
↩ -
FwpsCalloutRegister3 function (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/nf-fwpsk-fwpscalloutregister3
↩ -
↩FWPS_CALLOUT3structure (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/ns-fwpsk-fwps_callout3 -
↩FWPS_CALLOUT_CLASSIFY_FN3callback function (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/nc-fwpsk-fwpscalloutclassify_fn3 -
↩FWPS_CALLOUT_NOTIFY_FN3callback function (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/nc-fwpsk-fwpscalloutnotify_fn3 -
Processing Classify Callouts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/processing-classify-callouts
↩ -
↩FWPS_INCOMING_VALUES0structure (fwpstypes.h) https://learn.microsoft.com/ja-jp/windows/win32/api/fwpstypes/ns-fwpstypes-fwpsincomingvalues0 -
↩FWPS_INCOMING_VALUE0structure (fwpstypes.h) https://learn.microsoft.com/ja-jp/windows/win32/api/fwpstypes/ns-fwpstypes-fwpsincomingvalue0 -
Run-time filtering layer identifiers https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/run-time-filtering-layer-identifiers
↩ -
Filtering layer identifiers https://learn.microsoft.com/ja-jp/windows/win32/fwp/management-filtering-layer-identifiers-#remarks
↩ -
↩FWPS_FIELDS_ALE_AUTH_CONNECT_V4enumeration (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/ne-fwpsk-fwpsfieldsaleauthconnectv4 -
Data field identifiers https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/data-field-identifiers
↩ -
Metadata field identifiers https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/metadata-field-identifiers
↩ -
Processing Notify Callouts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/processing-notify-callouts
↩ -
Processing Classify Callouts Asynchronously https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/processing-classify-callouts-asynchronously
↩ -
FwpsPendOperation0 function (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/nf-fwpsk-fwpspendoperation0
↩ -
FwpsCompleteOperation0 function (fwpsk.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fwpsk/nf-fwpsk-fwpscompleteoperation0
↩ -
Filtering condition flags https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/filtering-condition-flags
↩ -
Types of Callouts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/network/types-of-callouts
↩ -
KeWaitForSingleObject function (wdm.h) https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/wdm/nf-wdm-kewaitforsingleobject
↩ -
Pending Connection Reauthorization https://learn.microsoft.com/ja-jp/windows/win32/fwp/ale-re-authorization#pending-connection-reauthorization
↩