还是没最终下那个决定,也许自己真的没有做大事的魄力,或者根本就做不成大事。做大事者,最忌,犹豫不决,婆婆妈妈,拖泥带水。但是,我有那么多的牵绊,我不得不考虑。其实,我心里已经做了决定,现在只是时间的问题。现在,心里的决定还是没有动摇的,这个我确信不疑的。所以,接下来的时间,我要好好准备。WFP差不多了,要开始到文件系统的minifilter,有时间的话,还要开始网络IO,还有javascript。重视基础。
这篇博客,应该是我最最长的一篇了,因为它差不多花了我一个星期左右的时间。我们来看看WFP中的callout驱动的操作。本来 是想分割成几篇的。但是,不太好分,索性就不分了吧。
Callout Driver Operations
Initializing a Callout Driver
callout驱动在DriverEntry函数中初始化自己,主要的初始化任务包含:
指定卸载函数例程。
基于WDM的Callout驱动。
VOID
Unload(
IN PDRIVER_OBJECT DriverObject
);
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
...
// Specify the callout driver's Unload function
DriverObject->DriverUnload = Unload;
...
}
基于WDF的callout驱动的卸载例程。
VOID
Unload(
IN WDFDRIVER Driver
);
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
WDF_DRIVER_CONFIG config;
WDFDRIVER driver;
...
// Initialize the driver config structure
WDF_DRIVER_CONFIG_INIT(&config, NULL);
// Indicate that this is a non-PNP driver
config.DriverInitFlags = WdfDriverInitNonPnpDriver;
// Specify the callout driver's Unload function
config.EvtDriverUnload = Unload;
// Create a WDFDRIVER object
status =
WdfDriverCreate(
DriverObject,
RegistryPath,
NULL,
&config,
&driver
);
...
return status;
}
创建设备对象。
基于WDM的Callout驱动:
PDEVICE_OBJECT deviceObject;
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
...
// Create a device object
status =
IoCreateDevice(
DriverObject,
0,
NULL,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject
);
...
return status;
}
基于WDF的Callout驱动
因为用过滤引擎注册callout,基于WDF的callout驱动需要得到一个指向WDM设备对象的指针,它和框架的设备对象关联。我们可以使用 WdfDeviceWdmGetDeviceObject 这个函数。
WDFDEVICE wdfDevice;
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
WDFDRIVER driver;
PWDFDEVICE_INIT deviceInit;
PDEVICE_OBJECT deviceObject;
NTSTATUS status;
...
// Allocate a device initialization structure
deviceInit =
WdfControlDeviceInitAllocate(
driver;
&SDDL_DEVOBJ_KERNEL_ONLY
);
// Set the device characteristics
WdfDeviceInitSetCharacteristics(
deviceInit,
FILE_DEVICE_SECURE_OPEN,
FALSE
);
// Create a framework device object
status =
WdfDeviceCreate(
&deviceInit,
WDF_NO_OBJECT_ATTRIBUTES,
&wdfDevice
);
// Check status
if (status == STATUS_SUCCESS) {
// Initialization of the framework device object is complete
WdfControlFinishInitializing(
wdfDevice
);
// Get the associated WDM device object
deviceObject = WdfDeviceWdmGetDeviceObject(wdfDevice);
}
...
return status;
}
使用过滤引擎注册Callouts.
Callout驱动创建设备对象后,它就可以使用过滤引擎注册callout,callout驱动可以在任何时候使用过滤引擎注册callout,甚至过滤引擎不在工作状态。注册callout调用FwpsCalloutRegister0.
// Prototypes for the callout's callout functions
VOID NTAPI
ClassifyFn(
IN const FWPS_INCOMING_VALUES0 *inFixedValues,
IN const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
IN OUT VOID *layerData,
IN const FWPS_FILTER0 *filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT0 *classifyOut
);
NTSTATUS NTAPI
NotifyFn(
IN FWPS_CALLOUT_NOTIFY_TYPE notifyType,
IN const GUID *filterKey,
IN const FWPS_FILTER0 *filter
);
VOID NTAPI
FlowDeleteFn(
IN UINT16 layerId,
IN UINT32 calloutId,
IN UINT64 flowContext
);
// Callout registration structure
const FWPS_CALLOUT0 Callout =
{
{ ... }, // GUID key identifying the callout
0, // Callout-specific flags (none set here)
ClassifyFn,
NotifyFn,
FlowDeleteFn
};
// Variable for the run-time callout identifier
UINT32 CalloutId;
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
PDEVICE_OBJECT deviceObject;
NTSTATUS status;
...
status =
FwpsCalloutRegister0(
deviceObject,
&Callout,
&CalloutId
);
...
return status;
}
如果调用FwpsCalloutRegister0函数成功,函数的最后一个参数包含一个实时标识callout标识符的指针。实时标识符是一个为callout键的GUID.
一个callout驱动可以执行多个callout,如果callout驱动执行多个callout,它必须为每一个callout调用函数
FwpsCalloutRegister0函数。
Processing Notify Callouts
过滤引擎调用callout的notifyFn callout函数通知callout 驱动和callout相关联的事件发生。
Filter Addition
当过滤器为过滤动作指定的callout已经加入到过滤引擎中,过滤引擎调用callout的notifyFn callout函数,notifyType指定为FWPS_CALLOUT_NOTIFY_ADD_FILTER.
Callout驱动在过滤器为过滤的动作指定Callout加入到过滤引擎以后,为过滤引擎注册callout.在这种情况下,过滤引擎不会调用callout的notifyFn callout函数,通知关于任何过滤器的callout.
过滤引擎只当新的为过滤动作指定的过滤器callout被加入到过滤引擎中的时候,调用notifyFn callout函数通知相关的callout.在这种条件下,callout的notifyFn callout函数可能不会为过滤动作指定的callout已经加入到过滤引擎中的每个过滤器获得调用。
如果,在过滤引起已经启动以后,callout驱动注册了一个callout,这个callout必须接收到每一个为过滤动作增加的Callout的每一个已经加入到过滤引擎中的过滤器的信息。callout驱动必须调用合适的管理函数来枚举过滤引擎中的所有过滤器。callout驱动必须通过过滤器类表进行分类,从而找到为过滤动作指定callout的过滤器。
Filter Deletion
当为了过滤动作指定的callout的过滤器从过滤引擎中被删除,过滤引擎调用callout的notifyFn callout函数,在参数notifyType参数中传递FWPS_CALLOUT_NOTIFY_DELETE_FILTER,参数filterKey传递NULL.过滤引擎会为每一个为了过滤动作指定callout的过滤器从过滤引擎中被删除的时候调用notifyFn callout函数。这些过滤器也包含那些callout驱动注册callout前已经被加入到过滤引擎的过滤器。因此,callout可能收到过滤器删除通知,但是收到一些过滤器的加入通知。
如果callout的notifyFn函数没有在其notifyType参数中认出通知的类别。驱动应该省略通知,并返回STATUS_SUCCESS.
callout驱动可以在过滤器被加入到过滤引擎的时候,指定和过滤器相关联的一个上下文结构。这个上下文结构对过滤引擎不透明。callout的classifyFn callout函数可以使用这个上下文结构保存一些信息状态给callout的函数下次给过滤引擎调用时使用。当过滤器从过滤引擎中被删除,callout驱动执行一些必须的清除上下文的动作。
// Context structure to be associated with the filters
typedef struct FILTER_CONTEXT_ {
.
. // Driver-specific content
.
} FILTER_CONTEXT, *PFILTER_CONTEXT;
// Memory pool tag for filter context structures
#define FILTER_CONTEXT_POOL_TAG 'fcpt'
// notifyFn callout function
NTSTATUS NTAPI
NotifyFn(
IN FWPS_CALLOUT_NOTIFY_TYPE notifyType,
IN const GUID *filterKey,
IN const FWPS_FILTER0 *filter
)
{
PFILTER_CONTEXT context;
ASSERT(filter != NULL);
// Switch on the type of notification
switch(notifyType) {
// A filter is being added to the filter engine
case FWPS_CALLOUT_NOTIFY_ADD_FILTER:
// Allocate the filter context structure
context =
(PFILTER_CONTEXT)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(FILTER_CONTEXT),
FILTER_CONTEXT_POOL_TAG
);
// Check the result of the memory allocation
if (context == NULL) {
// Return error
return STATUS_INSUFFICIENT_RESOURCES;
}
// Initialize the filter context structure
...
// Associate the filter context structure with the filter
filter->context = (UINT64)context;
break;
// A filter is being removed from the filter engine
case FWPS_CALLOUT_NOTIFY_DELETE_FILTER:
// Get the filter context structure from the filter
context = (PFILTER_CONTEXT)filter->context;
// Check whether the filter has a context
if (context) {
// Cleanup the filter context structure
...
// Free the memory for the filter context structure
ExFreePoolWithTag(
context,
FILTER_CONTEXT_POOL_TAG
);
}
break;
// Unknown notification
default:
// Do nothing
break;
}
return STATUS_SUCCESS;
}
Processing Classify Callouts
当网络数据将被callout进行处理的时候,过滤引擎就会调相应分类callout的函数。这仅仅只发生在指定过滤动作的callout的过滤条件都满足的时候才发生。如果过滤没有过滤条件,过滤引擎就会一直调用分类的callout函数。过滤引擎会传递不同种类的数据给分类callout的函数。这些数据类型包括固定的数据值,原始网络数据,过滤信息,流上下文。过滤引擎传递的部分数据类型要取决于指定的过滤层和分类函数调用的条件。分类的函数也可以使用这些数据元素的复合类型做过滤决定。
WFP支持异步处理分类的callout函数。
Using a Callout for Deep Inspection
当callout正在执行一些深度检查,它的分类callout函数可以检查任何传递的固定数据,媒体数据,任何包原始数据组成的数据和存储在和过滤相关联的上下文或数据流的数据。
// classifyFn callout function
VOID NTAPI
ClassifyFn(
IN const FWPS_INCOMING_VALUES0 *inFixedValues,
IN const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
IN OUT VOID *layerData,
IN const FWPS_FILTER0 *filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT *classifyOut
)
{
PNET_BUFFER_LIST rawData;
...
// Test for the FWPS_RIGHT_ACTION_WRITE flag to check the rights
// for this callout to return an action. If this flag is not set,
// a callout can still return a BLOCK action in order to VETO a
// PERMIT action that was returned by a previous filter. In this
// example the function just exits if the flag is not set.
if (!(classifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
{
// Return without specifying an action
return;
}
// Get the data fields from inFixedValues
...
// Get any metadata fields from inMetaValues
...
// Get the pointer to the raw data
rawData = (PNET_BUFFER_LIST)layerData;
// Get any filter context data from filter->context
...
// Get any flow context data from flowContext
...
// Inspect the various data sources to determine
// the action to be taken on the data
...
// If the data should be permitted...
if (...) {
// Set the action to permit the data
classifyOut->actionType = FWP_ACTION_PERMIT;
// Check whether the FWPS_RIGHT_ACTION_WRITE flag should be cleared
if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
{
// Clear the FWPS_RIGHT_ACTION_WRITE flag
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
return;
}
...
// If the data should be blocked...
if (...) {
// Set the action to block the data
classifyOut->actionType = FWP_ACTION_BLOCK;
// Clear the FWPS_RIGHT_ACTION_WRITE flag
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
return;
}
...
// If the decision to permit or block should be passed
// to the next filter in the filter engine...
if (...) {
// Set the action to continue with the next filter
classifyOut->actionType = FWP_ACTION_CONTINUE;
return;
}
...
}
分类callout函数可以查看filter->action.type类型从而决定classifyOut参数的actionType应该返回什么值。我们来看一下这个FWPS_ACTION0的结构体。
typedef struct FWPS_ACTION0_ {
FWP_ACTION_TYPE type;
UINT32 calloutId;
} FWPS_ACTION0;
如果callout需要在决定对数据进行许可还是禁止前要在分类的callout函数外执行额外的对数据包进行检查,它必须先暂停包数据直到数据处理完毕。
在一些过滤层上,传递给分类的callout函数的layerData的值为NULL.
Using a Callout for Deep Inspection of Stream Data
// classifyFn callout function
VOID NTAPI
ClassifyFn(
IN const FWPS_INCOMING_VALUES0 *inFixedValues,
IN const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
IN OUT VOID *layerData,
IN const FWPS_FILTER0 *filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT *classifyOut
)
{
FWPS_STREAM_CALLOUT_IO_PACKET0 *ioPacket;
FWPS_STREAM_BUFFER0 *dataStream;
UINT32 bytesRequired;
SIZE_T bytesToPermit;
SIZE_T bytesToBlock;
...
// Get a pointer to the stream callout I/O packet
ioPacket = (FWPS_STREAM_CALLOUT_IO_PACKET0 *)layerData;
// Get the data fields from inFixedValues
...
// Get any metadata fields from inMetaValues
...
// Get the pointer to the data stream
dataStream = ioPacket->dataStream;
// Get any filter context data from filter->context
...
// Get any flow context data from flowContext
...
// Inspect the various data sources to determine
// the action to be taken on the data
...
// If more stream data is required to make a determination...
if (...) {
// Let the filter engine know how many more bytes are needed
ioPacket->streamAction = FWPS_STREAM_ACTION_NEED_MORE_DATA;
ioPacket->countBytesRequired = bytesRequired;
ioPacket->countBytesEnforced = 0;
// Set the action to continue to the next filter
classifyOut->actionType = FWP_ACTION_CONTINUE;
return;
}
...
// If some or all of the data should be permitted...
if (...) {
// No stream-specific action is required
ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
// Let the filter engine know how many of the leading bytes
// in the stream should be permitted
ioPacket->countBytesRequired = 0;
ioPacket->countBytesEnforced = bytesToPermit;
// Set the action to permit the data
classifyOut->actionType = FWP_ACTION_PERMIT;
return