首页
论坛
课程
招聘
[原创]文件过滤驱动sfilter学习笔记
2011-5-15 17:55 20691

[原创]文件过滤驱动sfilter学习笔记

2011-5-15 17:55
20691
最近几天学习了下文件过滤驱动, 这方面的资料确实很少, 国内也就是楚狂人教程比较详细.
这篇文章是我这几天学习的总结, 有些内容楚狂人教程中没有提到, 特此贴出来供大家一起学习,
毕竟是初学文件过滤驱动, 错误之处难免, 还请多多见谅.

ps:1.第一次写文章, 如果斑竹大大觉得小弟的文章对大家有点帮助, 那么, 你懂的, 嘿嘿.
    2.文档中的代码可以在我的另一个帖子中下载到.http://bbs.pediy.com/showthread.php?p=958810

下面进入正题
1.DriverEntry例程
(1)创建过滤驱动的控制设备, 以后我们的IO控制码就是发到这个设备上面

//这里的设备名与普通设备有所不同.
//当然, 最简单的可以直接写成 L"\\Device\\Filemontor";(FileMon中是这么写的, 调试过可行)
RtlInitUnicodeString( &nameString, L"\\FileSystem\\Filters\\FileMonitor" );
status = IoCreateDevice( DriverObject,
                                                 0,                     //has no device extension  
                                                                                                //这是与其他Attach到别的设备上的设备的不同之处
                                                 &nameString,
                                                 FILE_DEVICE_DISK_FILE_SYSTEM,
                                                 FILE_DEVICE_SECURE_OPEN,
                                                 FALSE,
                                                 &gSFilterControlDeviceObject );

if (status == STATUS_OBJECT_PATH_NOT_FOUND) {

        RtlInitUnicodeString( &nameString, L"\\FileSystem\\FileMonitor" );
        status = IoCreateDevice( DriverObject,
                                                         0,                     
                                                         &nameString,
                                                         FILE_DEVICE_DISK_FILE_SYSTEM,
                                                         FILE_DEVICE_SECURE_OPEN,
                                                         FALSE,
                                                         &gSFilterControlDeviceObject );
}

//创建符号链接
RtlInitUnicodeString(&syblnkString, L"\\DosDevices\\FileMonitor");
status = IoCreateSymbolicLink( &syblnkString, &nameString );

if (!NT_SUCCESS(status)) {

        IoDeleteSymbolicLink( &syblnkString );
        status = IoCreateSymbolicLink( &syblnkString, &nameString );
       
        if (!NT_SUCCESS(status)) {

                KdPrint(("创建符号链接失败~\n"));
                IoDeleteDevice(gSFilterControlDeviceObject);
                return status;
        }
}

(2)设置例程
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {

        DriverObject->MajorFunction[i] = SfDispatch;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate;

DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;

(3)调用IoRegisterFsRegistrationChange函数来通知我们文件系统的加载和卷的mount.

2.SfCreate 例程
(1)sfilter的原版中是这么写的
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

        Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return STATUS_INVALID_DEVICE_REQUEST;
}

这样写的后果是我们用CreateFile函数在R3下打开此控制设备符号链接的时候失败
我刚开始学习文件过滤驱动的时候对此不是很了解, CreateFile老是失败,
起初还以为是符号链接名写错了, 后来参看了FileMon的代码才反应过来

于是修改如下:
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
               
        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = FILE_OPENED;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return STATUS_SUCCESS;
}

