Sfilter的实现原理
Sfilter的实现原理
简介
Windows 文件过滤驱动的原理主要基于操作系统的 I/O 管道,利用过滤驱动程序来监控、修改或控制文件系统的 I/O 操作。所有的 I/O 操作(如打开文件、读写文件、关闭文件等)都通过 IRP 来处理。当用户模式程序发出 I/O 请求时,操作系统生成相应的 IRP 并将其发送到适当的设备驱动程序。
回调函数 : 在 IRP 请求处理中,涉及的函数通常被称为 回调函数。这些回调函数是在驱动程序中定义的,用于处理特定类型的 IRP 请求。回调函数在特定的事件发生时被调用。这也就是为什么三环程序明明没有任何跳转就可以去执行注册的回调函数,就是发送了IRP请求,所以注册的回调函数会被执行
控制设备:DriverEntry中创建,接收自己客户端的IRP
过滤设备:绑定的时候创建,在设备栈上接收其他R3程序的IRP
驱动和设备的关系:
设备对象是驱动对象的具体实例:驱动对象定义驱动程序的属性和行为(例如处理各种IRP的函数),设备对象是驱动控制的具体设备,实际接收和处理IO请求。一个驱动对象可以管理多个设备对象,每个设备对象代表一个独立的硬件或虚拟设备。
创建过滤驱动代码
1 | // 定义 DEVICE_EXTENSION 结构,用于存储设备特定信息 |
注意的是要创建一下设备扩展
1 | typedef struct _DEVICE_EXTENSION |
然后在这里可以获取目标设备对象指针
1 | status = IoAttachDevice(filterDevice, &targetDevice, &deviceExtension->NextDevice); |
然后在过滤驱动中就可以把这个IRP往下分发,NextDevice
就来自DEVICE_EXTENSION
1 | // IRP分发例程 |
分发IRP函数
过滤驱动有一个很重要的点,就是判断是来自自己设备的IRP,还是来自其他客户端的IRP,如果是自己的IRP,就自己处理,一定不能往下分发IRP,如果是拦截来的IRP,就可以选择是否往下发
如何判断是来自自己客户端的还是其他客户端的IRP呢?直接将传进来的PDEVICE_OBJECT DeviceObject
和在DriverEntry
创建的设备对象做对比(做成一个全局变量),如果相同,就是自己客户端的IRP,如果不是,那么就是别人客户端发来的IRP,说明需要拦截处理,并传递给下一个IRP栈链的下一个设备
Sfliter框架监控移动存储设备原理
比较知名的文件过滤框架有Filemon,Slifter,Minifilter
其中Filemon和Sfliter虽然已经不是主流,但是仍然可以学习
Filemon只监控固定磁盘,对于移动硬盘,U盘无法感知
Sfilter可以监控到移动硬盘,U盘
那么Sfilter是如何监控到移动硬盘,U盘这类的呢?毕竟这些卷设备是后来在任何时间随时插入设备的,并不是在一开始就可以绑定过滤驱动
这里一共涉及到两次创建过滤驱动设备对象
设备对象是用来处理IRP请求的具体实例,而具体的IRP请求处理函数是写在驱动里面的
也就是说,两次创建的设备对象的IRP处理函数是共享的,但是并不冲突,因为第一次创建的设备对象只用到IRP_MJ_FILE_SYSTEM_CONTROL
,第二次创建的设备对象用使用其他的IRP请求函数
第一次创建过滤驱动设备对象:
第一次创建设备对象是为了获取到文件系统设备对象
首先注册一个 注册文件系统变更回调 IoRegisterFsRegistrationChange
, 以便在文件系统驱动(如 NTFS)加载时,过滤驱动能够收到通知。
一旦我们进入这个回调,我们可以从这个回调的参数VOID MyFsNotification(PDEVICE_OBJECT DeviceObject, BOOLEAN FsActive)
,DeviceObject
拿到对应的文件系统设备对象
此时,我们需要注册一个过滤驱动设备对象,用我们在DriverEntry
保存的驱动对象,注册过滤驱动设备对象,让这个过滤驱动设备对象使用我们自己的驱动注册的IRP例程处理,在这里是IRP_MJ_FILE_SYSTEM_CONTROL
,具体来说还要再细分是否是 IRP_MN_MOUNT_VOLUME
。
第二次创建过滤驱动设备对象:
值得注意的是,我们在IRP_MJ_FILE_SYSTEM_CONTROL
处理函数里面,我们第二次创建的过滤驱动设备对象将要附加到卷设备对象上,所以在IRP_MJ_FILE_SYSTEM_CONTROL
处理函数,我们的目标是获取到卷设备对象,但是一开始进去这个IRP处理拿到的DeviceObject
是还没初始化好的卷设备对象,我们需要注册一个完成例程函数,并将IRP下发到底层的驱动设备进行对卷设备的初始化,然后在完成例程函数里面,拿到初始化好的卷设备对象,拿到之后,才是正式第二次创建过滤驱动设备对象进行附加到卷设备对象
一些实现的细节
关于FastIO
Fast I/O 是一种绕过常规 I/O 请求路径的快速数据访问机制,它允许文件系统直接完成一些常用操作(如读取、写入等),而不经过驱动的 IRP 调度路径。对于文件系统过滤驱动来说,这样可能导致无法截获或处理所有的 I/O 操作。
Sfliter会禁用FastIO,要求全部数据都走IRP请求,确保监控到对于文件的全部操作
Skip和Copy的区别
关于IoCopyCurrentIrpStackLocationToNext
和IoSkipCurrentIrpStackLocation(Irp)
的区别
每一层驱动会有自己独立的 IRP 堆栈位置(称为 “IRP stack location”)。当一个过滤驱动拦截到 IRP 请求时,它实际上并不是“接管”了别人的 IRP,而是“共享”了这个 IRP。
当过滤驱动调用 IoSkipCurrentIrpStackLocation(Irp)
时,它跳过的是 当前堆栈位置 的作用,而 不是跳过下层驱动的堆栈位置。实际上,它只是告知系统当前驱动无需对堆栈位置做任何更改或检查,直接将 IRP 传递给下一层驱动即可。
当不需要设置完成例程函数的时候,调用IoSkipCurrentIrpStackLocation(Irp)
就够了
但是如果设置了完成例程函数,那么就需要调用IoCopyCurrentIrpStackLocationToNext
,将完成例程函数和当前IRP堆栈上下文保存在下一层驱动的IRP堆栈,记住,这里不是覆盖下一层的驱动IRP堆栈,而是把信息保存在下一层驱动IRP堆栈
完成例程本来就是设置在当前堆栈的下一层堆栈里,这相当于是一个规范,也可以用实际的IRP的返回来理解。在完成例程里,根据返回不同的状态值,IRP的控制流可能会发生相应的变化,这样,下层堆栈执行完成例程后,会将IRP的控制权交付给本层堆栈。从这个意义上讲,完成例程,只能放在下层堆栈,实际上,设计也是这样的。 2,拷贝当前堆栈的内容到下层堆栈,只是为了保证执行环境一样。
最下层的驱动如果设置了完成例程函数,它如何调用Iocopycurrentirpstacklocationtonext
?
最下层驱动实际上不需要也不应该调用IoCopyCurrentIrpStackLocationToNext
。这是因为在驱动程序的堆栈中,最下层驱动是直接与硬件交互的层级,它通常负责完成I/O请求,并且不需要将IRP传递给更下一层的驱动程序。
IoSkipCurrentIrpStackLocation(Irp)
:无设置完成例程
1 | NTSTATUS status; |
Iocopycurrentirpstacklocationtonext(Irp)
:设置了完成例程
1 | IoCopyCurrentIrpStackLocationToNext(Irp); |
还有配合事件对象的:
1 | // 完成例程函数 |
这里解释一下:
status = IoCallDriver(DeviceObject, Irp);
当调用这个代码的时候,有两种情况
如果下层驱动同步完成,即直接处理并返回成功状态(如 STATUS_SUCCESS
),那么 IoCallDriver
会立即返回,随后 MySynchronousIoRequest
函数继续执行接下来的代码。
如果下层驱动异步完成,即下层驱动返回 STATUS_PENDING
,IoCallDriver
也会立即返回 STATUS_PENDING
,然后 MySynchronousIoRequest
会调用 KeWaitForSingleObject
等待事件对象被信号化。
驱动和完成例程函数的返回状态
驱动需要返回状态,包括但不限于 STATUS_SUCCESS
STATUS_PENDING
STATUS_UNSUCCESSFUL
STATUS_ACCESS_DENIED
、STATUS_BUFFER_TOO_SMALL
、STATUS_INVALID_PARAMETER
完成例程函数也需要返回状态,一般就两种,STATUS_SUCCESS
, STATUS_MORE_PROCESSING_REQUIRED
STATUS_MORE_PROCESSING_REQUIRED
确保了 IRP
不会被系统自动完成,从而允许调用代码继续访问和操作该 IRP
。如果在完成例程中返回 STATUS_SUCCESS
,那么 IRP
会被系统标记为已完成,资源可能被释放,从而导致调用代码无法再安全地访问该 IRP
。
所以返回STATUS_MORE_PROCESSING_REQUIRED
意味着重新获得IRP的控制权,可以重修选择下发还是终结IRP