本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正
单层
驱动,只能接受自己进程的IRP多层
驱动,接受所有
进程的IRP
再加一层
而不影响它的上下层,以过滤它们之间的数据,对数据或行为进行安全控制。过滤是通过设备绑定实现的(有多少个文件卷设备就生成多少个文件过滤驱动设备对象一一绑定,这样,发给各个卷设备对象的IRP都会被监控到)。设备栈绑定的形式
:驱动自己生成一个设备(过滤设备
),调用系统提供的绑定API,绑定到自标设备上,并返回
一个在未绑定
之前目标设备所在设备栈的最顶层设备
。这样发往下层的IRP或者发往土层的数据都会被过滤设备截获。绑定的API
:
/// AttachedDevice需要记录在DEVICE_EXTENSION中,以便调用IoCallDriver()继续下发IRP
/// 返回一个在未绑定之前目标设备所在设备栈的最顶层设备,
/// 目的是为了IRP继续往下发,所以需要备份下一层的设备对象,即未绑定之前目标设备所在设备栈的最顶层设备
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice
);
2
个设备指针
typedef struct _DEVICE _OBJECT
{
SHORT Type;
WORD Size;
LONG ReferenceCount;
PORITER OBIECT DriverObject; ///< 生成该设备对象的驱动对象
DEVICE OBTECT NextDevice; ///< 驱动对象生成的所有设备对象通过NextDevice指针组织在1个链表里
PDEVICE OBTECT AttachedDevice; ///< 设备栈,绑定在设备对象上
PIRP CurrentIrp;
PIO TIMER Timer;
LONG Flags;
LONG Characteristics;
PUPB Vpb;
VOID DeviceExtension;
LONG DeviceType;
CHAR StackSize;
BYTE Queue [40];
LONG AlignmentRequirement:
DEVICE QUEUE DeviceQueue;
KDPC Dpc;
LONG ActiveThreadCount;
VOID SecurityDescriptor;
EVENT DeviceLock;
WORD SectorSize;
WORD Sparel;
PDEVOBT _EXTENSION DeviceObjectExtension;
VOID Reserved;
}DEVICE OBJECT, *PDEVICE_OBJECT:
控制设备对象
过滤设备对象
只有一个
。控制设备对象
),也用来接受其他进程IRP(发给过滤设备对象
)/// 判断设备对象是否是过滤设备对象:
/// 1.过滤设备对象不为空
/// 2.过滤设备对象是由驱动对象创建
/// 3.如果是过滤设备对象,DeviceExtension保存着下一层的设备对象
#define IS_MY_DEVICE OBJECT(_devObj)\
(((_devObj) != NULL) &&\
((_devObj)->DriverObject == gSfilterDriverObject)&&\
((devObj)->DeviceExtension != NULL))
/// 判断设备对象是否是控制设备对象:
/// 1.gSfilterControlDeviceObject指针保存的是在DriverEntry创建的控制设备对象的地址
/// 2.控制设备对象是由驱动对象创建
/// 3.如果是控制设备对象,DeviceExtension为空
#define IS_MY_CONTROL_DEVICE_OBJECT(_devObj)\
(((_devObj) == gSfilterControlDeviceObject) ?\
(ASSERT(((_devObj)->DriverObject == gSfilterDriverObject) && \
(_devObj)->DeviceExtension == NULL)),TRUE):FALSE)
/// xxx表示任意的过滤分发函数
NTSTATUS FilterXXX(PDEVICE_OBJECT DeviceObject,PIRP plrp)
{
NTSTATUS Status = STATUS_suCCESs;
ULONG ullnfomation = O;
IO_STACK_LOCATION* lpIrpStack = IoGetcurrentIrpStackLocation(pIrp);
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject))
{
/// 如果需要与R3交互,这里必须返回成功
pIrp->loStatus.Status = Status,
plrp->loStatus.Information = ullnfomation;
IoCompleteRequest(lplrp,IO_NO_INCREMENTY;
}
else if (IS_MY_DEVIGE_OBJECT(DeyiceObject))
{
/// 这里才是我们要过滤的操作,直接放行
IoSkipCurrentIrpStackLocation(plrp); //忽略当前的IRP
Status = IoCallDriver(((PSfilter DEVICE_EXTENSION)->DeviceExtension)DeviceObject->NLExtlHeader.AttachedToDeviceObject,plrp); //发给下一层的设备对象,下发后关心Irp成功或者失败
}
else
{
/// 非法参数
plrp->loStatus.Status = Status = STATUS_INVALID_PARAMETER;
plrp->loStatus.Information = 0
IoCompleteRequest(plrp,IO_NO_INCREMENT);
}
return Status;
}
不要钻牛角尖
,把握整体流程即可,不求把每行代码每个变量都扣的清楚。(上万行代码)
DriverEntry()
SfPassThrough()
通用的分发函数IoRegisterFsRegistrationChange();
注册回调函数,生成过滤对象,绑定到文件系统设备对象
进行监控
SfFsNotification()
SfAttachToFileSystemDevice()
SfAttachDeviceToDeviceStack()
SfFsControl
:lRP_WAFLE_SYSTEM_CONTROL处理发给文件系统设备对象mount IRP请求,从mount IRP中拿到卷设备对象
并生成一个过滤设备对象与之绑定
SfFsControlMountVolumeComplete();
SfAttachToMountedDevice()
SfFsControlCompletion()
SfCreate()
全盘监控
NLAllocateNameControl()
NLGetFullPathName()
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This is the initialization routine for the SFILTER file system filter
driver. This routine creates the device object that represents this
driver in the system and registers it for watching all file systems that
register or unregister themselves as active file systems.
Arguments:
DriverObject - Pointer to driver object created by the system.
Return Value:
The function value is the final status from the initialization operation.
--*/
{
PFAST_IO_DISPATCH fastIoDispatch;
UNICODE_STRING nameString;
UNICODE_STRING dosName;
NTSTATUS status;
ULONG i;
/// xp及以上系统
#if WINVER >= 0x0501
//
// Try to load the dynamic functions that may be available for our use.
//
SfLoadDynamicFunctions();
//
// Now get the current OS version that we will use to determine what logic
// paths to take when this driver is built to run on various OS version.
//
SfGetCurrentVersion();
#endif
//
// Get Registry values
//
SfReadDriverParameters(RegistryPath);
//
// Save our Driver Object, set our UNLOAD routine
//
/// 用来判断是不是过滤设备对象
gSFilterDriverObject = DriverObject;
#if DBG && WINVER >= 0x0501
//
// MULTIVERSION NOTE:
//
// We can only support unload for testing environments if we can enumerate
// the outstanding device objects that our driver has.
//
//
// Unload is useful for development purposes. It is not recommended for
// production versions
//
if (NULL != gSfDynamicFunctions.EnumerateDeviceObjectList) {
gSFilterDriverObject->DriverUnload = DriverUnload;
}
#endif
//
// Setup other global variables
//
ExInitializeFastMutex(&gSfilterAttachLock);
//
// Initialize the lookaside list for name buffering. This is used in
// several places to avoid having a large name buffer on the stack. It is
// also needed by the name lookup routines (NLxxx).
//
/// 避免内存碎片,SFilter监控大量的文件的操作,需要把这些操作保存起来,涉及到固定大小内存的分配,用LookasideList数据结构比较合适
ExInitializePagedLookasideList(&gSfNameBufferLookasideList,
NULL,
NULL,
0,
SFILTER_LOOKASIDE_SIZE,
SFLT_POOL_TAG_NAME_BUFFER,
0);
//
// Create the Control Device Object (CDO). This object represents this
// driver. Note that it does not have a device extension.
//
RtlInitUnicodeString(&nameString, L"\\FileSystem\\Filters\\SFilterDrv");
status = IoCreateDevice(DriverObject,
0, //has no device extension
&nameString,
FILE_DEVICE_DISK_FILE_SYSTEM, ///< 不再是FILE_DEVICE_UNKNOWN了
FILE_DEVICE_SECURE_OPEN,
FALSE,
&gSFilterControlDeviceObject); ///< 保存在全局指针,用于后续区分控制设备对象
if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
//
// This must be a version of the OS that doesn't have the Filters
// path in its namespace. This was added in Windows XP.
//
// We will try just putting our control device object in the
// \FileSystem portion of the object name space.
//
/// 如果创建设备对象失败,则换一种创建方式
RtlInitUnicodeString(&nameString, L"\\FileSystem\\SFilterDrv");
status = IoCreateDevice(DriverObject,
0, //has no device extension
&nameString,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&gSFilterControlDeviceObject);
if (!NT_SUCCESS(status)) {
KdPrint(("SFilter!DriverEntry: Error creating control device object \"%wZ\", status=%08x\n",
&nameString,
status));
return status;
}
}
else if (!NT_SUCCESS(status)) {
KdPrint(("SFilter!DriverEntry: Error creating control device object \"%wZ\", status=%08x\n",
&nameString, status));
return status;
}
RtlInitUnicodeString(&dosName, L"\\DosDevices\\SFilterDrv");
status = IoCreateSymbolicLink(&dosName, &nameString);
if (NT_SUCCESS(status) == FALSE)
{
IoDeleteDevice(gSFilterControlDeviceObject);
ExDeletePagedLookasideList(&gSfNameBufferLookasideList);
return STATUS_UNSUCCESSFUL;
}
/// 控制设备对象通信方式设置成buffer_io
/// 过滤设备对象则是要和被绑定的设备对象指定的通信方式一致
gSFilterControlDeviceObject->Flags |= DO_BUFFERED_IO;
//
// Initialize the driver object with this device driver's entry points.
//
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
DriverObject->MajorFunction[i] = SfPassThrough; ///< 设置成通用的Irp处理函数,对Irp直接放行
}
//
// We will use SfCreate for all the create operations
//
/// 生成过滤设备对象绑定到目标设备对象之后,凡是发送给目标设备对象的IRP都会被分发函数拿到
DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate; ///< 如果是自己进程下发的IRP(发给控制设备对象),在这里都会替换掉初始化的通用分发函数SfPassThrough,不用担心SfPassThrough处理控制设备对象的IRP的情况
//DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = SfCreate;
//DriverObject->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = SfCreate;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl; ///< Mount是RP_MJ_FILE_SYSTEM_CONTROL的一个子功能号,通过这个函数监控Mout IRP,生成过滤设备对象与移动(U盘)卷设备绑定
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = SfCleanupClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;
//
// Allocate fast I/O data structure and fill it in.
//
// NOTE: The following FastIo Routines are not supported:
// AcquireFileForNtCreateSection
// ReleaseFileForNtCreateSection
// AcquireForModWrite
// ReleaseForModWrite
// AcquireForCcFlush
// ReleaseForCcFlush
//
// For historical reasons these FastIO's have never been sent to filters
// by the NT I/O system. Instead, they are sent directly to the base
// file system. On Windows XP and later OS releases, you can use the new
// system routine "FsRtlRegisterFileSystemFilterCallbacks" if you need to
// intercept these callbacks (see below).
//
fastIoDispatch = ExAllocatePoolWithTag(NonPagedPool,
sizeof(FAST_IO_DISPATCH),
SFLT_POOL_TAG_FASTIO);
if (!fastIoDispatch) {
IoDeleteDevice(gSFilterControlDeviceObject);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(fastIoDispatch, sizeof(FAST_IO_DISPATCH));
/// 真正的IRP是从文件系统的磁盘上进行IO操作的,
/// 而fastIo不走IRP,直接在缓存中读写数据,目的是为了提高效率,比如读的时候提前把一些数据读出,写的时候提前把一些数据写入,
/// 最后一下子把缓存中的数据一次性写入磁盘,减少IO次数,提高系统性能
/// 在这里没有对fastIo做特殊处理,而是直接拒绝
fastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
fastIoDispatch->FastIoCheckIfPossible = SfFastIoCheckIfPossible;
fastIoDispatch->FastIoRead = SfFastIoRead; ///< 直接返回FALSE,相当于禁用掉了,性能会有一定的影响,性能损失在10%以下(《寒江独钓》中统计的数据),对于客户端来说,还是可以接受的
fastIoDispatch->FastIoWrite = SfFastIoWrite;
fastIoDispatch->FastIoQueryBasicInfo = SfFastIoQueryBasicInfo;
fastIoDispatch->FastIoQueryStandardInfo = SfFastIoQueryStandardInfo;
fastIoDispatch->FastIoLock = SfFastIoLock;
fastIoDispatch->FastIoUnlockSingle = SfFastIoUnlockSingle;
fastIoDispatch->FastIoUnlockAll = SfFastIoUnlockAll;
fastIoDispatch->FastIoUnlockAllByKey = SfFastIoUnlockAllByKey;
fastIoDispatch->FastIoDeviceControl = SfFastIoDeviceControl;
fastIoDispatch->FastIoDetachDevice = SfFastIoDetachDevice;
fastIoDispatch->FastIoQueryNetworkOpenInfo = SfFastIoQueryNetworkOpenInfo;
fastIoDispatch->MdlRead = SfFastIoMdlRead;
fastIoDispatch->MdlReadComplete = SfFastIoMdlReadComplete;
fastIoDispatch->PrepareMdlWrite = SfFastIoPrepareMdlWrite;
fastIoDispatch->MdlWriteComplete = SfFastIoMdlWriteComplete;
fastIoDispatch->FastIoReadCompressed = SfFastIoReadCompressed;
fastIoDispatch->FastIoWriteCompressed = SfFastIoWriteCompressed;
fastIoDispatch->MdlReadCompleteCompressed = SfFastIoMdlReadCompleteCompressed;
fastIoDispatch->MdlWriteCompleteCompressed = SfFastIoMdlWriteCompleteCompressed;
fastIoDispatch->FastIoQueryOpen = SfFastIoQueryOpen;
/// 将设置好的一组fastIo回调函数注册到DriverObject->FastIoDispatch
DriverObject->FastIoDispatch = fastIoDispatch;
//
// VERSION NOTE:
//
// There are 6 FastIO routines for which file system filters are bypassed as
// the requests are passed directly to the base file system. These 6 routines
// are AcquireFileForNtCreateSection, ReleaseFileForNtCreateSection,
// AcquireForModWrite, ReleaseForModWrite, AcquireForCcFlush, and
// ReleaseForCcFlush.
//
// In Windows XP and later, the FsFilter callbacks were introduced to allow
// filters to safely hook these operations. See the IFS Kit documentation for
// more details on how these new interfaces work.
//
// MULTIVERSION NOTE:
//
// If built for Windows XP or later, this driver is built to run on
// multiple versions. When this is the case, we will test
// for the presence of FsFilter callbacks registration API. If we have it,
// then we will register for those callbacks, otherwise, we will not.
//
#if WINVER >= 0x0501
{
FS_FILTER_CALLBACKS fsFilterCallbacks;
if (NULL != gSfDynamicFunctions.RegisterFileSystemFilterCallbacks) {
//
// Setup the callbacks for the operations we receive through
// the FsFilter interface.
//
// NOTE: You only need to register for those routines you really
// need to handle. SFilter is registering for all routines
// simply to give an example of how it is done.
//
fsFilterCallbacks.SizeOfFsFilterCallbacks = sizeof(FS_FILTER_CALLBACKS);
fsFilterCallbacks.PreAcquireForSectionSynchronization = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostAcquireForSectionSynchronization = SfPostFsFilterPassThrough;
fsFilterCallbacks.PreReleaseForSectionSynchronization = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostReleaseForSectionSynchronization = SfPostFsFilterPassThrough;
fsFilterCallbacks.PreAcquireForCcFlush = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostAcquireForCcFlush = SfPostFsFilterPassThrough;
fsFilterCallbacks.PreReleaseForCcFlush = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostReleaseForCcFlush = SfPostFsFilterPassThrough;
fsFilterCallbacks.PreAcquireForModifiedPageWriter = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostAcquireForModifiedPageWriter = SfPostFsFilterPassThrough;
fsFilterCallbacks.PreReleaseForModifiedPageWriter = SfPreFsFilterPassThrough;
fsFilterCallbacks.PostReleaseForModifiedPageWriter = SfPostFsFilterPassThrough;
status = (gSfDynamicFunctions.RegisterFileSystemFilterCallbacks)(DriverObject,
&fsFilterCallbacks);
if (!NT_SUCCESS(status)) {
DriverObject->FastIoDispatch = NULL;
ExFreePoolWithTag(fastIoDispatch, SFLT_POOL_TAG_FASTIO);
IoDeleteDevice(gSFilterControlDeviceObject);
return status;
}
}
}
#endif
//
// The registered callback routine "SfFsNotification" will be called
// whenever a new file systems is loaded or when any file system is
// unloaded.
//
// VERSION NOTE:
//
// On Windows XP and later this will also enumerate all existing file
// systems (except the RAW file systems). On Windows 2000 this does not
// enumerate the file systems that were loaded before this filter was
// loaded.
//
/// 注册SfFsNotification回调函数,生成过滤设备对象,绑定在文件系统设备对象上,监控移动设备,同时也会遍历固定的卷设备对象,同样生成过滤设备对象进行绑定
/// 绑定文件系统设备,是卷设备绑定的前提,怎么理解呢?
/// 比如,U盘插入电脑之后,系统的动作:
/// 首先生成一个文件系统设备对象(FAT32或ntfs),然后系统向文件系统设备对象发送一个IRP请求(Mount),在Mount IRP(IRP_MJ_SYSTEM_CONTROL)请求里面才会为U盘生成一个卷设备对象,
/// 所以必须先生成一个过滤设备对象绑定在文件系统设备对象上,才能够监控Mount IRP(IRP_MJ_SYSTEM_CONTROL)从而拿到为U盘生成的动态卷设备对象,后续SfFsControl才能生成一个过滤设备对象绑定在U盘的设备对象上
status = IoRegisterFsRegistrationChange(DriverObject, SfFsNotification);
if (!NT_SUCCESS(status)) {
KdPrint(("SFilter!DriverEntry: Error registering FS change notification, status=%08x\n",
status));
DriverObject->FastIoDispatch = NULL;
ExFreePoolWithTag(fastIoDispatch, SFLT_POOL_TAG_FASTIO);
IoDeleteDevice(gSFilterControlDeviceObject);
return status;
}
//
// Attempt to attach to the appropriate RAW file system device objects
// since they are not enumerated by IoRegisterFsRegistrationChange.
//
{
PDEVICE_OBJECT rawDeviceObject;
PFILE_OBJECT fileObject;
//
// Attach to RawDisk device
//
RtlInitUnicodeString(&nameString, L"\\Device\\RawDisk");
status = IoGetDeviceObjectPointer(
&nameString,
FILE_READ_ATTRIBUTES,
&fileObject,
&rawDeviceObject);
if (NT_SUCCESS(status)) {
SfFsNotification(rawDeviceObject, TRUE);
ObDereferenceObject(fileObject);
}
//
// Attach to the RawCdRom device
//
RtlInitUnicodeString(&nameString, L"\\Device\\RawCdRom");
status = IoGetDeviceObjectPointer(
&nameString,
FILE_READ_ATTRIBUTES,
&fileObject,
&rawDeviceObject);
if (NT_SUCCESS(status)) {
SfFsNotification(rawDeviceObject, TRUE);
ObDereferenceObject(fileObject);
}
}
//
// Clear the initializing flag on the control device object since we
// have now successfully initialized everything.
//
ClearFlag(gSFilterControlDeviceObject->Flags, DO_DEVICE_INITIALIZING);
DbgPrint("Sfilter installed\n");
return STATUS_SUCCESS;
}
NTSTATUS
SfPassThrough(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is the main dispatch routine for the general purpose file
system driver. It simply passes requests onto the next driver in the
stack, which is presumably a disk file system.
Arguments:
DeviceObject - Pointer to the device object for this driver.
IRP - Pointer to the request packet representing the I/O request.
Return Value:
The function value is the status of the operation.
Note:
A note to file system filter implementers:
This routine actually "passes" through the request by taking this
driver out of the IRP stack. If the driver would like to pass the
I/O request through, but then also see the result, then rather than
taking itself out of the loop it could keep itself in by copying the
caller's parameters to the next stack location and then set its own
completion routine.
Hence, instead of calling:
IoSkipCurrentIrpStackLocation( Irp );
You could instead call:
IoCopyCurrentIrpStackLocationToNext( Irp );
IoSetCompletionRoutine( Irp, NULL, NULL, FALSE, FALSE, FALSE );
This example actually NULLs out the caller's I/O completion routine, but
this driver could set its own completion routine so that it would be
notified when the request was completed (see SfCreate for an example of
this).
--*/
{
PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
//
// Sfilter doesn't allow handles to its control device object to be
// created, therefore, no other operation should be able to come through.
//
ASSERT(!IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)); ///< 不能是控制设备对象
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject)); ///< 必须是过滤设备对象
//
// File systems should NEVER receive a power IRP
//
ASSERT(pIrp->MajorFunction != IRP_MJ_POWER);
//
// Get this driver out of the driver stack and get to the next driver as
// quickly as possible.
//
IoSkipCurrentIrpStackLocation(Irp); ///< 忽略当前的IRP
//
// Call the appropriate file system driver with the request.
//
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
Irp);
}
VOID
SfFsNotification(
IN PDEVICE_OBJECT DeviceObject, ///<文件系统设备对象
IN BOOLEAN FsActive ///< 如果为ture表示mount,如何为false表示unmount
)
/*++
Routine Description:
This routine is invoked whenever a file system has either registered or
unregistered itself as an active file system.
For the former case, this routine creates a device object and attaches it
to the specified file system's device object. This allows this driver
to filter all requests to that file system. Specifically we are looking
for MOUNT requests so we can attach to newly mounted volumes.
For the latter case, this file system's device object is located,
detached, and deleted. This removes this file system as a filter for
the specified file system.
Arguments:
DeviceObject - Pointer to the file system's device object.
FsActive - Boolean indicating whether the file system has registered
(TRUE) or unregistered (FALSE) itself as an active file system.
Return Value:
None.
--*/
{
PNAME_CONTROL devName;
PAGED_CODE();
//
// Display the names of all the file system we are notified of
//
devName = NLGetAndAllocateObjectName(DeviceObject,
&gSfNameBufferLookasideList);
if (devName == NULL) {
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsNotification: Not attaching to %p, insufficient resources.\n",
DeviceObject));
return;
}
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsNotification: %s %p \"%wZ\" (%s)\n",
(FsActive) ? "Activating file system " : "Deactivating file system",
DeviceObject,
&devName->Name,
GET_DEVICE_TYPE_NAME(DeviceObject->DeviceType)));
//
// Handle attaching/detaching from the given file system.
//
if (FsActive) {
SfAttachToFileSystemDevice(DeviceObject, devName);
}
else {
SfDetachFromFileSystemDevice(DeviceObject);
}
//
// We're done with name (SfAttachToFileSystemDevice copies the name to
// the device extension) so free it.
//
NLFreeNameControl(devName, &gSfNameBufferLookasideList);
}
NTSTATUS
SfAttachToFileSystemDevice(
IN PDEVICE_OBJECT DeviceObject,
IN PNAME_CONTROL DeviceName
)
/*++
Routine Description:
This will attach to the given file system device object. We attach to
these devices so we will know when new volumes are mounted.
Arguments:
DeviceObject - The File System CDO to attach to
Name - An already initialized name control used to retrieve names.
This is passed in to reduce the number of strings buffers on
the stack.
Return Value:
Status of the operation
--*/
{
PDEVICE_OBJECT newDeviceObject;
PSFILTER_DEVICE_EXTENSION devExt;
UNICODE_STRING fsrecName;
NTSTATUS status;
PNAME_CONTROL fsName;
PAGED_CODE();
//
// See if this is a file system type we care about. If not, return.
//
if (!IS_DESIRED_DEVICE_TYPE(DeviceObject->DeviceType)) {
return STATUS_SUCCESS;
}
//
// See if we should attach to the standard file system recognizer device
// or not
//
if (!FlagOn(SfDebug, SFDEBUG_ATTACH_TO_FSRECOGNIZER)) {
//
// See if this is one of the standard Microsoft file system recognizer
// devices (see if this device is in the FS_REC driver). If so skip
// it. We no longer attach to file system recognizer devices, we
// simply wait for the real file system driver to load.
//
RtlInitUnicodeString(&fsrecName, L"\\FileSystem\\Fs_Rec");
fsName = NLGetAndAllocateObjectName(DeviceObject->DriverObject,
&gSfNameBufferLookasideList);
if (fsName == NULL) {
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfAttachToFileSystemDevice: Error retrieving name, may attach to FS recognizer, status=%08x\n",
STATUS_INSUFFICIENT_RESOURCES));
}
else if (RtlCompareUnicodeString(&fsName->Name,
&fsrecName, TRUE) == 0) {
//
// If it is a recognizer, don't attach
//
NLFreeNameControl(fsName, &gSfNameBufferLookasideList);
return STATUS_SUCCESS;
}
NLFreeNameControl(fsName, &gSfNameBufferLookasideList);
}
//
// We want to attach to this file system. Create a new device object we
// can attach with.
//
/// 生成一个过滤设备对象
status = IoCreateDevice(gSFilterDriverObject,
sizeof(SFILTER_DEVICE_EXTENSION),
NULL,
DeviceObject->DeviceType,
0,
FALSE,
&newDeviceObject);
if (!NT_SUCCESS(status)) {
return status;
}
//
// Propagate flags from Device Object we are trying to attach to.
// Note that we do this before the actual attachment to make sure
// the flags are properly set once we are attached (since an IRP
// can come in immediately after attachment but before the flags would
// be set).
//
/// 过滤设备对象的通信方式与目标设备对象保持一致
if (FlagOn(DeviceObject->Flags, DO_BUFFERED_IO)) {
SetFlag(newDeviceObject->Flags, DO_BUFFERED_IO);
}
if (FlagOn(DeviceObject->Flags, DO_DIRECT_IO)) {
SetFlag(newDeviceObject->Flags, DO_DIRECT_IO);
}
if (FlagOn(DeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN)) {
SetFlag(newDeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN);
}
//
// Initialize the device extension.
//
devExt = newDeviceObject->DeviceExtension;
devExt->Flags = 0;
NLInitDeviceExtensionHeader(&devExt->NLExtHeader,
newDeviceObject,
NULL);
//
// Set the name. We allocate from non-paged pool so this memory is always
// available for debugging (never gets paged out).
//
status = NLAllocateAndCopyUnicodeString(&devExt->NLExtHeader.DeviceName,
&DeviceName->Name,
SFLT_POOL_TAG_DEVNAME);
if (!NT_SUCCESS(status)) {
goto ErrorCleanupDevice;
}
//
// Do the attachment.
//
/// 把过滤设备对象绑定到文件系统设备对象上,拿到mount操作,才能拿到新生成卷设备对象
status = SfAttachDeviceToDeviceStack(newDeviceObject,
DeviceObject,
&devExt->NLExtHeader.AttachedToDeviceObject);
if (!NT_SUCCESS(status)) {
goto ErrorCleanupDevice;
}
//
// Mark we are done initializing
//
/// 在DriverEntry创建的设备对象,初始化标志不用管,系统会帮忙清理掉
/// 在在DriverEntry之外创建的设备对象,初始化标志必须由程序员自己清理掉
ClearFlag(newDeviceObject->Flags, DO_DEVICE_INITIALIZING);
//
// Display who we have attached to
//
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfAttachToFileSystemDevice: Attaching %p to file system %p \"%wZ\" (%s)\n",
newDeviceObject,
DeviceObject,
&devExt->NLExtHeader.DeviceName,
GET_DEVICE_TYPE_NAME(newDeviceObject->DeviceType)));
//
// VERSION NOTE:
//
// In Windows XP, the IO Manager provided APIs to safely enumerate all the
// device objects for a given driver. This allows filters to attach to
// all mounted volumes for a given file system at some time after the
// volume has been mounted. There is no support for this functionality
// in Windows 2000.
//
// MULTIVERSION NOTE:
//
// If built for Windows XP or later, this driver is built to run on
// multiple versions. When this is the case, we will test
// for the presence of the new IO Manager routines that allow for volume
// enumeration. If they are not present, we will not enumerate the volumes
// when we attach to a new file system.
//
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT(NULL != gSfDynamicFunctions.EnumerateDeviceObjectList &&
NULL != gSfDynamicFunctions.GetDiskDeviceObject &&
NULL != gSfDynamicFunctions.GetDeviceAttachmentBaseRef &&
NULL != gSfDynamicFunctions.GetLowerDeviceObject);
//
// Enumerate all the mounted devices that currently
// exist for this file system and attach to them.
//
status = SfEnumerateFileSystemVolumes(DeviceObject);
if (!NT_SUCCESS(status)) {
IoDetachDevice(devExt->NLExtHeader.AttachedToDeviceObject);
goto ErrorCleanupDevice;
}
}
#endif
return STATUS_SUCCESS;
/
// Cleanup error handling
/
ErrorCleanupDevice:
SfCleanupMountedDevice(newDeviceObject);
IoDeleteDevice(newDeviceObject);
return status;
}
NTSTATUS
SfAttachDeviceToDeviceStack(
IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice,
IN OUT PDEVICE_OBJECT* AttachedToDeviceObject
)
/*++
Routine Description:
This routine attaches the SourceDevice to the TargetDevice's stack and
returns the device object SourceDevice was directly attached to in
AttachedToDeviceObject. Note that the SourceDevice does not necessarily
get attached directly to TargetDevice. The SourceDevice will get attached
to the top of the stack of which TargetDevice is a member.
VERSION NOTE:
In Windows XP, a new API was introduced to close a rare timing window that
can cause IOs to start being sent to a device before its
AttachedToDeviceObject is set in its device extension. This is possible
if a filter is attaching to a device stack while the system is actively
processing IOs. The new API closes this timing window by setting the
device extension field that holds the AttachedToDeviceObject while holding
the IO Manager's lock that protects the device stack.
A sufficient work around for earlier versions of the OS is to set the
AttachedToDeviceObject to the device object that the SourceDevice is most
likely to attach to. While it is possible that another filter will attach
in between the SourceDevice and TargetDevice, this will prevent the
system from bug checking if the SourceDevice receives IOs before the
AttachedToDeviceObject is correctly set.
For a driver built in the Windows 2000 build environment, we will always
use the work-around code to attach. For a driver that is built in the
Windows XP or later build environments (therefore you are building a
multiversion driver), we will determine which method of attachment to use
based on which APIs are available.
Arguments:
SourceDevice - The device object to be attached to the stack.
TargetDevice - The device that we currently think is the top of the stack
to which SourceDevice should be attached.
AttachedToDeviceObject - This is set to the device object to which
SourceDevice is attached if the attach is successful.
Return Value:
Return STATUS_SUCCESS if the device is successfully attached. If
TargetDevice represents a stack to which devices can no longer be attached,
STATUS_NO_SUCH_DEVICE is returned.
--*/
{
PAGED_CODE();
/// 如果是XP及以上系统
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT(NULL != gSfDynamicFunctions.AttachDeviceToDeviceStackSafe);
return (gSfDynamicFunctions.AttachDeviceToDeviceStackSafe)(SourceDevice,
TargetDevice,
AttachedToDeviceObject); ///< 把原设备绑定到目标设备上
}
else {
ASSERT(NULL == gSfDynamicFunctions.AttachDeviceToDeviceStackSafe);
#endif
* AttachedToDeviceObject = TargetDevice;
*AttachedToDeviceObject = IoAttachDeviceToDeviceStack(SourceDevice,
TargetDevice); ///< 如果不是则使用IoAttachDeviceToDeviceStack来绑定
if (*AttachedToDeviceObject == NULL) {
return STATUS_NO_SUCH_DEVICE;
}
return STATUS_SUCCESS;
#if WINVER >= 0x0501
}
#endif
}
NTSTATUS
SfFsControl(
IN PDEVICE_OBJECT DeviceObject, ///< 文件系统设备对象
IN PIRP Irp ///< Mount IRP
)
/*++
Routine Description:
This routine is invoked whenever an I/O Request Packet (IRP) w/a major
function code of IRP_MJ_FILE_SYSTEM_CONTROL is encountered. For most
IRPs of this type, the packet is simply passed through. However, for
some requests, special processing is required.
Arguments:
DeviceObject - Pointer to the device object for this driver.
Irp - Pointer to the request packet representing the I/O request.
Return Value:
The function value is the status of the operation.
--*/
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PAGED_CODE();
//
// Sfilter doesn't allow handles to its control device object to be
// created, therefore, no other operation should be able to come through.
//
ASSERT(!IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject));
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
//
// Process the minor function code.
//
switch (irpSp->MinorFunction) {
case IRP_MN_MOUNT_VOLUME:
return SfFsControlMountVolume(DeviceObject, Irp); ///< 拿到卷设备对象进行绑定
case IRP_MN_LOAD_FILE_SYSTEM:
return SfFsControlLoadFileSystem(DeviceObject, Irp);
case IRP_MN_USER_FS_REQUEST:
{
switch (irpSp->Parameters.FileSystemControl.FsControlCode) {
case FSCTL_DISMOUNT_VOLUME:
{
PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControl: Dismounting volume %p \"%wZ\"\n",
devExt->NLExtHeader.AttachedToDeviceObject,
&devExt->NLExtHeader.DeviceName));
break;
}
}
break;
}
}
//
// Pass all other file system control requests through.
//
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
Irp);
}
NTSTATUS
SfFsControlMountVolumeComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PDEVICE_OBJECT NewDeviceObject
)
/*++
Routine Description:
This does the post-Mount work and must be done at PASSIVE_LEVEL.
Arguments:
DeviceObject - The device object for this operation,
Irp - The IRP for this operation that we will complete once we are finished
with it.
Return Value:
Returns the status of the mount operation.
--*/
{
PVPB vpb;
PSFILTER_DEVICE_EXTENSION newDevExt;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT attachedDeviceObject;
NTSTATUS status;
BOOLEAN justAttached = FALSE;
PAGED_CODE();
newDevExt = NewDeviceObject->DeviceExtension;
irpSp = IoGetCurrentIrpStackLocation(Irp);
//
// Get the correct VPB from the real device object saved in our
// device extension. We do this because the VPB in the IRP stack
// may not be the correct VPB when we get here. The underlying
// file system may change VPBs if it detects a volume it has
// mounted previously.
//
vpb = newDevExt->NLExtHeader.StorageStackDeviceObject->Vpb;
//
// Display a message when we detect that the VPB for the given
// device object has changed.
//
if (vpb != irpSp->Parameters.MountVolume.Vpb) {
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume: VPB in IRP stack changed %p IRPVPB=%p VPB=%p\n",
vpb->DeviceObject,
irpSp->Parameters.MountVolume.Vpb,
vpb));
}
//
// See if the mount was successful.
//
if (NT_SUCCESS(Irp->IoStatus.Status)) {
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume: Mount volume success for %p \"%wZ\", status=%08x\n",
DeviceObject,
&newDevExt->NLExtHeader.DeviceName,
Irp->IoStatus.Status));
//
// Acquire lock so we can atomically test if we area already attached
// and if not, then attach. This prevents a double attach race
// condition.
//
ExAcquireFastMutex(&gSfilterAttachLock);
//
// The mount succeeded. If we are not already attached, attach to the
// device object. Note: one reason we could already be attached is
// if the underlying file system revived a previous mount.
//
if (!SfIsAttachedToDevice(vpb->DeviceObject, &attachedDeviceObject)) {
//
// Attach to the new mounted volume. The file system device
// object that was just mounted is pointed to by the VPB.
//
/// 绑定
status = SfAttachToMountedDevice(vpb->DeviceObject, ///< 生成的卷设备对象
NewDeviceObject); ///< 新的过滤设备对象
if (NT_SUCCESS(status)) {
justAttached = TRUE;
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume: Mount volume - attached successfully to %p \"%wZ\", status=%08x\n",
DeviceObject,
&newDevExt->NLExtHeader.DeviceName,
Irp->IoStatus.Status));
}
else {
//
// The attachment failed, cleanup. Since we are in the
// post-mount phase, we can not fail this operation.
// We simply won't be attached. The only reason this should
// ever happen at this point is if somebody already started
// dismounting the volume therefore not attaching should
// not be a problem.
//
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume: Mount volume - attached to volume failed %p \"%wZ\", status=%08x\n",
DeviceObject,
&newDevExt->NLExtHeader.DeviceName,
Irp->IoStatus.Status));
SfCleanupMountedDevice(NewDeviceObject);
IoDeleteDevice(NewDeviceObject);
}
ASSERT(NULL == attachedDeviceObject);
}
else {
//
// We were already attached, handle it
//
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume Mount volume - already attached to %p \"%wZ\"\n",
((PSFILTER_DEVICE_EXTENSION)attachedDeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
&newDevExt->NLExtHeader.DeviceName));
//
// Cleanup and delete the device object we created
//
SfCleanupMountedDevice(NewDeviceObject);
IoDeleteDevice(NewDeviceObject);
//
// Dereference the returned attached device object
//
ObDereferenceObject(attachedDeviceObject);
}
//
// Release the lock
//
ExReleaseFastMutex(&gSfilterAttachLock);
//
// If we just successfully attached to the device and the appropriate
// debug flag is set, then get the DOS device name. We couldn't
// do this above because a mutex was held.
//
if (justAttached && FlagOn(SfDebug, SFDEBUG_GET_DOS_NAMES) &&
newDevExt->NLExtHeader.StorageStackDeviceObject != NULL) {
NLGetDosDeviceName(NewDeviceObject,
&newDevExt->NLExtHeader);
}
}
else {
//
// The mount request failed, handle it.
//
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfFsControlMountVolume: Mount volume failure for %p \"%wZ\", status=%08x\n",
DeviceObject,
&newDevExt->NLExtHeader.DeviceName,
Irp->IoStatus.Status));
//
// Cleanup and delete the device object we created
//
SfCleanupMountedDevice(NewDeviceObject);
IoDeleteDevice(NewDeviceObject);
}
//
// Complete the request.
// NOTE: We must save the status before completing because after
// completing the IRP we can not longer access it (it might be
// freed).
//
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
NTSTATUS
SfAttachToMountedDevice(
IN PDEVICE_OBJECT DeviceObject,
IN PDEVICE_OBJECT SFilterDeviceObject
)
/*++
Routine Description:
This will attach to a DeviceObject that represents a mounted volume.
Arguments:
DeviceObject - The device to attach to
SFilterDeviceObject - Our device object we are going to attach
Return Value:
Status of the operation
--*/
{
PSFILTER_DEVICE_EXTENSION newDevExt = SFilterDeviceObject->DeviceExtension;
NTSTATUS status;
ULONG i;
PAGED_CODE();
ASSERT(IS_MY_DEVICE_OBJECT(SFilterDeviceObject));
#if WINVER >= 0x0501
ASSERT(!SfIsAttachedToDevice(DeviceObject, NULL));
#endif
//
// Propagate flags from Device Object we are trying to attach to.
// Note that we do this before the actual attachment to make sure
// the flags are properly set once we are attached (since an IRP
// can come in immediately after attachment but before the flags would
// be set).
//
if (FlagOn(DeviceObject->Flags, DO_BUFFERED_IO)) {
SetFlag(SFilterDeviceObject->Flags, DO_BUFFERED_IO);
}
if (FlagOn(DeviceObject->Flags, DO_DIRECT_IO)) {
SetFlag(SFilterDeviceObject->Flags, DO_DIRECT_IO);
}
ASSERT(newDevExt->NLExtHeader.ThisDeviceObject == SFilterDeviceObject);
//
// It is possible for this attachment request to fail because this device
// object has not finished initializing. This can occur if this filter
// loaded just as this volume was being mounted.
//
/// 函数不太可靠,所以循环了8次进行绑定
for (i = 0; i < 8; i++) {
LARGE_INTEGER interval;
//
// Attach our device object to the given device object
// The only reason this can fail is if someone is trying to dismount
// this volume while we are attaching to it.
//
status = SfAttachDeviceToDeviceStack(SFilterDeviceObject,
DeviceObject,
&newDevExt->NLExtHeader.AttachedToDeviceObject);
if (NT_SUCCESS(status)) {
//
// Finished all initialization of the new device object, so clear
// the initializing flag now. This allows other filters to now
// attach to our device object.
//
ClearFlag(SFilterDeviceObject->Flags, DO_DEVICE_INITIALIZING);
//
// Display the name
//
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
("SFilter!SfAttachToMountedDevice: Attaching %p to volume %p \"%wZ\"\n",
SFilterDeviceObject,
newDevExt->NLExtHeader.AttachedToDeviceObject,
&newDevExt->NLExtHeader.DeviceName));
return STATUS_SUCCESS;
}
//
// Delay, giving the device object a chance to finish its
// initialization so we can try again
//
interval.QuadPart = (500 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode, FALSE, &interval);
}
return status;
}
NTSTATUS
SfFsControlCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This routine is invoked for the completion of an FsControl request. It
signals an event used to re-sync back to the dispatch routine.
Arguments:
DeviceObject - Pointer to this driver's device object that was attached to
the file system device object
Irp - Pointer to the IRP that was just completed.
Context - Pointer to the event to signal
--*/
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
ASSERT(Context != NULL);
/// XP及以后版本
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
//
// On Windows XP or later, the context passed in will be an event
// to signal.
//
/// 只需要对事件进行设置即可
KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
}
else {
#endif
//
// For Windows 2000, if we are not at passive level, we should
// queue this work to a worker thread using the workitem that is in
// Context.
//
if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
//
// We are not at passive level, but we need to be to do our work,
// so queue off to the worker thread.
//
/// 把绑定函数放在工作者线程中执行
ExQueueWorkItem((PWORK_QUEUE_ITEM)Context,
DelayedWorkQueue);
}
else {
PWORK_QUEUE_ITEM workItem = Context;
//
// We are already at passive level, so we will just call our
// worker routine directly.
//
/// 直接在这里完成绑定
(workItem->WorkerRoutine)(workItem->Parameter);
}
#if WINVER >= 0x0501
}
#endif
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
SfCreate(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This function filters create/open operations. It simply establishes an
I/O completion routine to be invoked if the operation was successful.
Arguments:
DeviceObject - Pointer to the target device object of the create/open.
Irp - Pointer to the I/O Request Packet that represents the operation.
Return Value:
The function value is the status of the call to the file system's entry
point.
--*/
{
NTSTATUS status;
PNAME_CONTROL fileName = NULL;
PSFILTER_DEVICE_EXTENSION devExt = (PSFILTER_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
BOOLEAN cacheName;
PAGED_CODE();
//
// If this is for our control device object, don't allow it to be opened.
//
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
//
// Sfilter doesn't allow for any communication through its control
// device object, therefore it fails all requests to open a handle
// to its control device object.
//
// See the FileSpy sample for an example of how to allow creates to
// the filter's control device object and manage communication via
// that handle.
//
//Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
//return STATUS_INVALID_DEVICE_REQUEST;
return STATUS_SUCCESS; ///< 控制设备对象,说明客户端正在加载驱动,返回STATUS_SUCCESS,客户端就可打开驱动设备对象
}
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject)); /// 断言表示必须为真,不然就蓝屏
//
// If debugging is enabled, do the processing required to see the packet
// upon its completion. Otherwise, let the request go with no further
// processing.
//
if (!FlagOn(SfDebug, SFDEBUG_DO_CREATE_COMPLETION | ///< SfDebug一般是从注册表中读出来的,这里硬编码了
SFDEBUG_GET_CREATE_NAMES |
SFDEBUG_DISPLAY_CREATE_NAMES)) {
//
// We don't want to get filenames, display filenames, or
// call our completion routine. Don't put us on the stack
// and call the next driver.
//
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
Irp);
}
if (FlagOn(SfDebug, SFDEBUG_GET_CREATE_NAMES |
SFDEBUG_DISPLAY_CREATE_NAMES) &&
!FlagOn(devExt->Flags, SFDEVFL_DISABLE_VOLUME)) {
//
// Debugging specifies that we need to get the filename
//
NAME_LOOKUP_FLAGS LookupFlags = 0x00000000;
//
// If DosName has been set, indicate via flags that we
// want to use it when getting the full file name.
//
if (devExt->NLExtHeader.DosName.Length != 0) {
SetFlag(LookupFlags, NLFL_USE_DOS_DEVICE_NAME);
}
//
// Indicate we are in pre-create
//
SetFlag(LookupFlags, NLFL_IN_CREATE);
if (FlagOn(irpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID)) {
//
// The file is being opened by ID, not file name.
//
SetFlag(LookupFlags, NLFL_OPEN_BY_ID);
}
if (FlagOn(irpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
//
// The file's parent directory should be opened
//
SetFlag(LookupFlags, NLFL_OPEN_TARGET_DIR);
}
//
// Retrieve the file name. Note that in SFilter we don't do any name
// caching.
//
/// 为拿到文件的名字,先分配一个结构
status = NLAllocateNameControl(&fileName, &gSfNameBufferLookasideList);
if (NT_SUCCESS(status)) {
//
// We are okay not checking the return value here because
// the GetFullPathName function will set the Unicode String
// length to 0. So either way, in an error it will print an empty string
//
/// 拿到文件的全路径
status = NLGetFullPathName(irpSp->FileObject,
fileName,
&devExt->NLExtHeader,
LookupFlags,
&gSfNameBufferLookasideList,
&cacheName);
}
}
if (FlagOn(SfDebug, SFDEBUG_DISPLAY_CREATE_NAMES |
SFDEBUG_DO_CREATE_COMPLETION) &&
!FlagOn(devExt->Flags, SFDEVFL_DISABLE_VOLUME)) {
//
// Debugging flags indicate we must do completion.
// Note that to display file names we must do completion
// because we don't know IoStatus.Status and IoStatus.Information
// until post-create.
//
KEVENT waitEvent;
//
// Initialize an event to wait for the completion routine to occur
//
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
//
// Copy the stack and set our Completion routine
//
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
Irp,
SfCreateCompletion,
&waitEvent,
TRUE,
TRUE,
TRUE);
//
// Call the next driver in the stack.
//
status = IoCallDriver(devExt->NLExtHeader.AttachedToDeviceObject, Irp);
//
// Wait for the completion routine to be called
//
if (STATUS_PENDING == status) {
NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent,
Executive,
KernelMode,
FALSE,
NULL);
ASSERT(STATUS_SUCCESS == localStatus);
}
//
// Verify the IoCompleteRequest was called
//
ASSERT(KeReadStateEvent(&waitEvent) ||
!NT_SUCCESS(Irp->IoStatus.Status));
//
// If debugging indicates we should display file names, do it.
//
/// 打印文件的名字
if (irpSp->Parameters.Create.Options & FILE_OPEN_BY_FILE_ID) {
SF_LOG_PRINT(SFDEBUG_DISPLAY_CREATE_NAMES,
("SFilter!SfCreate: OPENED fo=%p %08x:%08x %wZ (FID)\n",
irpSp->FileObject,
Irp->IoStatus.Status,
Irp->IoStatus.Information,
&fileName->Name));
}
else {
SF_LOG_PRINT(SFDEBUG_DISPLAY_CREATE_NAMES,
("SFilter!SfCreate: OPENED fo=%p st=%08x:%08x %wZ\n",
irpSp->FileObject,
Irp->IoStatus.Status,
Irp->IoStatus.Information,
&fileName->Name));
}
//
// Release the name control structure if we have
//
if (fileName != NULL) {
NLFreeNameControl(fileName, &gSfNameBufferLookasideList);
}
//
// Save the status and continue processing the IRP
//
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
else {
//
// Free the name control if we have one
//
if (fileName != NULL) {
NLFreeNameControl(fileName, &gSfNameBufferLookasideList);
}
//
// Debugging flags indicate we did not want to display the file name
// or call completion routine.
// (ie SFDEBUG_GET_CREATE_NAMES && !SFDEBUG_DO_CREATE_COMPLETION)
//
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
Irp);
}
}
IoCopyxxX+完成例程
下发
IoCopyCurrentlrpStackLocationToNext
+完成例程
:把IRP下发之后,上层驱动在某个事件上进行等待。下一层驱动把IRP完成之后就会调用完成例程设置事件为有事件状态来通知上层驱动/// 拿到当前IRP的栈
/// 拿到下一层IRP的栈
/// 把当前IRP的栈拷贝到下一层IRP的栈上,因为当前IRP还没下发,所以下一层IRP的栈是空的,所以不存在下层IRP栈原来有数据被覆盖的问题
#define IoCopycurrentIrpStackLocationToNext (Irp){\
PIO_STACK_LOCATION __irpSp;\
PI0_STACK_LOCATION __nextIrpSp;\
__irpsp = IoGetcurrentIrpstackLocation((Irp));\
__nextIrpSp =IoGetNextIrpstackLocation((Irp));\
RtlCopyMemory( __nextIrpSp,__irpSp,FIELD__OFFSET(IO_STACK_IOCATIONCompletionRoutine));\
nextIrpSp->control = O;}
IoSkip+IoCall
直接下发
IoSkipCurrentIrpStackLocation
,下层设备拿到的IO_STACKLOCATION和当前的一样#define IoskipcurrentIrpStackLocation(Irp){\
(Irp)->currentLocation++; \
...
/// 相互抵消,相当于IRP栈没有发生变化,下一层驱动对应的IRP栈和当前驱动用的是同一个栈,好像IRP从来没有到达过当前的驱动层一样
/// 为什么不用像第一种方式一样需要拷贝栈数据?是因为当前驱动不需要知道下一层的IRP结果,就不需要保存当前驱动对应IRP栈的的空间了
/// 为什么不直接不加不减?因为下发必然会减(IoCallDriver),为保持下一层使用当前驱动对应的IRP栈,所以需要先加
IoskipcurrentIrpstackLocation(Irp);//location+1
IoCallDriver(deviceExtension->nextLower,Irp);//location-1
结束IRP
不下发/// 一般用于拒绝的时候
PIO_STACK_LOCATION irpStack= IoGetCurrentlrpStackLocation(Irp);
Irp->loStatus.Status = STATUS_SUCCESS;//STATUS_ACCESS_DENIED
irp->loStatus.Information = O
IoCompleteRequest(lrp, IO_NO_INCREMENT);
释放或完成
。IoCompletion
例程。IoCompletion
例程时,这个驱动就能够在IoCompletion 例程执行期间
重新获得对这一IRP的所有权。如此,loCompletion例程就能够访问IRP中的域。STATUS_MORE_PROCESSING_REQUIRED
,以将IRP 的所有权返回给分发例程
。
IoCompleteRequest
来完成这个IRP,或者还能将这个IRP标记为等候进一步处理。IoCopyxxX+完成例程
下发/// IoCopyxxX+完成例程 下发
/// 分发例程
KEVENT event;
KelnitializeEvent(&event, NotificationEvent,FALSE); ///< 设置一个事件
IoCopyCurrentlrpStackLocationToNext(Irp); ///把当前驱动对应的IRP栈数据拷贝到下一层驱动上IRP栈上
/// 为当前IRP设置一个完成例程
/// 驱动需要访问一个已经在栈里传下去的IRP,这个驱动必须实现并设置`IoCompletion`例程。
IoSetCompletionRoutine(Irp,
IoCompRoutine,
&event,
TRUE,TRUE,TRUE
);
/// 将IRP往下发
status = IoCallDriver(DeviceObject,Irp);
/// 如果IRP处于pending状态,就在这个事件上等待
if (status == STATUS_PENDING)
{
status = KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
NULL
);
ASSERT(NT_SUCCESS(status));
/// 驱动的分发例程也还需要在IRP被后面的驱动处理完成之后再处理它,一般在驱动程序将IRP传递个下一个驱动之后,就不再拥有这个IRP,并且不能试图再去访问它。否则会导致系统崩溃。因为那个IRP会被其它的驱动或者线程`释放或完成`。这里可以访问是因为设置了完成例程,并且这个IoCompletion例程返回了`STATUS_MORE_PROCESSING_REQUIRED`
status = Irp->IoStatus.Status;
IoFreelrp(Irp) ///< 原本有完成例程负责Irp的销毁,这时候就需要交给分发例程来做了
}
/// 完成例程
NTSTATUS IoCompRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PKEVENT event = Context;
/// 当I/O管理器调用`IoCompletion`例程时,这个驱动就能够在IoCompletion `例程执行期间`重新获得对这一IRP的所有权。
/// 如此,IoCompletion例程就能够访问IRP中的域。
Irp->UserIosb->Status = Irp->IoStatus.Status;
Irp->UserIosb->Infomation = Irp->IoStatus.Information;
KeSetEvent(event,IO_NO_INCREMENT, FALSE);
//IoFreelrp(Irp) ///< 原本有完成例程负责Irp的销毁,这时候就需要交给分发例程来做了
return STATUS_MORE_PROCESSING_REQUIRED;
/// 返回STATUS_MORE_PROCESSING_REQUIRED,以将IRP 的所有权返回给`分发例程`
/// I/O 管理器会停止IRP的处理,将最终完成IRP的在务留给分发例程。分发例程能够在之后调用`IoCompleteRequest` 来完成这个IRP,或者还能将这个IRP标记为等候进一步处理。
}
IoSkip+IoCall
直接下发PDEVICE_EXTENSION deviceExtension;
IoSkipCurrentlrpstackLocation(lrp);
/// 拿到保存在设备扩展里的下层设备deviceExtension
(PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
/// 下发
/// 在驱动程序将IRP传递个下一个驱动之后,就不再拥有这个IRP,并且不能试图再去访问它。否则会导致系统崩溃。
/// 因为这个IRP会被其它的驱动或者线程`释放或完成`。
return IoCallDriver(deviceExtension->TargetDeviceObject,Irp);
/// 没有设置完成例程(并且在完成例程中返回STATUS_MORE_PROCESSING_REQUIRED,以将IRP 的所有权返回给`分发例程`)
// Forward request to next driver
IoCopyCurrentlrpStackLocationToNext(Irp)
// Send the lRP down
status IoCallDriver(nextDevice,Irp);
// The following is an error because thisdriver
// no longer owns the IRP
if(status == STATUS_PENDING)
{
IoMarklrpPending(Irp); //错误,无权操作Irp了
}
// Return the lower driver's status
return status;
FilterCreate
(创建)
.sys
,.dll
文件或者一个链接
等等都属于文件创建操作FilterRead
(一般不拦,加解密处理)
隐私保护器
,那需要拦截,防止别人读取文件FilterWrite
(修改,小心加解密处理)
FilterSetInfo
(删,重命名)
FilterClose
(一般不拦)FilterClean
(写关闭等)
foul
,要把病毒写到文件中去,直接写foul
,在写的时候就会被杀毒软件匹配并拦截到。但每次写的时候只写一部分,第1次写f
,第2次写o
,第3次写u
,第4次写l
,写完之后文件保存就是foul
,绕过了杀毒软件的查杀。所以要在每次写完的关闭之前都需要扫描一遍文件是不是匹配病毒特征码,所以写关闭是需要拦截的。关闭之前不知道是读还是写
,在xp里面,关闭的时候information是2则表示是写,win7及以后版本则没有了这些标志。1min
之内则认为是写关闭。上下文
,写操作在打开文件的时候我们是知道的,因为打开文件的时候带写权限,把信息记录下来通过上下文的形式传给关闭操作的。//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
lpIrp->IoStatus.Status = Status;
lpIrp->IoStatus.Information = ulInfomation;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
return Status;
}
ulOptions = Irp Stack->Parameters.Create.Options;
FlagOn(IrpStack->FileObject->Flags,FO_VOLUME_OPEN)||FlagOn(ulOptions,FILE_DIRECTORY_FILE)||FlagOn(lrpStack->Flags,SL_OPEN_PAGING_FILE)
集中起来一次性写入
),因为磁盘是低速设备,减少IO次数是能提高效率的。IRP_NOCACHE
&& 非IRP_XXX_ PAGING _IO
,也就是用户程序设置FILE_NO_INTERMEDIATE_BUFFERING,流程是App->IO->FSD->DISKIRP_CACHE
&& 非IRP_XXX_PAGING_IO
,也就是用户程序默认设置, 流程是APP->IO->FSD-CC(Cache Manger)->MM(->FSD-DISK)IRP_PAGING_IO
:在情况2中:MM会发起一个IRP并标记为IRP_XXX_PAGING_IO
,流程是MM->FSD->DISK(on behalt of vm),所以IRP_PAGING_IO
不是由用户程序发起的,而是由内存管理器
发起的,所以不需要监控。FILE_NO_INTERMEDIATE_BUFFERING
&&非IRR_XXX_PAGING_IO
的时候会发给DISK,即App->IO->FSD->DISKIRP_XXX_PAGING_IO
时候会发给DISK,即MM->FSD->DISK if (
lpIrp->RequestorMode == KernelMode || //来自内核态
IsDir(lpIrpStack) == TRUE || //进程操作是个文件夹
PsGetCurrentProcessId() == g_hSystemProcID || //系统进程的创建
FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY) ||
(lpIrp->Flags & IRP_PAGING_IO) ||
(lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io放行,不是应用层程序发起的IO,而是内核由内存管理器发起的IO
{
bSkipped = TRUE; //放行
goto _EXIT;
}
IoCopyxxX+完成例程
下发,因为后面需要根据创建的结果决定是否将文件名插入到hash表(如果HASH表中没有的话,将获得的文件名放入HASH表中),供其它Filter函数使用
sfCreate(PDEVICE_OBJECT lpDevice, PIRP lpIrp)
{
PLIST_ENTRY CurrentList = NULL;
USER_RESULT R3UserResult = User_Pass;
NAME_LOOKUP_FLAGS LookupFlags = 0;
PNAME_CONTROL lpNameControl = NULL;
NTSTATUS ntStatus = STATUS_SUCCESS;
ULONG ulInfo = 0;
BOOLEAN bSkipped = FALSE;
ULONG ulDisposition = 0;
KEVENT waitEvent = { 0 };
IO_STACK_LOCATION* lpIrpStack = IoGetCurrentIrpStackLocation(lpIrp);
PFILE_OBJECT lpFileObject = lpIrpStack->FileObject;
PTWOWAY pTwoWay = NULL;
WCHAR wszLongName[MAX_PATH] = { 0 };
WCHAR wszFileName[MAX_PATH] = { 0 };
WCHAR wszDeviceName[MAX_PATH] = { 0 };
UNICODE_STRING ustrDeviceName = { 0 };
UNICODE_STRING ustrRule = { 0 };
BOOLEAN bPopWindow = TRUE;
/// 1.放行
/// 如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
lpIrp->IoStatus.Status = ntStatus;
lpIrp->IoStatus.Information = ulInfo;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
return ntStatus;
}
else if (!IS_MY_DEVICE_OBJECT(lpDevice))
{
///非法参数
lpIrp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
lpIrp->IoStatus.Information = ulInfo;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
return ntStatus;
}
else
{
/// 发给过滤设备的IRP
PSFILTER_DEVICE_EXTENSION lpDevExt = (PSFILTER_DEVICE_EXTENSION)(lpDevice->DeviceExtension);
PIO_SECURITY_CONTEXT securityContext = lpIrpStack->Parameters.Create.SecurityContext;
if (
lpIrp->RequestorMode == KernelMode || //来自内核态
IsDir(lpIrpStack) == TRUE || //进程操作是个文件夹
PsGetCurrentProcessId() == g_hSystemProcID || //系统进程的创建
FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY) ||
(lpIrp->Flags & IRP_PAGING_IO) ||
(lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io,不是应用层程序发起的IO,而是内核由内存管理器发起的IO
{
bSkipped = TRUE; //放行
goto _EXIT;
}
ulDisposition = (lpIrpStack->Parameters.Create.Options >> 24) & 0xFF; ///取下高8位
if (ulDisposition == FILE_CREATE || ulDisposition == FILE_OVERWRITE || ulDisposition == FILE_OVERWRITE_IF)
{
//修改打开?
}
else
{
bPopWindow = FALSE; //只读的情况下不弹窗
}
//在create时获取文件路径
if (FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY))
{
SetFlag(LookupFlags, NLFL_OPEN_TARGET_DIR);
}
if (lpDevExt->NLExtHeader.DosName.Length != 0)
{
SetFlag(LookupFlags, NLFL_USE_DOS_DEVICE_NAME);
}
if (FlagOn(lpIrpStack->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID))
{
SetFlag(LookupFlags, NLFL_OPEN_BY_ID);
}
SetFlag(LookupFlags, NLFL_IN_CREATE);
/// 2. 拿文件名/长短名转化,盘符转化。
ntStatus = GetFileNameFromObject(&lpNameControl, LookupFlags, lpIrpStack->FileObject, lpDevice);
if (lpNameControl == NULL ||
!NT_SUCCESS(ntStatus))
{
//允许
bSkipped = TRUE;
goto _EXIT;
}
if (IsShortNamePath(lpNameControl->Name.Buffer)) ///判断短名,即判断~
{
//实际上应该处理短格式文件名
ConverShortToLongName(wszLongName, lpNameControl->Name.Buffer, sizeof(WCHAR) * MAX_PATH);
RtlCopyMemory(lpNameControl->Name.Buffer, wszLongName, sizeof(WCHAR) * MAX_PATH);
}
ustrDeviceName.Buffer = wszDeviceName;
ustrDeviceName.Length = 0;
ustrDeviceName.MaximumLength = sizeof(WCHAR) * MAX_PATH;
RtlCopyUnicodeString(&ustrDeviceName, &lpNameControl->Name);
//RtlCopyMemory(wszDeviceName, lpNameControl->Name.Buffer, MAX_PATH*sizeof(WCHAR));
//ustrDeviceName:\device\harddiskvolume3\doc\1.txt
//wszFileName:c:\doc\1.txt
if (!GetNTLinkName(ustrDeviceName.Buffer, wszFileName)) ///文件名格式转换成符号链接的格式
{
bSkipped = TRUE;
goto _EXIT;
}
//ustrDeviceName:c:\doc\1.txt
RtlInitUnicodeString(&ustrDeviceName, wszFileName);
RtlCopyUnicodeString(&lpNameControl->Name, &ustrDeviceName);
/// 3. 匹配规则
//ADD MODULE FOR RULES
///硬编码不可取,把规则放到.dat文件里,用客户端读取规则文件,通过IoConctorl下发到内核驱动中,组织到一个链表里,通过链表来匹配规则
RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS"); ///全部用大小,是因为下面匹配的时候TRUE表示忽略大小写,规则就需要全部是大写的的
if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE))
{
//bSkipped = TRUE;
//goto _EXIT;
bPopWindow = FALSE;
}
else
{
DbgPrint("File:%wZ\n", &lpNameControl->Name);
DbgPrint("Noted: rule matched\n");
}
/// 4.弹窗
if (bPopWindow)
{
R3UserResult = hipsGetResultFromUser(L"创建", lpNameControl->Name.Buffer, NULL, User_DefaultNon);
}
if (R3UserResult == User_Block)
{
//禁止
lpIrp->IoStatus.Information = 0;
lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
ntStatus = STATUS_ACCESS_DENIED;
bSkipped = FALSE;
goto _EXIT;
}
/// Q:为什么这里需要使用`IoCopyxxX+完成例程`往下发
IoCopyCurrentIrpStackLocationToNext(lpIrp);
KeInitializeEvent(&waitEvent,
NotificationEvent,
FALSE);
IoSetCompletionRoutine(lpIrp,
FilterCreateCompletion,
&waitEvent,
TRUE,
TRUE,
TRUE);
ntStatus = IoCallDriver(((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
lpIrp);
if (ntStatus == STATUS_PENDING)
{
ntStatus = KeWaitForSingleObject(&waitEvent,
Executive,
KernelMode,
FALSE,
NULL);
}
/// A:因为这里需要知道IRP的执行结果
ntStatus = lpIrp->IoStatus.Status;
if (NT_SUCCESS(ntStatus))
{
/// 如果HASH表中没有的话,此处将获得的文件名放入HASH表中
/// 文件名只由在CreateFile成功之后才是可靠的,在创建之前,write,Read,删掉拿到的文件名都是不可靠的
/// 因为创建或者打开文件必须保证文件名是对的,不然无法成功,而且创建出来之后,不再依赖文件名了,以后操作都基于fileObjec,文件名完全可以被修改甚至抹除
/// 所以一开始创建或者打开文件获得的文件名放入HASH表中,后续以fileObject为key查询hash表,拿到文件名才是可靠的
{
PHASHDATA lpData = ExAllocatePoolWithTag(NonPagedPool, sizeof(HASHDATA), 'HSAH');
if (lpData)
{
RtlZeroMemory(lpData, sizeof(HASHDATA));
lpData->lpNameControl = lpNameControl;
lpNameControl = NULL;
LockWrite(&g_HashTableLock);
Insert((DWORD)lpFileObject, lpData, g_pHashTable); ///hash key是文件的内核对象 value是包含文件名的lpData
UnLockWrite(&g_HashTableLock);
}
}
}
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
}
_EXIT:
if (bSkipped)
{
IoSkipCurrentIrpStackLocation(lpIrp);
ntStatus = IoCallDriver(((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject, lpIrp);
}
if (lpNameControl != NULL)
{
NLFreeNameControl(lpNameControl, &gSfNameBufferLookasideList);
}
return ntStatus;
}
//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
lpIrp->IoStatus.Status = Status;
lpIrp->IoStatus.Information = ulInfomation;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
return Status;
}
if (
KeGetCurrentIrql() > APC_LEVEL || // Irql > APC_LEVEL
PsGetCurrentProcessId() == g_hSystemProcID || //系统进程
(lpIrp->Flags & IRP_PAGING_IO) ||
(lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io
{
bSkipped = TRUE; //放行
goto _Exit;
}
//此处需要通过FileObject获得文件名
pTwoWay = Find((DWORD)lpFileObject, g_pHashTable);
if (pTwoWay == NULL)
{
bSkipped = TRUE; //从表中根据file object没有找到的
goto _Exit;
}
lpNameControl = pTwoWay->data.lpNameControl;
RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS");
if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE)) //匹配规则
{
bSkipped = TRUE;
goto _Exit;
}
IoSkip+IoCall
直接下发) R3UserResult = hipsGetResultFromUser(L"写入", lpNameControl->Name.Buffer, NULL, User_DefaultNon);
if (R3UserResult == User_Block)
{
//禁止
lpIrp->IoStatus.Information = 0;
lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
Status = STATUS_ACCESS_DENIED;
bSkipped = FALSE;
goto _Exit;
}
//允许
bSkipped = TRUE;
goto _Exit;
}
_Exit:
if (bSkipped)
{
IoSkipCurrentIrpStackLocation(lpIrp);
Status = IoCallDriver(((PSfilter_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject, lpIrp);
}
//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
lpIrp->IoStatus.Status = Status;
lpIrp->IoStatus.Information = ulInfomation;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
return Status;
}
PSfilter_DEVICE_EXTENSION lpDevExt = (PSfilter_DEVICE_EXTENSION)(lpDevice->DeviceExtension);
if (PsGetCurrentProcessId() == g_hSystemProcID) //系统进程
{
bSkipped = TRUE;
goto _EXIT;
}
//从HASH表中获得文件名
pTwoWay = Find((DWORD)lpFileObject, g_pHashTable);
if (pTwoWay == NULL)
{
bSkipped = TRUE;
goto _EXIT;
}
lpNameControl = pTwoWay->data.lpNameControl;
if(lpIrpStack->Parameters.SetFile.FileInformationClass == FileRenameInformation ||
lpIrpStack->Parameters.SetFile.FileInformationClass == FileBasicInformation ||
lpIrpStack->Parameters.SetFile.FileInformationClass == FileAllocationInformation ||
lpIrpStack->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation ||
lpIrpStack->Parameters.SetFile.FileInformationClass == FileDispositionInformation)
{
switch (lpIrpStack->Parameters.SetFile.FileInformationClass)
{
case FileAllocationInformation:
case FileEndOfFileInformation:
szOper = L"设置大小";
bSkipped = TRUE;
goto _EXIT;
// break;
case FileRenameInformation:
szOper = L"重命名";
break;
case FileBasicInformation:
szOper = L"设置基础信息";
bSkipped = TRUE;
goto _EXIT;
//break;
case FileDispositionInformation:
bNeedPostOp = TRUE;
szOper = L"删除";
break;
}
}
else
{
// 允许
bSkipped = TRUE;
goto _EXIT;
}
3.匹配
RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS");
if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE))
{
bSkipped = TRUE; //没匹配上,放行
goto _EXIT;
}
if (lpIrpStack->Parameters.SetFile.FileInformationClass == FileRenameInformation)
{
///@todo 重命名的目标路径?
}
Irp->AssociatedIrp.SystemBuffer
;(从这里可以直接拿)更新hash表
(把文件名删除),所以使用IoCopyxxX+完成例程
将IRP下发更新hash表
(把文件名改了),所以使用IoCopyxxX+完成例程
将IRP下发 //如果是重名名,这里的NULL要改成重名后的数据
R3UserResult = hipsGetResultFromUser(szOper, lpNameControl->Name.Buffer, NULL, User_DefaultNon);
if (R3UserResult == User_Block)
{
// 禁止
lpIrp->IoStatus.Information = 0;
lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
Status = STATUS_ACCESS_DENIED;
bSkipped = FALSE;
goto _EXIT;
}
bSkipped = TRUE;
}
_EXIT:
if (bSkipped)
{
KEVENT waitEvent;
IoCopyCurrentIrpStackLocationToNext(lpIrp);
KeInitializeEvent(&waitEvent,
NotificationEvent,
FALSE);
IoSetCompletionRoutine(lpIrp,
SetFilterCompletion,
&waitEvent,
TRUE,
TRUE,
TRUE);
Status = IoCallDriver(
((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
lpIrp);
if (Status == STATUS_PENDING)
{
Status = KeWaitForSingleObject(&waitEvent,
Executive,
KernelMode,
FALSE,
NULL);
}
Status = lpIrp->IoStatus.Status;
///@todo to update the hash table if succeeded
IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
}
IoSkip+IoCall
直接下发.dat
文件里,用客户端读取规则文件,通过IoConctorl下发到内核驱动中,组织到一个链表里,通过链表来匹配规则加载顺序更易控制
。altitude
被绑定到合适的位置。可卸载
能力。
pending
起来,如果这时候把hook的函数卸载,当重新回来执行的时候,函数的地址就失效了,内存无效,蓝屏。仅需处理必要操作
的能力(pre_create,post create)
pre_create
(IRP下发之前),post create
(IRP处理完之后)通用
的过滤分发函数)被隐藏
兼容性
更好
名字处理
更容易
Zw*
函数,而是使用Flt*
函数,避免重入
(死循环)I/O Nanager
:负责把应用层的IO请求封装成IRP包,发送给Filter Manager
Filter Manager Frame
:把IRP重新组装成FLT_CALLBACK_DATA
结构体,把这个结构体传给逐层传给Minifilter驱动A,B,C(Altitude值不一样,每次加载的时候相对关系是固定的,值大的在上层,越优先处理),即Minifilter中没有IRP这一说法了,处理IO数据的时候都是从FLT_CALLBACK_DATA
结构体中拿数据。Sfilter
驱动,虽然夹在两个Filter Manager Frame
,但Sfilter驱动是没有高度
的。Sfilter驱动可以在Filter Manager Frame
的上面也可以在下面,二者之间没有说谁的级别比谁的级别高。minifilter驱动
必须有一个叫做altitude
的唯一标识符
。一个minifilter驱动的altitude定义了它加载时在I/O栈中相对其他minifilter驱动的位置。值越小,栈中位置就越低。反病毒在加减密之前
是合理的,如果加减密在前面,把文件加密了,反病毒就无法识别病毒特征码了。加密
和解密
数据的过滤驱动。FLT_REGISTRATION
的定义// https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/managing-contexts-in-a-minifilter-driver
const FLT_REGISTRATION fileMonitorRegistration =
{
sizeof( FLT_REGISTRATION), ///< Size
FELT_REGISTRATION_VERSION, ///< Version
0, ///< Flags
ContextRegistration, ///< ContextRegistration
fileMonitorCallbacks, ///< Operation callbacks,结构体数组,每一个结构体封装了IRP和与之对应的回调函数,注册的回调函数就是放在这个结构体数组里
fileMonUnload, ///< FilterUnload,在Minifilter卸载时调用,做一些清理工作
fileMonInstanceSetup, ///< InstanceSetup,instance类似Sfilter中的过滤设备对象,有多少的卷设备就生成多个实例与之绑定,生成的过程是不可见的(看不到生成实例的代码,是由框架去做的),instance的数量和卷设备对象的数量相等的,一一对应。
///< 调用时机:把Minifilter的实例绑定到卷设备对象上会调用这个函数
///< 作用:可以记录下instance对应卷设备的一些属性,比如说是C盘还是D盘,文件类型是FAT32还是NTFS,卷的扇区大小是512B还是4KB。存到一个上下文里面(是个缓存),下次用到的时候,之间从缓存里面拿数据既可以了,提高效率。
NULL, ///< InstanceQuery Teardown
fileMonInstanceTeardownStart, ///< InstanceTeardownStart
NULL, ///< InstanceTeardownComplete
NULL, ///< GenerateFileName
NULL, ///< GenerateDestinationFileName
NULL ///< NormalizeNameComponent
};
fileMonitorCallbacks
结构体数组的定义const FLT_OPERATION_REGISTRATION fileMonitorCallbacks[]=
{
{
IRP_MJ_CREATE, ///表示创建或者打开IRP的回调函数
FLTFL_OPERATION_REGISTRATION_SKIP_PAGlNG_IO, // Flage,忽略掉paging_io
HOOK_PreNtCreateFile, ///可以缺省其中一个
HOOK_PostNtCreateFile ///可以缺省其中一个
},
{
IRP_MJCLEANUP,
0,
HOOK_PreNtCleanup,
NULL
},
{
IRP_MJ_WRITE,
0,
HOOK_PreNtWriteFile,
HOOK_PostNtWriteFile
},
{
lRP_MJ_SETLINF ORMATION,
0,
HOOK_PreNtSetInformationFile,
HOOK_PostNtSetInformationFile
},
IRPZMJ_ORERATION_END
}
};
集中起来一次性写入
),因为磁盘是低速设备,减少IO次数是能提高效率的。IRP_NOCACHE
&& 非IRP_XXX_ PAGING _IO
,也就是用户程序设置FILE_NO_INTERMEDIATE_BUFFERING,流程是App->IO->FSD->DISKIRP_CACHE
&& 非IRP_XXX_PAGING_IO
,也就是用户程序默认设置, 流程是APP->IO->FSD-CC(Cache Manger)->MM(->FSD-DISK)IRP_PAGING_IO
:在情况2中:MM会发起一个IRP并标记为IRP_XXX_PAGING_IO
,流程是MM->FSD->DISK(on behalt of vm),所以IRP_PAGING_IO
不是由用户程序发起的,而是由内存管理器
发起的,所以不需要监控。FILE_NO_INTERMEDIATE_BUFFERING
&&非IRR_XXX_PAGING_IO
的时候会发给DISK,即App->IO->FSD->DISKIRP_XXX_PAGING_IO
时候会发给DISK,即MM->FSD->DISKHOOK_PreNtCreatelFile
/HOOK_PostNtCreateFile
定义/// create执行之前调用
FLT_PREOP_CALLBACK_STATUS HOOK_PreNtCreateFile(
PFLTCALLBACK_DATA Data, ///Filter Manager Frame将IRP重新组装成`FLT_CALLBACK_DATA`结构体
PCFLT_RELATED_OBJECTS FltObjects, ///与Minifilter相关的对象
PVOID *CompletionContext ///分配的一个context资源,可以传给Post函数处理,然后在Post函数释放掉context资源
)
//sandbox
//主防
//杀毒引擎
//加解密
return xxx; ///这个返回值是返回给Minifilter管理器的,拿到返回值之后再决定要不要把操作继续往下发给Mnifilter驱动或者Sfilter驱动
/// Data->IoStatus.Status = STATUS_ACCESS_DENIED; 这个才是返回给IO管理器的,即应用层
/// PRE-OP的返回值:(和sfilter比较)
/// FLT_PREOP_SUCCESS_WITH_CALLBACK // 告诉Minifilte管理器要把操作往下发,结束之后要调用Post,类似Sfilter中`IoCopyxxX+完成例程`将IRP下发
/// FLTPREOP_SUCCESS_NO_CALLBACK // 告诉Minifilte管理器要把操作往下发,结束之后但不需要调用Post,类似Sfilter中`IoSkip+IoCall`直接下发
/// FLT_PREOP_PENDING // 挂起
/// FLT PREOP DISALLOW_FASTIO // 禁用fastio
/// FLT_PREOP_COMPLETE // 告诉Minifilte管理器要把操作完成之后不下发了,当前为止,不下发有拒绝(STATUS_ACCESS_DENIED),成功完成(STATUS_ACCESS)
/// FLT_PREOP_SYNCHRONIZE // 同步
}
/// create完成之后创建
FLT_POSTOP_CALLBACK_STATUS HOOK_PostNtCreateFile(
PFLT_CALLBACK_DATA Data,
PCFLT RELATED_OBJECTS FltObjects,
PVOID completionContext, //在PRE-OP里返回FLT_PREOPsuCcEss_wITH_CALLBACK 时获取里面的上下文,并最后释放Context资源
FLT_POST_OPERATION_FLAGS Flags
)
{
return xxx;
// POST-OP的返回值:
// FLT_POSTOP_FINISHED_PROCESSING // 最终完成处理
// FLT_POSTOP_MORE_PROCESSING_REQUIRED // post处理完之后,还需要更多处理,一般发生在Post里面,如果Irql比较高,比如处于DISPATCH_LEVEL,这样需要做一些操作的时候,是需要开一个工作者线程去做,这时候就需要返回一个FLT_POSTOP_MORE_PROCESSING_REQUIRED
}
FltObjects->volume,FltObjects->Instance,FltObjects->FileObject,
FltObjects->FileObject->DeviceObject
判断Data是什么操作的宏
FLT_IS_IRP_OPERATION ///应用下发的IRP
FLT_IS_FASTIO_OPERATION ///走缓存
FLJ IS_FS_FILTER_OPERATION ///其他Minifilter或者Sfilter下发的
// eg:禁用fastio
if(FLT_IS_FASTIO_OPERATION(Data)) ///为真则是Fasdtio操作
{
ntStatus STATUS_FLT DISALLOW_FAST_I0;
Data->loStatus.status = ntStatus;
Data->loStatus.Information = 0;
return FLT_PREOR_DISALLOW_FASTIO;
}
参数数据的获取
PFLT_CALLBACK_DATA Data;
PEPROCESS processObject = Data->Thread ? loThreadToProcess(Data->Thread) : PsGetCurrentProcess(); ///把Thread转换成Exprocess结构
HandleToUlong(PsGetProcessldf processObject));///通过Exprocess结构拿到PID
Data->IoStatus.Status = ntStatus; ///pIrp->loStatus.Status=ntStatus;
Data->IoStatus.Information = 0; ///跟IRP类似
Eltobjects->Volume, ///卷设备对象
FltObjects->Instance, ///实例对象和Volume一一对应
FltObjects->FileObject, ///要被操作目标文件的内核对象
FltObjects-FileObject->DeviceObject ///文件所在的设备对象
PMDE pReadMdl = Data->lopb->Parameters.Read.MdlAddress; ///MdlAddress!=NULL使用的是direct io
PVOID pReadBuffer = Data->Iopb->Parameters.ReadBuffer;///MdlAddress==NULL使用的是readbuffer
ULONG uReadLength = Data->lopb->Parameters.Read.Length; ///读数据的长度
Data->Iopb->Parameters.create.SecurityContext->DesiredAccess ///如果是创建,创建的一些操作可以拿到,比如说以写的方式,读的方式还是读写的方式等
PVOID pQueryBuffer
Data>Iopb->Parameters.DirectoryControl.QueryDirectory.DineectoryBuffer;
///查询文件夹的buffer
ULONG uQueryBuffersize = Data->lopb->Parameters.DirectoryControl.QueryDirectory.Length; ///查询文件夹的buffer长度
/// 返回
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
return FLT_PREOP_COMPLETE;
启动mifilter
/// 注册
/// 自己封装的一个initFileMonitor
NTSTATUS initFileMonitor(PDRIVER_OBJECT DriverObject )
{
// fileMonitorRegistration结构体传给FltRegisterFilter函数进行注册,得到句柄g_pFilter
return FltRegisterFilter( DriverObject,
&fileMonitorRegistration,
&g_pFilter);
}
/// 启动
NTSTATUS startFileMonitor()
{
if(g_pFilter)
// 启动Minifilter驱动,以后所有与Write,READ,SET_INFORMATION等相关的IO操作依次会被 _Pre* 和 _Pos*函数拦截
return FltStartFiltering(g_pFilter);
return STATUS_INSUFFICIENT_RESOURCES,
}
/// 卸载
VOID stopFileMonitor()
{
if(g_pFilter)
{
FltUnregisterFilter(g_pFilter); // 卸载时会调用fileMonitorRegistration.fileMonUnload来释放一些资源
g_pFilter = NULL;
}
}
null
表示没有为系统任何的Irp提供回调函数/*++
Copyright (c) 1999 - 2002 Microsoft Corporation
Module Name:
nullFilter.c
Abstract:
This is the main module of the nullFilter mini filter driver.
It is a simple minifilter that registers itself with the main filter
for no callback operations.
Environment:
Kernel mode
--*/
#pragma comment(lib, "FltMgr.lib")
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; };
#include
#include
#include
#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")
//---------------------------------------------------------------------------
// Global variables
//---------------------------------------------------------------------------
#define NULL_FILTER_FILTER_NAME L"NullFilter"
typedef struct _NULL_FILTER_DATA {
//
// The filter handle that results from a call to
// FltRegisterFilter.
//
PFLT_FILTER FilterHandle;
} NULL_FILTER_DATA, * PNULL_FILTER_DATA;
/*************************************************************************
Prototypes for the startup and unload routines used for
this Filter.
Implementation in nullFilter.c
*************************************************************************/
DRIVER_INITIALIZE DriverEntry;
NTSTATUS
DriverEntry(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
);
NTSTATUS
NullUnload(
__in FLT_FILTER_UNLOAD_FLAGS Flags
);
NTSTATUS
NullQueryTeardown(
__in PCFLT_RELATED_OBJECTS FltObjects,
__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
);
//
// Structure that contains all the global data structures
// used throughout NullFilter.
//
NULL_FILTER_DATA NullFilterData;
//
// Assign text sections for each routine.
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, NullUnload)
#pragma alloc_text(PAGE, NullQueryTeardown)
#endif
//
// This defines what we want to filter with FltMgr
//
CONST FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
NULL, // Context
NULL, // Operation callbacks 没有值,没有为系统的Irp提供回调,所以叫Nullfilter
NullUnload, // FilterUnload
NULL, // InstanceSetup
NullQueryTeardown, // InstanceQueryTeardown
NULL, // InstanceTeardownStart
NULL, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
};
/*************************************************************************
Filter initialization and unload routines.
*************************************************************************/
NTSTATUS
DriverEntry(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This is the initialization routine for this miniFilter driver. This
registers the miniFilter with FltMgr and initializes all
its global data structures.
Arguments:
DriverObject - Pointer to driver object created by the system to
represent this driver.
RegistryPath - Unicode string identifying where the parameters for this
driver are located in the registry.
Return Value:
Returns STATUS_SUCCESS.
--*/
{
NTSTATUS status;
UNREFERENCED_PARAMETER(RegistryPath);
//
// Register with FltMgr
//
status = FltRegisterFilter(DriverObject,
&FilterRegistration,
&NullFilterData.FilterHandle);
ASSERT(NT_SUCCESS(status));
if (NT_SUCCESS(status)) {
//
// Start filtering i/o
//
status = FltStartFiltering(NullFilterData.FilterHandle);
if (!NT_SUCCESS(status)) {
FltUnregisterFilter(NullFilterData.FilterHandle);
}
}
DbgPrint("Minifilter started\n");
return status;
}
NTSTATUS
NullUnload(
__in FLT_FILTER_UNLOAD_FLAGS Flags
)
/*++
Routine Description:
This is the unload routine for this miniFilter driver. This is called
when the minifilter is about to be unloaded. We can fail this unload
request if this is not a mandatory unloaded indicated by the Flags
parameter.
Arguments:
Flags - Indicating if this is a mandatory unload.
Return Value:
Returns the final status of this operation.
--*/
{
UNREFERENCED_PARAMETER(Flags);
PAGED_CODE();
/// 卸载
FltUnregisterFilter(NullFilterData.FilterHandle);
DbgPrint("Minifilter unloaded\n");
return STATUS_SUCCESS;
}
NTSTATUS
NullQueryTeardown(
__in PCFLT_RELATED_OBJECTS FltObjects,
__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
/*++
Routine Description:
This is the instance detach routine for this miniFilter driver.
This is called when an instance is being manually deleted by a
call to FltDetachVolume or FilterDetach thereby giving us a
chance to fail that detach request.
Arguments:
FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
opaque handles to this filter, instance and its associated volume.
Flags - Indicating where this detach request came from.
Return Value:
Returns the status of this operation.
--*/
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(Flags);
PAGED_CODE();
return STATUS_SUCCESS;
}
net start servicename/net stop name
;; 加载
SetupCopyOEMlnf
;; 或者
InstallHinfSection(NULL, NULL,TEXT("DefaultInstall128 .\\myfilter.inf"), 0);
;; 卸载
InstallHinfSection(NULL, NULL,TEXT("DefaultUninstall128 .\\upfilter.inf"), 0);
;;; 修改的时候只需要替换NullFilter替换成自己的Minifilter项目的名字
;;;
;;; NullFilter
;;;
;;;
;;; Copyright (c) 1999 - 2002, Microsoft Corporation
;;;
[Version]
Signature = "$Windows NT$"
Class = "ActivityMonitor" ;This is determined by the work this filter driver does,行为监控
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by the Class,ClassGuid和ActivityMonitor一一对应,可以查询MSDN的文档得到
;@Todo: File System Filter Driver Classes and Class GUIDs
Provider = %Msft% ;Minifilter的提供者
DriverVer = 06/16/2007,1.0.0.0 ;保持默认即可
CatalogFile = nullfilter.cat
[DestinationDirs]
DefaultDestDir = 12 ;12代表%windir%\system32\drivers
NullFilter.DriverFiles = 12 ;把.sys拷贝到%windir%\system32\drivers,
;;
;; Default install sections
;;
[DefaultInstall]
OptionDesc = %ServiceDescription%
CopyFiles = NullFilter.DriverFiles
[DefaultInstall.Services]
AddService = %ServiceName%,,NullFilter.Service
;;
;; Default uninstall sections
;;
[DefaultUninstall]
DelFiles = NullFilter.DriverFiles
[DefaultUninstall.Services]
DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting
;
; Services Section,实际上是在修改注册表
;
[NullFilter.Service]
DisplayName = %ServiceName% ;服务名
Description = %ServiceDescription% ;描述
ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\
Dependencies = "FltMgr" ;依赖的服务
ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER
StartType = 3 ;SERVICE_DEMAND_START,是需要的时候动态加载,不会随着系统启动
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor"
AddReg = NullFilter.AddRegistry
;
; Registry Modifications,Minifilter共有的一部分
;
[NullFilter.AddRegistry]
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%
;
; Copy Files
;
[NullFilter.DriverFiles]
%DriverName%.sys
[SourceDisksFiles]
nullfilter.sys = 1,,
[SourceDisksNames]
1 = %DiskId1%,,,
;;
;; String Section
;;
[Strings]
Msft = "Microsoft Corporation"
ServiceDescription = "NullFilter mini-filter driver"
ServiceName = "NullFilter"
DriverName = "NullFilter"
DiskId1 = "NullFilter Device Installation Disk"
;Instances specific information.
DefaultInstance = "Null Instance"
Instance1.Name = "Null Instance"
Instance1.Altitude = "370020"
Instance1.Flags = 0x1 ; Suppress automatic attachments
// MinifilterInstall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include
#include
#include
#include
//#include
//#include
#define DRIVER_NAME "nullfilter"
#define DRIVER_PATH ".\\nullfilter.sys"
#define DRIVER_ALTITUDE "370020" // 这里没有使用UNICODE编码,导致安装不生效,是因为工程是宽字节的工程,如果是多字节工程那就没问题
// SYS文件跟程序放在同个目录下
// InstallDriver(DRIVER_NAME,DRIVER_PATH,DRIVER_ALTITUDE);
// 启动驱动服务 StartDriver(DRIVER_NAME);
// 停止驱动服务 StopDriver(DRIVER_NAME);
// 卸载服务 DeleteDriver(DRIVER_NAME);
BOOL InstallDriver(const char* lpszDriverName,const char* lpszDriverPath,const char* lpszAltitude)
{
char szTempStr[MAX_PATH];
HKEY hKey;
DWORD dwData;
char szDriverImagePath[MAX_PATH];
if( NULL==lpszDriverName || NULL==lpszDriverPath )
{
return FALSE;
}
//得到完整的驱动路径
GetFullPathName(lpszDriverPath, MAX_PATH, szDriverImagePath, NULL);
SC_HANDLE hServiceMgr=NULL;// SCM管理器的句柄
SC_HANDLE hService=NULL;// NT驱动程序的服务句柄
//打开服务控制管理器
hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if( hServiceMgr == NULL )
{
// OpenSCManager失败
CloseServiceHandle(hServiceMgr);
return FALSE;
}
// OpenSCManager成功
//创建驱动所对应的服务
hService = CreateService( hServiceMgr,
lpszDriverName, // 驱动程序的在注册表中的名字
lpszDriverName, // 注册表驱动程序的DisplayName 值
SERVICE_ALL_ACCESS, // 加载驱动程序的访问权限
SERVICE_FILE_SYSTEM_DRIVER, // 表示加载的服务是文件系统驱动程序
SERVICE_DEMAND_START, // 注册表驱动程序的Start 值
SERVICE_ERROR_IGNORE, // 注册表驱动程序的ErrorControl 值
szDriverImagePath, // 注册表驱动程序的ImagePath 值
"FSFilter Activity Monitor",// 注册表驱动程序的Group 值
NULL,
"FltMgr", // 注册表驱动程序的DependOnService 值
NULL,
NULL);
if( hService == NULL )
{
if( GetLastError() == ERROR_SERVICE_EXISTS )
{
//服务创建失败,是由于服务已经创立过
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄
return TRUE;
}
else
{
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄
return FALSE;
}
}
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄
//-------------------------------------------------------------------------------------------------------
// SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances子健下的键值项
//-------------------------------------------------------------------------------------------------------
strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr,"\\Instances");
if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的DefaultInstance 值
strcpy(szTempStr,lpszDriverName);
strcat(szTempStr," Instance");
if(RegSetValueEx(hKey,"DefaultInstance",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
{
return FALSE;
}
RegFlushKey(hKey);//刷新注册表
RegCloseKey(hKey);
//-------------------------------------------------------------------------------------------------------
// SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance子健下的键值项
//-------------------------------------------------------------------------------------------------------
strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr,"\\Instances\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr," Instance");
if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的Altitude 值
strcpy(szTempStr,lpszAltitude);
if(RegSetValueEx(hKey,"Altitude",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的Flags 值
dwData=0x0;
if(RegSetValueEx(hKey,"Flags",0,REG_DWORD,(CONST BYTE*)&dwData,sizeof(DWORD))!=ERROR_SUCCESS)
{
return FALSE;
}
RegFlushKey(hKey);//刷新注册表
RegCloseKey(hKey);
return TRUE;
}
BOOL StartDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService;
if(NULL==lpszDriverName)
{
return FALSE;
}
schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
CloseServiceHandle(schManager);
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
}
if(!StartService(schService,0,NULL))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
if( GetLastError() == ERROR_SERVICE_ALREADY_RUNNING )
{
// 服务已经开启
return TRUE;
}
return FALSE;
}
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return TRUE;
}
BOOL StopDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService;
SERVICE_STATUS svcStatus;
bool bStopped=false;
schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schManager);
return FALSE;
}
if(!ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus) && (svcStatus.dwCurrentState!=SERVICE_STOPPED))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
}
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return TRUE;
}
BOOL DeleteDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService;
SERVICE_STATUS svcStatus;
schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schManager);
return FALSE;
}
ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus);
if(!DeleteService(schService))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
}
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return TRUE;
}
int main(int argc, char* argv[])
{
printf("Print any key to install driver\n");
getch();
DeleteDriver(DRIVER_NAME);
//安装驱动调用这个函数
BOOL bRet = InstallDriver(DRIVER_NAME, DRIVER_PATH, DRIVER_ALTITUDE);
if (bRet == FALSE)
{
printf("Driver install failed\n");
return -1;
}
printf("Print any key to start driver\n");
getch();
//启动驱动调用这个函数
bRet = StartDriver(DRIVER_NAME);
if (bRet == FALSE)
{
printf("StartDriver failed\n");
return -1;
}
printf("Print any key to stop driver\n");
getch();
//停止驱动调用这个
StopDriver(DRIVER_NAME);
//删除服务调用这个
DeleteDriver(DRIVER_NAME);
return 0;
}
/// 在postcreate里获得
PFLT_FILE_NAME_INFORMATON pNameInfo = NULL; ///Q:没有内存的指针,为什么可以直接传给FltGetFileNameInformation使用呢?
/// A:FltGetFileNameInformation是带缓存的,如果第一次查询文件的路径,函数会在内部为其分配内存,把文件名放到结构体的内存里面,其他驱动再次查询这个文件路径的时候,如果路径在缓存中,就直接把缓存给它,不再分配新内存。这么多人使用缓存,难以管理,做不到谁分配谁谁释放,所以这里使用了引用计数。
ntStatus = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED |
FLT_FILE_NAME_QuERY_DEFAULT,
&pNamelnfo); ///传二级指针或者指针的引用才能分配到内存
/// 解析
F1tParseFileNamelnformation(pNamelnfo);
pNamelnfo->Name //\\device\\harddiskvolume3\\doc\\x.dat
pNamelnfo->Volume
FltReleaseFileNameInformation(Namelnfo); // 并不是真正释放内存,而是将缓存的引用计数减1,当减到0的时候才释放内存
// 重命名路径的获得:
PFILE_RENAME_INFORMATION
pFileRenamelnfomation =(PENLE_RENAME_INFORMATION)Data->IopParameters.SetFilelnformationInfoBuffer;
// 或者通过这样获得重命名的路径
FltGetDestinationFileInation;
//
// operation registration
//
/// 几乎涵盖所有的IRP
/// 共用同一组PtPreOperationPassThrough和PtPostOperationPassThrough
CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
{IRP_MJ_CREATE,
0,
PtCreatePreOperationPassThrough,
PtCreatePostOperationPassThrough}, // 除了这个修改了一下
{IRP_MJ_CREATE_NAMED_PIPE,
0,
PtPreOperationPassThrough, // 直接返回
PtPostOperationPassThrough},
{IRP_MJ_CLOSE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_READ,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_WRITE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_QUERY_INFORMATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SET_INFORMATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_QUERY_EA,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SET_EA,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_FLUSH_BUFFERS,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_QUERY_VOLUME_INFORMATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SET_VOLUME_INFORMATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_DIRECTORY_CONTROL,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_FILE_SYSTEM_CONTROL,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_DEVICE_CONTROL,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_INTERNAL_DEVICE_CONTROL,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SHUTDOWN,
0,
PtPreOperationNoPostOperationPassThrough,
NULL}, // post operations not supported
{IRP_MJ_LOCK_CONTROL,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_CLEANUP,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_CREATE_MAILSLOT,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_QUERY_SECURITY,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SET_SECURITY,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_QUERY_QUOTA,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_SET_QUOTA,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_PNP,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_ACQUIRE_FOR_MOD_WRITE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_RELEASE_FOR_MOD_WRITE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_ACQUIRE_FOR_CC_FLUSH,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_RELEASE_FOR_CC_FLUSH,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_NETWORK_QUERY_OPEN,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_MDL_READ,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_MDL_READ_COMPLETE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_PREPARE_MDL_WRITE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_MDL_WRITE_COMPLETE,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_VOLUME_MOUNT,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_VOLUME_DISMOUNT,
0,
PtPreOperationPassThrough,
PtPostOperationPassThrough},
{IRP_MJ_OPERATION_END} };
FLT_PREOP_CALLBACK_STATUS
PtCreatePreOperationPassThrough(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID* CompletionContext)
/*++
Routine Description:
This routine is the main pre-operation dispatch routine for this
miniFilter. Since this is just a simple passThrough miniFilter it
does not do anything with the callbackData but rather return
FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
miniFilter in the chain.
This is non-pageable because it could be called on the paging path
Arguments:
Data - Pointer to the filter callbackData that is passed to us.
FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
opaque handles to this filter, instance, its associated volume and
file object.
CompletionContext - The context for the completion routine for this
operation.
Return Value:
The return value is the status of the operation.
--*/
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
FLT_POSTOP_CALLBACK_STATUS
PtCreatePostOperationPassThrough(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__in_opt PVOID CompletionContext,
__in FLT_POST_OPERATION_FLAGS Flags)
/*++
Routine Description:
This routine is the post-operation completion routine for this
miniFilter.
This is non-pageable because it may be called at DPC level.
Arguments:
Data - Pointer to the filter callbackData that is passed to us.
FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
opaque handles to this filter, instance, its associated volume and
file object.
CompletionContext - The completion context set in the pre-operation routine.
Flags - Denotes whether the completion is successful or is being drained.
Return Value:
The return value is the status of the operation.
--*/
{
NTSTATUS ntStatus;
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;
UNREFERENCED_PARAMETER(Data);
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
UNREFERENCED_PARAMETER(Flags);
/*
typedef struct _FLT_FILE_NAME_INFORMATION {
USHORT Size;
FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
FLT_FILE_NAME_OPTIONS Format;
UNICODE_STRING Name;
UNICODE_STRING Volume;
UNICODE_STRING Share;
UNICODE_STRING Extension;
UNICODE_STRING Stream;
UNICODE_STRING FinalComponent;
UNICODE_STRING ParentDir;
} FLT_FILE_NAME_INFORMATION, *PFLT_FILE_NAME_INFORMATION;
*/
ntStatus = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED |
FLT_FILE_NAME_QUERY_DEFAULT,
&pNameInfo);
if (NT_SUCCESS(ntStatus))
{
FltParseFileNameInformation(pNameInfo);
DbgPrint("FileName:%wZ\n", &pNameInfo->Name);
FltReleaseFileNameInformation(pNameInfo);
}
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("PassThrough!PtPostOperationPassThrough: Entered\n"));
return FLT_POSTOP_FINISHED_PROCESSING;
}
MiniFilterFItEnumerateFilters
,它会返回过滤器对象(FLT_FILTER)的地址根据过滤器对象地址,加上偏移,获得Minifilter中PreCall、 PostCallIRP等信息的结构体指针,可以Hook
掉。思路:
Data->lopb->Parameters.AcquireForSectionSynchronization.PageProtection ==PAGE_EXECUTE
,如果等于就是在创建进程,return STATS_ACCESS_DENY
patchguard
机制,除非使用VT技术欺骗操作系统),Minifilter不会触发patchguard
机制,所以x86和x64 都支持
。preoperation
callback routine can be called at IRQL_PASSIVE_LEVEL
or at IRQL_APC_LEVELS
Typically it is called at IRQL_PASSIVE_LEVEL
preoperation
callback routine returnsFLT_PREOP_SYNCHRONIZE
for an lRP-based l/O operation,the corresponding postoperation
callback routine is called at lRQL <= APC_LEVEL
, in the same thread context as the preoperation
postoperation
callback routine for a fast I/O
operation is calledIRQL_PASSIVE_LEVEL
, in the same thread context as the preoperation callback routine.Post-create
callback routines are called at IRQL_PASSIVE_LEVEL
IRP_MJ_CREATE
operation.preoperation
一定处于IRQL_APC_LEVELS
或者IRQL_PASSIVE_LEVEL
;而postoperation
根据具体情况具体分析:2-4中情况下,处于IRQL_PASSIVE_LEVEL
,其他情况就不好说了。FltCreateFile
FltReadFile
FltWriteFile
FltClose
FltQueryXxxFltSetXxXFltGetXxx
FltPerformXxx
ntStatus = FltCreateFile(pFilter, ///这个参数是注册Minifilter返回的句柄
pDstInstance, ///这个参数是实例的指针,从FltObjects中拿,这两个参数是FltCreateFile特有的,其他参数和FltReadFile、FltWriteFile等都一致
&hDstFile,
GENERIC_WRITE | SYNCHRONIZE,
&objDstAttrib,
&ioStatus,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ |
FILE_SHARE_WRITE |
FILE_SHARE DELETE,
FILECREATE,
CreateOptions,
NULL,
0,
0);
设备扩展是什么?
context上下文
:其实就是附着在某个对象上的一段内存,这段内存缓存的相关数据由自己定义;
利用缓存提高程序执行的效率
是非常重要的思想,包括在系统架构里面,经常使用Redis(NOSQL数据库)把平时查询的数据库缓存起来,来缓解关系数据库后台执行程序的压力.和中断、进程上下文区别
:
进程的上下文
Minifilter的上下文
:其实就是附着在Minifilter
中对象上的一段内存,这段内存缓存的相关数据由自己定义(其实是Mifnifilter运行期间的所在环境中的信息)
Minifilter
中常见的对象分配上下文
FltAllocateContext // 分配上下文,可以指定上下文的类型,比如Stream Context,Stream Handle Context,Instance Contxt等等
FltReleaseContext // 释放上下文,其实就是把引用计数减1,直到引用计数变为0之后才释放
Minifilter上下文的应用
:写关闭
写关闭
:以写的方式打开,得到文件的句柄,往这个文件写入数据,最后把文件关闭掉CloseHandle(FileHandle)
,所以单从FileHanle这个参数是无法知道是读关闭还是写关闭(在XP系统,IRP_MJ_Clean这个IRP里面information等于2
,就表示是写关闭,否则就是都关闭;但在vista中这个条件就不成立了)1min之内
,则认为是写关闭。Minifilter则可以使用上下文来记录标记
,比如打开文件文件的时候是可以知道R/W的
(必须传一个读写的标志),然后把这个标记信息记录在文件(FileObject)的上下文
里面,Clean的时候(调用CloseHandle(FileHandle)的时候),通过FilerHander找到对应的FileObject的上下文找到打开时记录的R/W
的标志,如果是写的话,即写关闭就处理它(扫描一遍该文件,和病毒库的特征码匹配一遍,防止生成病毒)。根据对象不同
分为不同的类。Stream Context
(流上下文),绑定到FCB (File control Block,文件控制块)的上下文,FltGetStreamContext // 获取对象上的上下文
FltSetStreamContext // 将缓存重新设置到对象上,比如修改了上下文的数据,使用这个函数把上下文更新到对象上
Stream Handle Context
(流句柄上下文),就是常见FO(FileObjec),文件和FileObjec是多对一的关系。
FltGetStreamHandleContext
FltSetStreamHandleContext
Instance Context
(实例上下文),也就是过滤驱动在文件系统的设备堆栈上创建的一个过滤器实例;FltGetlnstanceContext // 获取对象上的上下文
FltSetInstanceC ontext // 将缓存重新设置到对象上,比如修改了上下文的数据,使用这个函数把上下文更新到对象上
volume context
(卷上下文),卷就是通常看到的C,D盘以及网络重定向器,一般情况下一个券对一个对滤器实例对象,在实际应用上经常用Instance Context来代替volume Context。
(在启动Minifilter的时候有多少卷设备对象就生成多少个Minigilter实例,安装到卷设备上,安装的那一刻可以把卷设备的信息查询出来(卷的名字,卷的文件系统信息,文件系统类型,卷的扇区大小等))放到Instance Context,以后要想知道卷的信息,直接从Instance Context获取即可。FltGetVolumeContext
FltSetvolumeContext
文件上下文
(vista之后)FltGetFileContext
FltSetFileContext
Stream Handle Context
和 Instance Context
使用频率最高fileMonitorCallbacks
数组,还有一组用来清理Context的回调函数数组
PFLT_FILTER g_pFilter = NULL;
const FLT_CONTEXT_REGISTRATION ContextRegistration[]=
{
/// 在释放context之前调用,可以在此释放context里的内存等
FLT_INSTANCE_CONTEXT,
0,
CtxContextCleanup, ///每种类型的Context都共用同一个CtxContextCleanup
///Q:既然有FltReleaseContext来释放Context了,为什么这里还需要CtxContextCleanup?
///A:是为了针对Minifilter的Context中保存了一些其他资源或者指向其他内存的情况,可以用CtxContextCleanup释放掉,比如Minifilter的Context中存放了文件的句柄、锁、指针,得先用CtxContextCleanup释放掉,才可以用FltReleaseContext来释放Context(类似C++中的delete,释放内存之前调用析构函数把内存中的其他资源释放掉,再释放要内存)
CTX_INSTANCE_CONTEXT_SIZE,
CTX_INSTANCE_CONTEXT_TAG
},
{
FLT_FILE_CONTEXT,
0,
CtxContextCleanup,
CTX_FILE_CONTEXT_SIZE,
CTX_FILE_CONTEXT_TAG
},
{
FLT_STREAM_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAM_CONTEXT_SIZE,
CTX_STREAM_CONTEXT_TAG
},
{
FLT_STREAMHANDLE_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAMHANDLE_CONTEXT_SIZE,
CTX_STREAMHANDLEICONTEXT_TAG
},
{
FLT_CONTEXT_END
}
}
/// @Warring 多线环境下记得加锁
typedef struct _INSTANCE_CONTEXT{
... ///自定义的数据
}INSTANCE_CONTEXT,*PINSTANCECCONTEXT;
PINSTANCE_CONTEXT pContext = NULL;
/// 分配与设置
ntStatus = FltGetInstanceContext(FltObjects->Instance,&pContext); ///拿到上下文
if(NT_SUCCESS(ntStatus) == FALSE) ///失败说明还没有为其指定上下文
{
/// 分配上下文,指定上下文的类型
ntStatus = FltAllocateContexti(g_pFilter, ///Minifilter的句柄
FLT_INSTANCE_CONTEXT, ///Context类型
sizeof(INSTANCE_CONTEXT), ///Context大小
PagedPool, ///从PagedPool中分配
&pContext), ///分配的内存的首地址放在这个指针上
if(NT_SUCCESS(Status) == FALSE){
return ntStatus; ///如果分配失败,该函数直接return了
}
RtZeroMemory(pContext,sizeof(INSTANCE_CONTEXT)); ///初始化为0
}
/// 结构体里面定义了设备类型和结构体类型
pContext->m_DeviceType = VolumeDeviceType;
pContext->m_FSType = volumeFilesystemType;
FltSetInstanceContext(FltQbjects->Instance,FLT_SET_CONTEXT_TREPLACE_IF_EXISTS,pContext,NLLL); ///把刚分配的上下文绑定到instance上去,引用计数+1
if (pContext) // 如果分配成功,需要对引用计数进行减1,因为实际上没有新的Context产生,为保持引用计数的数量,一加就需要一减
{
FltReleaseContext(pContext); ///引用计数-1
}
/// 获取访问
PINSTANCE_CONTEXT pContext = NULL;
Status = FltGetInstanceContext{EItObjects->Instance,&pContext); ///引用计数+1
pContext->xxx->xxx;
FltReleaseConte // -1
两套API
,一套是在R3中调用的,另一套是在R0中调用的
DeviceIoControl
发数据给R0,R0通过DispatchIoctr
来处理R3的下发的数据FilterSendMessage
发数据给R0,R0通过fnMessageFromClient
处理从R3 FilterSendMessage的请求
R3主动给R0发数据的情景
:
/// R0创建端口,
/// @note 可以指定安全属性,比如可以指定为只允许管理员连接
const PWSTR ScannerPortName = L"\\ScannerPort";
// status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); ///创建安全描述符,指定为只允许管理员连接
FltCreateCommunicationPort(g_pEilter,
&g_pSeryerPort,
&oa, ///设置PORT的名字
NULL,
///获得R3端口g_pClientPort等
fnConnectFromCRlient, ///有客户端连接R0端口的时候被调用
// 作用1:这个函数执行的时候处在应用层当前进程的上下文中,可以记录下进程的信息,比如pid和Exprocess结构,以后发现由这个客户端发下来的请求,就可以把他放行了
// 作用2:拿到R3的端口
fnDisconnectFromclient, ///当客户端断开连接的时候被调用
fnMessageFromClient, ///处理从R3 FilterSendMessage的请求,名字和随便起,只要保证接口一致,注册时一一对应即可
1);
/// R3连接R0,即R3拿到R0的端口
FilterConnectCommunicationPort
/// R3主动发请求给RO
FilterSendMessage(Port, ///拿到的R0端口
&request, ///R3给R0发数据的缓存,自定义控制码放在这里
sizeof(REQUEST), //缓存的大小
&reply, ///R0处理完数据返回给R3的结果
sizeof(REPLY),
&dwRtn); ///实际上传输的字节数,对应IRP中的IOSTATUS.Information
/// R0处理从R3 FilterSendMessage的请求
NTSTATUS fnMessageFromClient(
IN PVOID PortCookie, ///Port相关的私有数据
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOLD OutputBuffer OPTIONAL,
IN PULONG OutputBufferLength,
OUT PULONG ReturnOutputEufferLength ///实际传输的长度
)
{
/// 这里通信方式使用的是neither io需要ProbeForRead和ProbeForWite
/// 1.保证内存对齐,2.保证地址合法(保证时用户态的地址,应用层程序没有权限访问内核态的地址),避免提权
__try
{
ProbeForRead(InputBuffer,InputBufferLength,sizeof(ULONG));
//GET InputBuffer
//Do something:
ProbeForWite(OutputBuffer,OutputBufferLength,sizeof(ULONG));
//Copy Result to Outputbuffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return STATUS_NOT_IMPLEMENTED;
}
return STATUS_SUCCESS;
}
FltSendMessage
发数据给R3(FltSendMessage可以设置超时等待
,比如内核层等待弹窗返回结果40s或者无限等待),R3开启多个线程(每个线程都可用来接受R0发送的数据),通过FilterGetMessage
来处理从R0的请求(异步读
,不管拿没拿到数据都会返回),拿到数据处理完之后通过FilterReplyMessage
返回给R0/// 发送消息给R3
timeout.QuadPart = (LONGLONG)40* -10000000i64; ///40 seconds,设为0不等待,设置为null无限等待
Status = FltSendMessage(g_pFilter,
&g_pClientPort, ///客户端的端口
&request,
sizeof(SCANNER_NOTIFICATION)
&reply,
replySize,
&timeout);
/// 关闭端口
FItCloseCommunicationPort(g_pServerPort);
/// 响应R0的:FltSendMessage()
/// 一般放在后台的工作者线程中去执行,而且可以创建多线程,在多个线程中去处理这个部分代码,提高处理的效率
FilterConnectCommunicationPort(ScannerPortName, ///与R0端口名字一致
0,
NULL,
0,
NULL,
&Port); ///RO端口
/// 处理从R0来的请求,即R0调用FltSendMessage的请求
completion = CreateloCompletionPort(port,NULL,0,1); ///创建一个完成端口
FilterGetMessage(Port, ///异步读,拿没拿到数据都不等待,直接返回
&message->MessagelHleader, ///数据放在这里
FIELD_OFFSET(SANDBOX_MESSAGE,Ovlp),
&message->Ovlp);
while(1)
{
GetQueuedCompletionStatus(completion,&outSize,&key,&pOvlp,INFINITE); ///在完成端口这里阻塞,一旦R0调用FltSendMessage(),completion完成端口有了数据之后,这个函数就会返回
... ///处理数据
/// R3处理结果返回给内核层
FilterReplyMessage(Port,
(PEILTER_REPLY_HEADER)
&replyMessage,
sizeof(replyMessage));
/// 异步读,继续下一个循环
FilterGetMessaget(Port,
&message->MessageHeader,
FIELD_OFFSET_SANDBOXMESSAGE,
Ovlp),
&message->Ovlp);
}
纯c写的
,比新的WDK的C++写的要好#include //C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um
#pragma comment(lib,"fltLib.lib") //C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86
.txt,doc,.inf,.bat
,"foul"
代表病毒的特征码
foul
),只能在应用层程序中编码openfile—>writefile->openfile->…/*++
Copyright (c) 1999-2002 Microsoft Corporation
Module Name:
scanner.c
Abstract:
This is the main module of the scanner filter.
This filter scans the data in a file before allowing an open to proceed. This is similar
to what virus checkers do.
Environment:
Kernel mode
--*/
#include
#include
#include
#include "scanuk.h"
#include "scanner.h"
#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")
//
// Structure that contains all the global data structures
// used throughout the scanner.
//
SCANNER_DATA ScannerData;
//
// This is a static list of file name extensions files we are interested in scanning
//
const UNICODE_STRING ScannerExtensionsToScan[] =
{ RTL_CONSTANT_STRING(L"doc"),
RTL_CONSTANT_STRING(L"txt"),
RTL_CONSTANT_STRING(L"bat"),
RTL_CONSTANT_STRING(L"cmd"),
RTL_CONSTANT_STRING(L"inf"),
/*RTL_CONSTANT_STRING( L"ini"), Removed, to much usage*/
{0, 0, NULL}
};
//
// Function prototypes
//
NTSTATUS
ScannerPortConnect(
__in PFLT_PORT ClientPort,
__in_opt PVOID ServerPortCookie,
__in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
__in ULONG SizeOfContext,
__deref_out_opt PVOID* ConnectionCookie
);
VOID
ScannerPortDisconnect(
__in_opt PVOID ConnectionCookie
);
NTSTATUS
ScannerpScanFileInUserMode(
__in PFLT_INSTANCE Instance,
__in PFILE_OBJECT FileObject,
__out PBOOLEAN SafeToOpen
);
BOOLEAN
ScannerpCheckExtension(
__in PUNICODE_STRING Extension
);
//
// Assign text sections for each routine.
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, ScannerInstanceSetup)
#pragma alloc_text(PAGE, ScannerPreCreate)
#pragma alloc_text(PAGE, ScannerPortConnect)
#pragma alloc_text(PAGE, ScannerPortDisconnect)
#endif
//
// Constant FLT_REGISTRATION structure for our filter. This
// initializes the callback routines our filter wants to register
// for. This is only used to register with the filter manager
//
const FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE, ///拦截Create操作,即打开文件的时候看文件是否有"foul",打开文件前会去扫描文件(得到文件的签名,发云端去查询好与坏)一般这些耗时的任务都是放在R3去做的
0,
ScannerPreCreate,
ScannerPostCreate},
{ IRP_MJ_CLEANUP,
0,
ScannerPreCleanup,
NULL},
{ IRP_MJ_WRITE,
0,
ScannerPreWrite,
NULL},
{ IRP_MJ_OPERATION_END}
};
const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {
{ FLT_STREAMHANDLE_CONTEXT,
0,
NULL, ///上下文没有保存其他资源需要清理的,清理函数设置为null
sizeof(SCANNER_STREAM_HANDLE_CONTEXT),
'chBS' },
{ FLT_CONTEXT_END }
};
/**
* @brief 这是一个代表 Student 的类.
* @details Student 有参数:name, age, and grade. 我们可以设置他们.
*/
const FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), ///< Size
FLT_REGISTRATION_VERSION, ///< Version
0, ///< Flags
ContextRegistration, ///< ContextRegistration
Callbacks, ///< Operation callbacks,结构体数组,每一个结构体封装了IRP和与之对应的回调函数,注册的回调函数就是放在这个结构体数组里
ScannerUnload, ///< FilterUnload
ScannerInstanceSetup, ///< InstanceSetup,instance类似Sfilter中的过滤设备对象,有多少的卷设备就生成多个个实例与之绑定,生成的过程是不可见的(看不到生成实例的代码,是由框架去做的)
///< 可以记录下instance对应卷设备的一些属性,比如说是C盘还是D盘,文件类型是FAT32还是NTFS,卷的扇区大小是512还是4K。存到一个上下文里面(是个缓存),下次用到的时候,之间从缓存里面拿数据既可以了,提高效率。
ScannerQueryTeardown, ///< InstanceQuery Teardown
NULL, ///< InstanceTeardownStart
NULL, ///< InstanceTeardownComplete
NULL, ///< GenerateFileName
NULL, ///< GenerateDestinationFileName
NULL ///< NormalizeNameComponent
};
//
// Filter initialization and unload routines.
//
NTSTATUS
DriverEntry(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This is the initialization routine for the Filter driver. This
registers the Filter with the filter manager and initializes all
its global data structures.
Arguments:
DriverObject - Pointer to driver object created by the system to
represent this driver.
RegistryPath - Unicode string identifying where the parameters for this
driver are located in the registry.
Return Value:
Returns STATUS_SUCCESS.
--*/
{
OBJECT_ATTRIBUTES oa;
UNICODE_STRING uniString;
PSECURITY_DESCRIPTOR sd;
NTSTATUS status;
UNREFERENCED_PARAMETER(RegistryPath);
//
// Register with filter manager.
//
status = FltRegisterFilter(DriverObject,
&FilterRegistration,
&ScannerData.Filter); ///Minifilter句柄
if (!NT_SUCCESS(status)) {
return status;
}
//
// Create a communication port.
//
RtlInitUnicodeString(&uniString, ScannerPortName); ///初始化端口
//
// We secure the port so only ADMINs & SYSTEM can acecss it.
//
status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); ///创建安全描述符,指定为只允许管理员连接
if (NT_SUCCESS(status)) {
InitializeObjectAttributes(&oa,
&uniString,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
sd);
/// @todo 完善回调函数
status = FltCreateCommunicationPort(ScannerData.Filter,
&ScannerData.ServerPort,
&oa,
NULL,
ScannerPortConnect,
ScannerPortDisconnect,
NULL,///@todo:处理R3的请求,这里R3没有主动发消息给R0,都是R0主动发消息给R3的,所以传NULL
1);
//
// Free the security descriptor in all cases. It is not needed once
// the call to FltCreateCommunicationPort() is made.
//
FltFreeSecurityDescriptor(sd);
if (NT_SUCCESS(status)) {
//
// Start filtering I/O.
//
status = FltStartFiltering(ScannerData.Filter); ///启动Minifilter驱动
if (NT_SUCCESS(status)) {
return STATUS_SUCCESS;
}
FltCloseCommunicationPort(ScannerData.ServerPort);
}
}
FltUnregisterFilter(ScannerData.Filter);
return status;
}
/**
* @brief ScannerPortConnect 是加载驱动程序后调用的第一个驱动程序提供的例程,它负责初始化驱动程序;
* @param[in] pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构;
* @param[in] pRegPath 驱动安装后在注册表中的路径,指向 UNICODE_STRING 结构的指针,
* 该结构指定注册表中驱动程序的 Parameters 项的路径;
* @return ntStatus 成功返回0,否则为非0,如果例程成功,则它必须返回 STATUS_SUCCESS。
* 否则,它必须返回在 ntstatus中定义的错误状态值之一;
* @pre
* @see https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*/
NTSTATUS
ScannerPortConnect(
__in PFLT_PORT ClientPort,
__in_opt PVOID ServerPortCookie,
__in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
__in ULONG SizeOfContext,
__deref_out_opt PVOID* ConnectionCookie
)
/*++
Routine Description
This is called when user-mode connects to the server port - to establish a
connection
Arguments
ClientPort - This is the client connection port that will be used to
send messages from the filter
ServerPortCookie - The context associated with this port when the
minifilter created this port.
ConnectionContext - Context from entity connecting to this port (most likely
your user mode service)
SizeofContext - Size of ConnectionContext in bytes
ConnectionCookie - Context to be passed to the port disconnect routine.
Return Value
STATUS_SUCCESS - to accept the connection
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER(ServerPortCookie);
UNREFERENCED_PARAMETER(ConnectionContext);
UNREFERENCED_PARAMETER(SizeOfContext);
UNREFERENCED_PARAMETER(ConnectionCookie);
ASSERT(ScannerData.ClientPort == NULL);
ASSERT(ScannerData.UserProcess == NULL);
//
// Set the user process and port.
//
ScannerData.UserProcess = PsGetCurrentProcess(); ///拿到当前进程的Exprocess结构
ScannerData.ClientPort = ClientPort; ///拿到客户端的结构体
DbgPrint("!!! scanner.sys --- connected, port=0x%p\n", ClientPort);
return STATUS_SUCCESS;
}
VOID
ScannerPortDisconnect(
__in_opt PVOID ConnectionCookie
)
/*++
Routine Description
This is called when the connection is torn-down. We use it to close our
handle to the connection
Arguments
ConnectionCookie - Context from the port connect routine
Return value
None
--*/
{
UNREFERENCED_PARAMETER(ConnectionCookie);
PAGED_CODE();
DbgPrint("!!! scanner.sys --- disconnected, port=0x%p\n", ScannerData.ClientPort);
//
// Close our handle to the connection: note, since we limited max connections to 1,
// another connect will not be allowed until we return from the disconnect routine.
//
FltCloseClientPort(ScannerData.Filter, &ScannerData.ClientPort); ///把应用层端口关闭
//
// Reset the user-process field.
//
ScannerData.UserProcess = NULL;
}
NTSTATUS
ScannerUnload(
__in FLT_FILTER_UNLOAD_FLAGS Flags
)
/*++
Routine Description:
This is the unload routine for the Filter driver. This unregisters the
Filter with the filter manager and frees any allocated global data
structures.
Arguments:
None.
Return Value:
Returns the final status of the deallocation routines.
--*/
{
UNREFERENCED_PARAMETER(Flags);
//
// Close the server port.
//
FltCloseCommunicationPort(ScannerData.ServerPort);
//
// Unregister the filter
//
FltUnregisterFilter(ScannerData.Filter);
return STATUS_SUCCESS;
}
NTSTATUS
ScannerInstanceSetup(
__in PCFLT_RELATED_OBJECTS FltObjects,
__in FLT_INSTANCE_SETUP_FLAGS Flags,
__in DEVICE_TYPE VolumeDeviceType,
__in FLT_FILESYSTEM_TYPE VolumeFilesystemType
)
/*++
Routine Description:
This routine is called by the filter manager when a new instance is created.
We specified in the registry that we only want for manual attachments,
so that is all we should receive here.
Arguments:
FltObjects - Describes the instance and volume which we are being asked to
setup.
Flags - Flags describing the type of attachment this is.
VolumeDeviceType - The DEVICE_TYPE for the volume to which this instance
will attach.
VolumeFileSystemType - The file system formatted on this volume.
Return Value:
FLT_NOTIFY_STATUS_ATTACH - we wish to attach to the volume
FLT_NOTIFY_STATUS_DO_NOT_ATTACH - no, thank you
--*/
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(Flags);
UNREFERENCED_PARAMETER(VolumeFilesystemType);
PAGED_CODE();
ASSERT(FltObjects->Filter == ScannerData.Filter);
//
// Don't attach to network volumes.
//
if (VolumeDeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) {
return STATUS_FLT_DO_NOT_ATTACH;
}
return STATUS_SUCCESS;
}
NTSTATUS
ScannerQueryTeardown(
__in PCFLT_RELATED_OBJECTS FltObjects,
__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
/*++
Routine Description:
This is the instance detach routine for the filter. This
routine is called by filter manager when a user initiates a manual instance
detach. This is a 'query' routine: if the filter does not want to support
manual detach, it can return a failure status
Arguments:
FltObjects - Describes the instance and volume for which we are receiving
this query teardown request.
Flags - Unused
Return Value:
STATUS_SUCCESS - we allow instance detach to happen
--*/
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(Flags);
return STATUS_SUCCESS;
}
FLT_PREOP_CALLBACK_STATUS
ScannerPreCreate(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID* CompletionContext
)
/*++
Routine Description:
Pre create callback. We need to remember whether this file has been
opened for write access. If it has, we'll want to rescan it in cleanup.
This scheme results in extra scans in at least two cases:
-- if the create fails (perhaps for access denied)
-- the file is opened for write access but never actually written to
The assumption is that writes are more common than creates, and checking
or setting the context in the write path would be less efficient than
taking a good guess before the create.
Arguments:
Data - The structure which describes the operation parameters.
FltObject - The structure which describes the objects affected by this
operation.
CompletionContext - Output parameter which can be used to pass a context
from this pre-create callback to the post-create callback.
Return Value:
FLT_PREOP_SUCCESS_WITH_CALLBACK - If this is not our user-mode process.
FLT_PREOP_SUCCESS_NO_CALLBACK - All other threads.
--*/
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
PAGED_CODE(); ///Minifilter也遵循Irql的准则,这个宏会发现当前的Irql级别是否大于APC_LEVEL,如果是,就蓝屏
//
// See if this create is being done by our user process.
//
if (IoThreadToProcess(Data->Thread) == ScannerData.UserProcess) { ///当前线程对应的进程等于ScannerData.UserProcess,表示操作是自己的进程下发的请求
DbgPrint("!!! scanner.sys -- allowing create for trusted process \n");
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
BOOLEAN
ScannerpCheckExtension(
__in PUNICODE_STRING Extension
)
/*++
Routine Description:
Checks if this file name extension is something we are interested in
Arguments
Extension - Pointer to the file name extension
Return Value
TRUE - Yes we are interested
FALSE - No
--*/
{
const UNICODE_STRING* ext;
if (Extension->Length == 0) {
return FALSE;
}
//
// Check if it matches any one of our static extension list
//
ext = ScannerExtensionsToScan; ///扩展名的数组
while (ext->Buffer != NULL) {
if (RtlCompareUnicodeString(Extension, ext, TRUE) == 0) {
//
// A match. We are interested in this file
//
return TRUE;
}
ext++;
}
return FALSE;
}
FLT_POSTOP_CALLBACK_STATUS
ScannerPostCreate(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__in_opt PVOID CompletionContext,
__in FLT_POST_OPERATION_FLAGS Flags
)
/*++
Routine Description:
Post create callback. We can't scan the file until after the create has
gone to the filesystem, since otherwise the filesystem wouldn't be ready
to read the file for us.
Arguments:
Data - The structure which describes the operation parameters.
FltObject - The structure which describes the objects affected by this
operation.
CompletionContext - The operation context passed fron the pre-create
callback.
Flags - Flags to say why we are getting this post-operation callback.
Return Value:
FLT_POSTOP_FINISHED_PROCESSING - ok to open the file or we wish to deny
access to this file, hence undo the open
--*/
{
PSCANNER_STREAM_HANDLE_CONTEXT scannerContext;
FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
PFLT_FILE_NAME_INFORMATION nameInfo;
NTSTATUS status;
BOOLEAN safeToOpen, scanFile;
UNREFERENCED_PARAMETER(CompletionContext);
UNREFERENCED_PARAMETER(Flags);
//
// If this create was failing anyway, don't bother scanning now.
//
if (!NT_SUCCESS(Data->IoStatus.Status) || ///这次操作失败
(STATUS_REPARSE == Data->IoStatus.Status)) { ///重定向操作
return FLT_POSTOP_FINISHED_PROCESSING; ///不监控了
}
//
// Check if we are interested in this file.
//
status = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED |
FLT_FILE_NAME_QUERY_DEFAULT,
&nameInfo);
if (!NT_SUCCESS(status)) {
return FLT_POSTOP_FINISHED_PROCESSING;
}
FltParseFileNameInformation(nameInfo); ///拿文件路径
//
// Check if the extension matches the list of extensions we are interested in
//
scanFile = ScannerpCheckExtension(&nameInfo->Extension);
//
// Release file name info, we're done with it
//
FltReleaseFileNameInformation(nameInfo); ///存放文件的内存是用引用用计数来管理的
if (!scanFile) { ///不是感兴趣的文件扩展名
//
// Not an extension we are interested in
//
return FLT_POSTOP_FINISHED_PROCESSING;
}
(VOID)ScannerpScanFileInUserMode(FltObjects->Instance,
FltObjects->FileObject, ///读一部分,1024字节发到R3中去,扫描病毒的特征码有特定的算法,不一定从文件头读起
&safeToOpen); ///存放扫描的结果
if (!safeToOpen) {
//
// Ask the filter manager to undo the create.
//
DbgPrint("!!! scanner.sys -- foul language detected in postcreate !!!\n");
DbgPrint("!!! scanner.sys -- undoing create \n");
FltCancelFileOpen(FltObjects->Instance, FltObjects->FileObject);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
}
else if (FltObjects->FileObject->WriteAccess) { ///后续需要判断写关闭,所以这里要判断是否是以写的方式打开的,把写操作记录下来,如果是则后续还需要重新扫描文件
//
//
// The create has requested write access, mark to rescan the file.
// Allocate the context.
//
status = FltAllocateContext(ScannerData.Filter,
FLT_STREAMHANDLE_CONTEXT,///流句柄上下文保存在file_object
sizeof(SCANNER_STREAM_HANDLE_CONTEXT), ///就只有一个布尔值,用来标记此次是以写方式打开,后续需要些关闭
PagedPool,
&scannerContext);
if (NT_SUCCESS(status)) {
//
// Set the handle context.
//
scannerContext->RescanRequired = TRUE;
(VOID)FltSetStreamHandleContext(FltObjects->Instance, ///流句柄上下文绑定到Instance
FltObjects->FileObject,
FLT_SET_CONTEXT_REPLACE_IF_EXISTS,
scannerContext,
NULL);
//
// Normally we would check the results of FltSetStreamHandleContext
// for a variety of error cases. However, The only error status
// that could be returned, in this case, would tell us that
// contexts are not supported. Even if we got this error,
// we just want to release the context now and that will free
// this memory if it was not successfully set.
//
//
// Release our reference on the context (the set adds a reference)
//
FltReleaseContext(scannerContext); ///前面set会增加引用计数,所以这里需要release一下
}
}
return returnStatus;
}
FLT_PREOP_CALLBACK_STATUS
ScannerPreCleanup(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID* CompletionContext
)
/*++
Routine Description:
Pre cleanup callback. If this file was opened for write access, we want
to rescan it now.
Arguments:
Data - The structure which describes the operation parameters.
FltObject - The structure which describes the objects affected by this
operation.
CompletionContext - Output parameter which can be used to pass a context
from this pre-cleanup callback to the post-cleanup callback.
Return Value:
Always FLT_PREOP_SUCCESS_NO_CALLBACK.
--*/
{
NTSTATUS status;
PSCANNER_STREAM_HANDLE_CONTEXT context;
BOOLEAN safe;
UNREFERENCED_PARAMETER(Data);
UNREFERENCED_PARAMETER(CompletionContext);
status = FltGetStreamHandleContext(FltObjects->Instance, ///拿上下文
FltObjects->FileObject,
&context);
if (NT_SUCCESS(status)) {
if (context->RescanRequired) {///写关闭
(VOID)ScannerpScanFileInUserMode(FltObjects->Instance,
FltObjects->FileObject,
&safe);
if (!safe) {
DbgPrint("!!! scanner.sys -- foul language detected in precleanup !!!\n"); ///这里只是简单打印警告,一般应该隔离该文件或者弹窗提醒用户比较好
}
}
FltReleaseContext(context);
}
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
FLT_PREOP_CALLBACK_STATUS
ScannerPreWrite(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID* CompletionContext
)
/*++
Routine Description:
Pre write callback. We want to scan what's being written now.
Arguments:
Data - The structure which describes the operation parameters.
FltObject - The structure which describes the objects affected by this
operation.
CompletionContext - Output parameter which can be used to pass a context
from this pre-write callback to the post-write callback.
Return Value:
Always FLT_PREOP_SUCCESS_NO_CALLBACK.
--*/
{
FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
NTSTATUS status;
PSCANNER_NOTIFICATION notification = NULL;
PSCANNER_STREAM_HANDLE_CONTEXT context = NULL;
ULONG replyLength;
BOOLEAN safe = TRUE;
PUCHAR buffer;
UNREFERENCED_PARAMETER(CompletionContext);
//
// If not client port just ignore this write.
//
if (ScannerData.ClientPort == NULL) {
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
status = FltGetStreamHandleContext(FltObjects->Instance, ///拿上下文,Q:写操作为什么也需要访问上下文,不是只有写关闭才需要访问?
FltObjects->FileObject,
&context);
if (!NT_SUCCESS(status)) {
//
// We are not interested in this file
//
return FLT_PREOP_SUCCESS_NO_CALLBACK; ///放行,A:只有在文件扩展名属于扩展数组指定的扩展名,才会为其创建上下文,所以在写操作,根据是否有上下文来判断文件是否是属于扩展数组指定的扩展名,即是否需要监控的扩展名
}
//
// Use try-finally to cleanup
//
try {
//
// Pass the contents of the buffer to user mode.
//
if (Data->Iopb->Parameters.Write.Length != 0) { ///写的字节数
//
// Get the users buffer address. If there is a MDL defined, use
// it. If not use the given buffer address.
//
if (Data->Iopb->Parameters.Write.MdlAddress != NULL) { ///direct io
buffer = MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.Write.MdlAddress, ///映射成内核态对应的虚拟地址
NormalPagePriority);
//
// If we have a MDL but could not get and address, we ran out
// of memory, report the correct error
//
if (buffer == NULL) {
Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
leave;
}
}
else {
//
// Use the users buffer
//
/// Callbacks对Irp进行了封装,在Minifilter管理器应该已经对Buffer进行了Probe,所以这里就可以直接使用buffer
buffer = Data->Iopb->Parameters.Write.WriteBuffer; ///不是direct io
}
//
// In a production-level filter, we would actually let user mode scan the file directly.
// Allocating & freeing huge amounts of non-paged pool like this is not very good for system perf.
// This is just a sample!
//
notification = ExAllocatePoolWithTag(NonPagedPool, ///分配一个结构体
sizeof(SCANNER_NOTIFICATION),
'nacS');
if (notification == NULL) { ///初始化结构体
Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
leave;
}
notification->BytesToScan = min(Data->Iopb->Parameters.Write.Length, SCANNER_READ_BUFFER_SIZE);
//
// The buffer can be a raw user buffer. Protect access to it
//
try {
RtlCopyMemory(¬ification->Contents,
buffer,
notification->BytesToScan);
} except(EXCEPTION_EXECUTE_HANDLER) {
//
// Error accessing buffer. Complete i/o with failure
//
Data->IoStatus.Status = GetExceptionCode();
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
leave;
}
//
// Send message to user mode to indicate it should scan the buffer.
// We don't have to synchronize between the send and close of the handle
// as FltSendMessage takes care of that.
//
replyLength = sizeof(SCANNER_REPLY);
status = FltSendMessage(ScannerData.Filter, ///发给R3
&ScannerData.ClientPort,
notification,
sizeof(SCANNER_NOTIFICATION),
notification,
&replyLength,
NULL);
if (STATUS_SUCCESS == status) {
safe = ((PSCANNER_REPLY)notification)->SafeToOpen; ///R3扫描的结果
}
else {
//
// Couldn't send message. This sample will let the i/o through.
//
DbgPrint("!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status);
}
}
if (!safe) {
//
// Block this write if not paging i/o (as a result of course, this scanner will not prevent memory mapped writes of contaminated
// strings to the file, but only regular writes). The effect of getting ERROR_ACCESS_DENIED for many apps to delete the file they
// are trying to write usually.
// To handle memory mapped writes - we should be scanning at close time (which is when we can really establish that the file object
// is not going to be used for any more writes)
//
DbgPrint("!!! scanner.sys -- foul language detected in write !!!\n");
if (!FlagOn(Data->Iopb->IrpFlags, IRP_PAGING_IO)) {
DbgPrint("!!! scanner.sys -- blocking the write !!!\n");
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
}
}
}
finally {
if (notification != NULL) {
ExFreePoolWithTag(notification, 'nacS');
}
if (context) {
FltReleaseContext(context);
}
}
return returnStatus;
}
//
// Local support routines.
//
/
NTSTATUS
ScannerpScanFileInUserMode(
__in PFLT_INSTANCE Instance,
__in PFILE_OBJECT FileObject,
__out PBOOLEAN SafeToOpen
)
/*++
Routine Description:
This routine is called to send a request up to user mode to scan a given
file and tell our caller whether it's safe to open this file.
Note that if the scan fails, we set SafeToOpen to TRUE. The scan may fail
because the service hasn't started, or perhaps because this create/cleanup
is for a directory, and there's no data to read & scan.
If we failed creates when the service isn't running, there'd be a
bootstrapping problem -- how would we ever load the .exe for the service?
Arguments:
Instance - Handle to the filter instance for the scanner on this volume.
FileObject - File to be scanned.
SafeToOpen - Set to FALSE if the file is scanned successfully and it contains
foul language.
Return Value:
The status of the operation, hopefully STATUS_SUCCESS. The common failure
status will probably be STATUS_INSUFFICIENT_RESOURCES.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PVOID buffer = NULL;
ULONG bytesRead;
PSCANNER_NOTIFICATION notification = NULL;
FLT_VOLUME_PROPERTIES volumeProps;
LARGE_INTEGER offset;
ULONG replyLength, length;
PFLT_VOLUME volume = NULL;
*SafeToOpen = TRUE;
//
// If not client port just return.
//
if (ScannerData.ClientPort == NULL) {
return STATUS_SUCCESS;
}
try {
//
// Obtain the volume object .
//
status = FltGetVolumeFromInstance(Instance, &volume); ///拿到卷设备对象
if (!NT_SUCCESS(status)) {
leave;
}
//
// Determine sector size. Noncached I/O can only be done at sector size offsets, and in lengths which are
// multiples of sector size. A more efficient way is to make this call once and remember the sector size in the
// instance setup routine and setup an instance context where we can cache it.
//
status = FltGetVolumeProperties(volume, ///获取卷设备对象的属性,扇区
&volumeProps,
sizeof(volumeProps),
&length);
//
// STATUS_BUFFER_OVERFLOW can be returned - however we only need the properties, not the names
// hence we only check for error status.
//
if (NT_ERROR(status)) {
leave;
}
length = max(SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize); ///扇区大小,传统扇区大小是512B,大扇区(4K)是为了突破bios+MBR最大只支持2T磁盘的限制
//
// Use non-buffered i/o, so allocate aligned pool
//
buffer = FltAllocatePoolAlignedWithTag(Instance,
NonPagedPool,
length,
'nacS');
if (NULL == buffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}
notification = ExAllocatePoolWithTag(NonPagedPool,
sizeof(SCANNER_NOTIFICATION), ///存放从文件中读出length长度的数据发给R3
'nacS');
if (NULL == notification) {
status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}
//
// Read the beginning of the file and pass the contents to user mode.
//
offset.QuadPart = bytesRead = 0;
status = FltReadFile(Instance,
FileObject,
&offset,
length,
buffer,
FLTFL_IO_OPERATION_NON_CACHED |
FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
&bytesRead,
NULL,
NULL);
if (NT_SUCCESS(status) && (0 != bytesRead)) {
notification->BytesToScan = (ULONG)bytesRead;
//
// Copy only as much as the buffer can hold
//
RtlCopyMemory(¬ification->Contents,
buffer,
min(notification->BytesToScan, SCANNER_READ_BUFFER_SIZE));
replyLength = sizeof(SCANNER_REPLY);
status = FltSendMessage(ScannerData.Filter,
&ScannerData.ClientPort,
notification,///request
sizeof(SCANNER_NOTIFICATION),
notification,///reply,和equest用同一块内存,由于不会同时对同一块内存进行修改,所以这里没有问题
&replyLength,
NULL); ///无限等待
if (STATUS_SUCCESS == status) {
*SafeToOpen = ((PSCANNER_REPLY)notification)->SafeToOpen;
}
else {
//
// Couldn't send message
//
DbgPrint("!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status);
}
}
}
finally {
if (NULL != buffer) {
FltFreePoolAlignedWithTag(Instance, buffer, 'nacS');
}
if (NULL != notification) {
ExFreePoolWithTag(notification, 'nacS');
}
if (NULL != volume) {
FltObjectDereference(volume);
}
}
return status;
}
/*++
Copyright (c) 1999-2002 Microsoft Corporation
Module Name:
scanUser.c
Abstract:
This file contains the implementation for the main function of the
user application piece of scanner. This function is responsible for
actually scanning file contents.
Environment:
User mode
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "scanuk.h"
#include "scanuser.h"
#include
#include //C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um
#pragma comment(lib,"fltLib.lib") //C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86
//
// Default and Maximum number of threads.
//
#define SCANNER_DEFAULT_REQUEST_COUNT 5
#define SCANNER_DEFAULT_THREAD_COUNT 2
#define SCANNER_MAX_THREAD_COUNT 64
UCHAR FoulString[] = "foul";
//
// Context passed to worker threads
//
typedef struct _SCANNER_THREAD_CONTEXT {
HANDLE Port;
HANDLE Completion;
} SCANNER_THREAD_CONTEXT, * PSCANNER_THREAD_CONTEXT;
VOID
Usage(
VOID
)
/*++
Routine Description
Prints usage
Arguments
None
Return Value
None
--*/
{
printf("Connects to the scanner filter and scans buffers \n");
printf("Usage: scanuser [requests per thread] [number of threads(1-64)]\n");
}
BOOL
ScanBuffer(
__in_bcount(BufferSize) PUCHAR Buffer,
__in ULONG BufferSize
)
/*++
Routine Description
Scans the supplied buffer for an instance of FoulString.
Note: Pattern matching algorithm used here is just for illustration purposes,
there are many better algorithms available for real world filters
Arguments
Buffer - Pointer to buffer
BufferSize - Size of passed in buffer
Return Value
TRUE - Found an occurrence of the appropriate FoulString
FALSE - Buffer is ok
--*/
{
PUCHAR p;
ULONG searchStringLength = sizeof(FoulString) - sizeof(UCHAR);
for (p = Buffer;
p <= (Buffer + BufferSize - searchStringLength);
p++) {
if (RtlEqualMemory(p, FoulString, searchStringLength)) {
printf("Found a string\n");
//
// Once we find our search string, we're not interested in seeing
// whether it appears again.
//
return TRUE;
}
}
return FALSE;
}
DWORD
ScannerWorker(
__in PSCANNER_THREAD_CONTEXT Context
)
/*++
Routine Description
This is a worker thread that
Arguments
Context - This thread context has a pointer to the port handle we use to send/receive messages,
and a completion port handle that was already associated with the comm. port by the caller
Return Value
HRESULT indicating the status of thread exit.
--*/
{
PSCANNER_NOTIFICATION notification;
SCANNER_REPLY_MESSAGE replyMessage;
PSCANNER_MESSAGE message;
LPOVERLAPPED pOvlp;
BOOL result;
DWORD outSize;
HRESULT hr;
ULONG_PTR key;
#pragma warning(push)
#pragma warning(disable:4127) // conditional expression is constant
while (TRUE) {
#pragma warning(pop)
//
// Poll for messages from the filter component to scan.
//
result = GetQueuedCompletionStatus(Context->Completion, &outSize, &key, &pOvlp, INFINITE);
//
// Obtain the message: note that the message we sent down via FltGetMessage() may NOT be
// the one dequeued off the completion queue: this is solely because there are multiple
// threads per single port handle. Any of the FilterGetMessage() issued messages can be
// completed in random order - and we will just dequeue a random one.
//
message = CONTAINING_RECORD(pOvlp, SCANNER_MESSAGE, Ovlp);
if (!result) {
//
// An error occured.
//
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
printf("Received message, size %d\n", pOvlp->InternalHigh);
notification = &message->Notification;
assert(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE); ///待扫描的字节数
__analysis_assume(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE); ///待扫描的数据
///这个地方,可以修改成弹窗的代码:result=PopupWindow(notification);
result = ScanBuffer(notification->Contents, notification->BytesToScan);
replyMessage.ReplyHeader.Status = 0;
replyMessage.ReplyHeader.MessageId = message->MessageHeader.MessageId; ///Mifilfter框架生成的
//
// Need to invert the boolean -- result is true if found
// foul language, in which case SafeToOpen should be set to false.
//
replyMessage.Reply.SafeToOpen = !result; ///扫描结果
printf("Replying message, SafeToOpen: %d\n", replyMessage.Reply.SafeToOpen);
hr = FilterReplyMessage(Context->Port,
(PFILTER_REPLY_HEADER)&replyMessage,
sizeof(replyMessage));
//这个时候,执行完上面函数后,驱动中FltSendMessage()返回
if (SUCCEEDED(hr)) {
printf("Replied message\n");
}
else {
printf("Scanner: Error replying message. Error = 0x%X\n", hr);
break;
}
memset(&message->Ovlp, 0, sizeof(OVERLAPPED));
hr = FilterGetMessage(Context->Port, ///异步读,开始下一轮
&message->MessageHeader,
FIELD_OFFSET(SCANNER_MESSAGE, Ovlp),
&message->Ovlp);
if (hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING)) {
break;
}
}
if (!SUCCEEDED(hr)) {
if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)) {
//
// Scanner port disconncted.
//
printf("Scanner: Port is disconnected, probably due to scanner filter unloading.\n");
}
else {
printf("Scanner: Unknown error occured. Error = 0x%X\n", hr);
}
}
free(message);
return hr;
}
//scanuser.exe reqno(5) threadno(2,MAX64)
//scanuser.exe 5 10 5表示程序一次性可以处理5个请求,10表示允许创建10个线程
//scanuser.exe
int _cdecl
main(
__in int argc,
__in_ecount(argc) char* argv[]
)
{
DWORD requestCount = SCANNER_DEFAULT_REQUEST_COUNT;
DWORD threadCount = SCANNER_DEFAULT_THREAD_COUNT;
HANDLE threads[SCANNER_MAX_THREAD_COUNT];
SCANNER_THREAD_CONTEXT context;
HANDLE port, completion;
PSCANNER_MESSAGE msg;
DWORD threadId;
HRESULT hr;
DWORD i, j;
//
// Check how many threads and per thread requests are desired.
//
if (argc > 1) {
requestCount = atoi(argv[1]); ///一次最多处理请求的个数
if (requestCount <= 0) {
Usage();
return 1;
}
if (argc > 2) {
threadCount = atoi(argv[2]); ///允许创建线程的个数
}
if (threadCount <= 0 || threadCount > 64) {
Usage();
return 1;
}
}
//
// Open a commuication channel to the filter
//
printf("Scanner: Connecting to the filter ...\n");
hr = FilterConnectCommunicationPort(ScannerPortName, ///连接R0
0,
NULL,
0,
NULL,
&port);
if (IS_ERROR(hr)) {
printf("ERROR: Connecting to filter port: 0x%08x\n", hr);
return 2;
}
//
// Create a completion port to associate with this handle.
//
completion = CreateIoCompletionPort(port, ///创建完成端口
NULL,
0,
threadCount);
if (completion == NULL) {
printf("ERROR: Creating completion port: %d\n", GetLastError());
CloseHandle(port);
return 3;
}
printf("Scanner: Port = 0x%p Completion = 0x%p\n", port, completion);
context.Port = port;
context.Completion = completion;
//
// Create specified number of threads.
//
for (i = 0; i < threadCount; i++) { ///创建线程
threads[i] = CreateThread(NULL,
0,
ScannerWorker,
&context,
0,
&threadId);
if (threads[i] == NULL) {
//
// Couldn't create thread.
//
hr = GetLastError();
printf("ERROR: Couldn't create thread: %d\n", hr);
goto main_cleanup;
}
for (j = 0; j < requestCount; j++) { ///为每个线程分配requestCount个结构体
//
// Allocate the message.
//
#pragma prefast(suppress:__WARNING_MEMORY_LEAK, "msg will not be leaked because it is freed in ScannerWorker")
msg = malloc(sizeof(SCANNER_MESSAGE));
if (msg == NULL) {
hr = ERROR_NOT_ENOUGH_MEMORY;
goto main_cleanup;
}
memset(&msg->Ovlp, 0, sizeof(OVERLAPPED));
//
// Request messages from the filter driver.
//
hr = FilterGetMessage(port, ///异步读
&msg->MessageHeader,
FIELD_OFFSET(SCANNER_MESSAGE, Ovlp),
&msg->Ovlp);
if (hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING)) {
free(msg);
goto main_cleanup;
}
}
}
hr = S_OK;
WaitForMultipleObjectsEx(i, threads, TRUE, INFINITE, FALSE);
main_cleanup:
printf("Scanner: All done. Result = 0x%08x\n", hr);
CloseHandle(port);
CloseHandle(completion);
return hr;
}
沙盒的根目录
:
\device\harddiskvolume1\doc\hi.txt
源路径
:
\device\harddiskvolume2\doc\hi.txt
内部路径
:
\device\harddiskvolume1\sandbox\harddiskvolume2\doc\hi.txt
删除标记
:
\device\harddiskvolume1\sandbox\harddiskvolume2\doc\hi.txt.del
Windows的I/O管理器提供了一个方便的方法来重定向一个文件对象。通常使用文件过滤驱动(在文件打开和文件创建的操作中)实现该方法。操作方法如下;
Device\HardDiskVolume0\Doc\1.txt
)可以释放掉原有的FileName.Buffer,同时用自己定义义的缓冲区(buffer,以NonPagedPool
方式申请)替换它STATUS_REPARSE
,然后设置Information字段为IO_REPARSE
。STATUS_REPARSE
IRP_MJ_CREATE
的请求(带上新的重定向的路径),完成了重定向。///重定向
NTSTATUS
SbRedirectFile(
IN PFLT_CALLBACK_DATA Data, ///Minifilter必须带的结构体
IN PCFLT_RELATED_OBJECTS FltObjects,
IN PUNICODE_STRING pUstrDstFileName ///重定向之后的路径
)
{
PFILE_OBJECT pFileObject;
pFileObject = Data->Iopb->TargetFileObject; ///1.拿到目标文件内核对象
if (pFileObject == NULL)
return STATUS_INVALID_PARAMETER;
if (pFileObject->FileName.Length > 0 && pFileObject->FileName.Buffer != NULL) ///2.把原来的Buffer释放掉
{
ExFreePool(pFileObject->FileName.Buffer);
pFileObject->FileName.Buffer = NULL;
}
pFileObject->FileName = *pUstrDstFileName; ///绝对路径,重新指向新的路径
pFileObject->RelatedFileObject = NULL; ///相对路径
Data->IoStatus.Status = STATUS_REPARSE; ///3.设置loStatus的status字段为`STATUS_REPARSE`,然后设置Information字段为`IO_REPARSE`.
Data->IoStatus.Information = IO_REPARSE;
FltSetCallbackDataDirty(Data); ///让IO管理器认识到已经把数据改了
return STATUS_REPARSE; ///5.返回`STATUS_REPARSE`,I/O管理器接收到该返回后,使会触发另一个文件打开操作,并发一个`IRP_MJ_CREATE`的请求(带上新的重定向的路径),完成了重定向.
}
insandbox
and outsandbox
filename).del
文件。如果有则按照是否来自insandbox的请求和是否改变文件进行处理。
来自外面
,则重定向到内部来自里面
,则删除该标志文件,并交给文件系统去创建只读操作
该文件,含有删除标志,访问失败存在
该文件,且请求来自sandbox,交给文件系统处理;请求来自外面,reparse
到里面。不改变
文件,但是sandbox里没有
这个文件并且请求来自
sandbox,则reparse
到外面;否则交给文件系统处理改变
文件, sandbox里没有
该文件,则准备路径;如果sandbox外面有
该必文件,则负责拷贝
。外面路径
,则转换为里面的路径
里面路径
,获得外面的路径
不存在
,直接删除里面的该文件存在
;则在里面建立一个删除标志,并删除d:\doc\
所有的文件d:\doc\1.txt 2.txt 3.txt
c:\sandbox\harddiskvolume2\doc\1.txt.del,2.txt,4.txt,5.txt
WriteFile不需要重定向
,读写之前需要把文件打开,得到句柄后进行读写,然后再把文件关闭,所以在写之前,打开已经重定向了,所以WriteFile不需要重定向。MAX_PATH
长度的文件路径,如果返回失败(说明有沙合在前面加了一个沙盒的根目录导致重定向的路径长度超过了MAX_PATH
,文件创建失败)NTSTATUS DriverEntry(
IN PDRIVER_OBJECTDriverObject,
IN PUNICODE_STRINGRegistryPath)
{
CmRegisterCallbackEx(
MyRegCallback, ///回调函数
&uAltitude, ///指定高度
driverObject,
NULL,
&g_RegCookie, ///回调的handle
NULL);
}
/// 回调函数
NTSTATUS MyRegCallback(
__in PVOID CallbackContext,
__in_opt PVOID Argument1,///REG_NOTIFY_CLASS,标识注册表的操作
__in_opt PVOID Argument2 ) ///KEY_INFORMATION,拿到相关的信息,比如文件路径
{
switch( (REG_NOTIlFY_CLASS)Argument1)
{
case RegNtPreDeleteKey :
return HOOK_PreNtDeleteKey(PREG_DELETE_KEY_INFORMATION)Argument2);
case RegNtPreRenameKey:
return HOoK_PreNtRenameKeyf(PREG_RENAME_KEY_INFORMATION)Argument2);
case RegNtPreCreateKeyEx: ///pre操作
return HOoK_PreNtCreatekeyEx((PREG_CREATE_KEY_INFORMATION)Argument2);
case RegNtPostCreatekeyEx://post操作
return HOOK_PostNtCreateKeyExt(PRGG_PoST_OPERATION_INFORMATION)Argument2);
}
}