(2)根据不同软件的需要, 编写此函数的接下来部分
[1]比如我们要阻止病毒在Windows目录下创建文件, 那么我们就要在此文件还没创建的时候得到此文件的全路径.
要在这个时候得到文件的路径, 楚狂人也说了有点麻烦. 下面提供一个函数给大家, 用于在文件创建前得到路径.
BOOLEAN MzfGetFileFullPathPreCreate(PFILE_OBJECT pFile, PUNICODE_STRING path )
{
         NTSTATUS status;
         POBJECT_NAME_INFORMATION pObjName = NULL;
         WCHAR buf[256] = {0};
         void *obj_ptr = NULL;
         ULONG ulRet = 0;
         BOOLEAN bSplit = FALSE;

         if (pFile == NULL) return FALSE;
         if (pFile->FileName.Buffer == NULL) return FALSE;

         pObjName = (POBJECT_NAME_INFORMATION)buf;

         if (pFile->RelatedFileObject != NULL)
                        obj_ptr = (void *)pFile->RelatedFileObject;
         else
                        obj_ptr = (void *)pFile->DeviceObject;

         status = ObQueryNameString(obj_ptr, pObjName, 256*sizeof(WCHAR), &ulRet);
         if (status == STATUS_INFO_LENGTH_MISMATCH)
         {
                  pObjName = (POBJECT_NAME_INFORMATION)ExAllocatePool(NonPagedPool, ulRet);

                  if (pObjName == NULL)  return FALSE;

                  RtlZeroMemory(pObjName, ulRet);

                  status = ObQueryNameString(obj_ptr, pObjName, ulRet, &ulRet);
                  if (!NT_SUCCESS(status)) return FALSE;
         }

//拼接的时候, 判断是否需要加 '\\'
if (pFile->FileName.Length > 2 &&
  pFile->FileName.Buffer[0] != L'\\' &&
  pObjName->Name.Buffer[pObjName->Name.Length/sizeof(WCHAR) -1] != L'\\')
                bSplit = TRUE;

ulRet = pObjName->Name.Length + pFile->FileName.Length;

if (path->MaximumLength < ulRet)  return FALSE;

RtlCopyUnicodeString(path, &pObjName->Name);
if (bSplit)
         RtlAppendUnicodeToString(path, L"\\");

RtlAppendUnicodeStringToString(path, &pFile->FileName);

if ((void*)pObjName != (void*)buf)
                ExFreePool(pObjName);

return TRUE;
}

至此, 得到了文件的路径以后, 我们就可以做出判断了, 如果要阻止文件创建,
那么直接用IoCompleteRequest函数结束此IRP即可, 否则下发
IoSkipCurrentIrpStackLocation( Irp );
return IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );

[2]要是像FileMon那样只是记录系统中创建了哪些文件的话, 我们可以设置此函数的完成例程.
然后等文件创建完成了之后, 只要调用 IoQueryFileDosDeviceName 函数即可知道文件的全路径了.

设置完成例程如下:
{
        KEVENT waitEvent;
        KeInitializeEvent( &waitEvent, NotificationEvent, FALSE );

        IoCopyCurrentIrpStackLocationToNext( Irp );
        IoSetCompletionRoutine(Irp, SfCreateCompletion, &waitEvent, TRUE, TRUE, TRUE );
        status = IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );

        if (STATUS_PENDING == status) {

                NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
                ASSERT(STATUS_SUCCESS == localStatus);
        }
        //此处文件已经创建完成了, 我们可以调用IoQueryFileDosDeviceName函数得到文件的全路径

        //最后结束此IRP
        status = Irp->IoStatus.Status;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return status;
}

3.SfDispatch例程
在此例程中要判断是不是我们的控制设备, 如果使我们的控制设备, 则要处理相应的IO控制码.
否则, 下发此IRP
NTSTATUS SfDispatch ( IN PDEVICE_OBJECT DeviceObject,  IN PIRP Irp )
{
        NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStack;
   
    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

        Irp->IoStatus.Information = 0;
        irpStack = IoGetCurrentIrpStackLocation( Irp );
               
        switch (irpStack->MajorFunction) {
                       
                case IRP_MJ_DEVICE_CONTROL:
                        // 此函数用来执行相应的控制码
                        status = SpyCommonDeviceIoControl( Irp->AssociatedIrp.SystemBuffer,
                                                   irpStack->Parameters.DeviceIoControl.InputBufferLength,
                                                   Irp->AssociatedIrp.SystemBuffer,
                                                   irpStack->Parameters.DeviceIoControl.OutputBufferLength,
                                                   irpStack->Parameters.DeviceIoControl.IoControlCode,
                                                   &Irp->IoStatus );
                        break;
                       
                case IRP_MJ_CLEANUP
                        status = STATUS_SUCCESS;
                        break;
                       
                default:
                        status = STATUS_INVALID_DEVICE_REQUEST;
        }
               
        Irp->IoStatus.Status = status;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return status;
    }
       
        //不是我们的控制设备则下发此IRP
    return SfPassThrough( DeviceObject, Irp );
}

IO控制码也可以在FASTIO例程的SfFastIoDeviceControl函数中处理,如下:
当然, 最简单的还是在FASTIO例程中返回FALSE. 这样, 系统便会调用我们上面的SfDispatch函数.
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
               
        SpyCommonDeviceIoControl( InputBuffer,
                        InputBufferLength,
                        OutputBuffer,
                        OutputBufferLength,
                        IoControlCode,
                        IoStatus );
               
        return TRUE;
}

[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~

收藏
点赞0
打赏
分享
最新回复 (18)
雪    币: 100
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xyzjanker 活跃值 2011-5-15 18:54
2
0
文件过滤感觉还好!!!!!!

NDIS协议驱动看得那个才叫头痛!!!!N多回调!!!!
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-15 21:33
3
0
NDIS还没学, 等到把文件过滤驱动看透了之后在来学下NDIS.
雪    币: 69
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2011-5-15 21:42
4
0
直接看楚狂人的教程都还一头雾水,楼主这个就不用说了
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-15 21:57
5
0
这个文章是对楚狂人教程的一点补充.

我的学习经验是, 你看完楚狂人的教程后, 在回过头去看sfilter代码, 看完之后再实践下写个小工具什么的, 最后再实践中遇到不懂的, 回过头来看楚狂人的教程记忆会很深刻, 也会加深理解.
雪    币: 107
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tydef 活跃值 2011-5-16 09:43
6
0
[1]比如我们要阻止病毒在Windows目录下创建文件, 那么我们就要在此文件还没创建的时候得到此文件的全路径.
要在这个时候得到文件的路径, 楚狂人也说了有点麻烦. 下面提供一个函数给大家, 用于在文件创建前得到路径.
CREATE不见得都是成功的 所以没创建时候获得路径有几率是失败的
所以楚狂人总是说在创建完成后获取
而且好像还有其他原因也不能在创建前获得。

楚狂人对于FASTIO 再教程里还是语焉不详 我现在的认识是FASTIO就是为了节省生成IRP 首先在缓存中寻找文件内容 再决定是否建立发送IRP
TOPLEVEL组件 过滤驱动中根本谈都没谈  这个知识点我也很疑惑
想搞好过滤驱动对于文件驱动还是要了解
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-16 11:57
7
0
[QUOTE=tydef;959269][1]比如我们要阻止病毒在Windows目录下创建文件, 那么我们就要在此文件还没创建的时候得到此文件的全路径.
要在这个时候得到文件的路径, 楚狂人也说了有点麻烦. 下面提供一个函数给大家, 用于在文件创建前得到路径.
CREATE不见得都是成功的 所以没创建时候获得路径有几率是失败的
所以...[/QUOTE]

Create确实有失败的情况, 我在MzfFileMonitor中就只过滤了生成成功的情况, 对于返回值不是STATUS_SUCCESS的情况, 直接舍弃了, FileMon是不管失败还是成功都显示在R3程序中.

但是, 要阻止文件创建就只能在Create之前. 虽然他在创建过程中有可能会失败~

FASTIO我的理解是为了提高文件系统的效率而设的, 因为在文件吞吐量大的情况话, 每个都要分配IRP, 填写IRP中的各个域,  释放IRP占用的内存等等操作, 会相当消耗时间.

所以在FASTIO返回FALSE的时候, 文件过滤驱动才会调用正常的IRP例程, 这点可以实验获得, 楚狂人的教程中也有提到.
雪    币: 107
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tydef 活跃值 2011-5-16 13:43
8
0
FASTIO节省IRP 也是缓冲命中的情况下 不然还是要回头构建IRP
真的要做文件过滤可行版本的话 我是建议看看深入解析文件系统

创建前获得路径名 总觉得有意外的错误
之所以有这种印象 是因为我在MINIFILTER创建前操作下使用函数获得路径名 直接返回解析失败
应该不单单是创建失败这么一个问题  可能还有别的问题 个人觉得最好不用
雪    币: 682
活跃值: 活跃值 (67)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
zyqqyz 活跃值 1 2011-5-16 14:19
9
0
弱弱的指出一点疑问,在完成例程中应该不能再调用IoCompleteRequest( Irp, IO_NO_INCREMENT )函数了吧!
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-16 14:36
10
0
这跟缓冲命中有什么关系, 我是在Create例程中处理, 那时候文件还没有被打开或者创建, 何来缓冲, 不懂, 求解释?

那如果我要在文件创建前弹窗给用户判断是否要创建文件呢? 有好方法吗? 跪求指点~
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-16 14:37
11
0
完成函数如果返回 STATUS_MORE_PROCESSING_REQUIRED 的话, 本层驱动还是要完成此IRP的.
雪    币: 682
活跃值: 活跃值 (67)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
zyqqyz 活跃值 1 2011-5-16 17:00
12
0
还是不太明白,完成例程是被某一层的驱动调用IoCompleteRequest函数后,然后逐层向上调用完成例程,然后结束IoCompleteRequest函数,并结束对IRP的处理,那怎么能在IoCompleteRequest函数内在调用自己,那不成了无穷递归了吗?求解答!
雪    币: 107
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tydef 活跃值 2011-5-17 08:24
13
0
FASTIO节省IRP 也是缓冲命中的情况下 不然还是要回头构建IRP
真的要做文件过滤可行版本的话 我是建议看看深入解析文件系统

这跟缓冲命中有什么关系, 我是在Create例程中处理, 那时候文件还没有被打开或者创建, 何来缓冲, 不懂, 求解释?

可能我扯远了  从你的笔记谈到楚狂人对FASTIO只提供了他同事的笔记 再说到FASTIO是节省IRP构造 就是因为缓冲命中  和CREATE创建啥的没什么关系
雪    币: 107
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tydef 活跃值 2011-5-17 08:30
14
0
IoCompleteRequest函数内在调用自己
分发函数和完成函数是两个函数 无所谓调用自己 而且现在也没说调用。
我的理解应该是
IoCompleteRequest返回STATUS_MORE_PROCESSING_REQUIRED  
是将IRP控制权再次交给派遣函数 是下发或者完成有派遣函数自己根据情况处理
http://hi.baidu.com/jonathan2004/blog/item/7fedf03b0ffc04f93b87ce23.html
这里是IRP和完成函数的几种处理情况 我开始就是记下来 以后慢慢理解
再实际应用中也够了
雪    币: 31
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
defddr 活跃值 2011-5-17 09:37
15
0
终于记起密码了 呵呵  关注一下文件驱动相关的帖子
雪    币: 122
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
butian 活跃值 2011-5-17 10:00
16
0
唉,偶要给力的学习啊!
雪    币: 1363
活跃值: 活跃值 (957)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-5-18 12:49
17
0
不是IoCompleteRequest返回STATUS_MORE_PROCESSING_REQUIRED ,
是完成函数返回STATUS_MORE_PROCESSING_REQUIRED.
雪    币: 372
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
GVU 活跃值 2011-10-13 10:47
18
0
楼主,我现在也在学习文件过滤驱动,我是根据微软自带的sfilter源码上修改的,在SfCreate中使用你的MzfGetFileFullPathPreCreate函数总是会蓝屏,代码如下:
NTSTATUS
SfCreate (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    // ... 这里省略原有的代码
	PFILE_OBJECT file_object;
	WCHAR buf[260];
	UNICODE_STRING full_path;
	
	// ... 这里省略原有的代码
	
	file_object = IoGetCurrentIrpStackLocation(Irp)->FileObject;
	RtlInitUnicodeString(&full_path, buf);

	// 获取完整路径
	if (MzfGetFileFullPathPreCreate(file_object, &full_path))
	{
		KdPrint(("SFilter!SfCreate: full path \"%wZ\" \n", &full_path));
	}
	// 获取完整路径结束

	/* 
			把前面的所有return都修改成把结果保存到status变量中
		保证会执行到这里
			如果去掉获取完整路径的代码,没有任何问题
		否则会蓝屏
			已经确定是调用MzfGetFileFullPathPreCreate蓝屏。
	*/

	return status;
}


我实在不明白为什么会蓝屏,只要调用ObQueryNameString都会蓝,请问ObQueryNameString要在哪些地方才能使用?或者使用前后要做什么工作?
雪    币: 1463
活跃值: 活跃值 (961)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 活跃值 2019-2-18 09:52
19
0
请教一下大佬,过滤驱动有哪些技术论坛可以交流?国外或者国内都可
游客
登录 | 注册 方可回帖
返回