一个文件系统过滤驱动的demo
因为没写过FSD过滤驱动,所以拿来练练手,没有什么技术含量。参考自Win内核安全与驱动开发。
先梳理一下大概的流程,就是怎么去绑定设备栈、怎么去过滤各种请求的。
首先肯定是要绑定设备栈的,来看下怎么绑定的设备栈。
先确定绑定的对象是什么,文件系统的设备是分两个部分的,分别是卷设备和控制设备。其中,卷设备是每有一个卷就会对应有一个卷设备,这么说可能不太清楚我画了一张图。
注意,这里的卷设备并不是磁盘的卷设备。而是文件系统生成的,用来对应每一个卷的卷设备。一定要区分开这两者。绑定这些个卷设备也不能够直接进行,因为这些由文件系统生成的卷设备是无名的设备,根本没法直接绑定。这里使用的办法是先绑定FSD的控制设备,当控制设备接收到新建卷设备的请求这时就是可以绑定到卷设备上了。但是,FSD的控制设备也不是直接就可以绑定上的,所以也要想办法去获取下,这里用的是注册文件系统变动回调函数的方法。IoRegisterFsRegistrationChange()函数就是用来注册FSD过滤驱动的,这个回调在文件系统发生变动时会被调用,比如说文件系统被挂载时。回调函数和其他的系统事件通知类似都是有固定格式的,函数原型如下。
1 VOID NotifyFunction(PDEVICE_OBJECT DeviceObject,BOOLEAN Type) 2 { 3 //其中DeviceObject就可以是FSD加载时的控制设备指针,Type参数表示是卸载还是加载 4 }
注册了这个回调函数后,在里面对DeviceObject进行一下判断看一下是不是自己想要的文件系统,因为一个电脑上有很多的文件系统。
小结一下,目前为止我们知道了要进行这么一个流程:
分三步才能实现最终绑定。而我们的目的只是为了去绑定FSD卷设备,因为这个卷设备才是真正去接受和处理读写请求的设备。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegPath) { UNICODE_STRING DeviceName; DEVICE_OBJECT DeviceObject; NTSTATUS Status; FilterDriver=DriverObject;//全局变量 RtlInitUnicodeString(&DeviceName,L"\\FileSystem\\Filters\\DemoDriver"); Status=IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_DISK_FILE_SYSTEM, FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject); if(!NT_STATUS(Status)) { //设备创建失败 return Status; } for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION) { DriverObject->MajorFunction[i]=CallNextIrpLocation; } //DriverObject->MajorFunction[] //当前设备绑定到文件系统的控制设备(CDO)上 //注册一个文件系统变动回调函数 Status=IoRegisterFsRegistrationChange(DriverObject,FsChangeCallBack); if(!NT_STATUS(Status)) { IoDeleteDevice(DeviceObject); return Status; } }
我这里把MajorFunctionp[]的分发函数一些给省略了,后面会写出来。再来看一下FsChangeCallBack也就文件系统变动回调函数的内容,这个函数的目的是要绑定FSD控制设备。
1 VOID FsChangeCallBack(PDEVICE_OBJECT DeviceObject,BOOLEAN Create) 2 { 3 NTSTATUS Status; 4 UNICODE_STRING CompareName; 5 UNICODE_STRING DriverName; 6 DEVICE_OBJECT FilterDevice; 7 RtlInitUnicodeString(&CompareName,L"\\FileSystem\\Fs_Rec"); 8 if(Create) 9 { 10 //附加当前过滤驱动到CDO上去 11 //附加前要注意两点 12 //1.是不是自己想要的CDO类型 13 //2.是不是文件识别器 14 if(IS_WANTED_DEVICE_TYPE(DeviceObject->DriverObject->type)) 15 { 16 // 17 GetDriverNameFromDeviceObject(&DriverName,DeviceObject); 18 if(!RtlCompareUnicodeString(&DriverName,&CompareName)) 19 { 20 Status=IoCreateDevice(FilterDriver, 21 sizeof(DEVICE_EXTENSION), 22 NULL, 23 DeviceObject->DeviceType, 24 0, 25 FALSE, 26 &FilterDevice); 27 if(!NT_SUCCESS(Status)) 28 { 29 return Status; 30 } 31 FilterDevice->Flags=DeviceObject->Flags; 32 Status=(PVOID)IoAttachDeviceToDeviceStack(FilterDevice,DeviceObject); 33 if(!Status) 34 { 35 IoDeleteDevice(FilterDevice); 36 return Status; 37 } 38 DeviceObject->DeviceExtension->NextDevice=(PDRIVER_OBJECT)Status; 39 40 } 41 42 } 43 } 44 else 45 { 46 return 0; 47 } 48 return 0; 49 }
做了简单的判断:
- 判断是挂载文件系统的通知
- 判断是想要的文件系统类型
- 判断不是文件识别器
这里FSD的控制设备就被绑定上了,然后再去过滤FSD控制设备识别器接受的卷挂载请求。过滤请求这里有一点要注意的,因为当请求发下来时卷设备还没真正创建,所以不能去绑定,要等Irp返回后才能去绑定。这一点跟处理过滤驱动过滤读请求时的处理是很像的,因为读请求下发下来时,还没有正在的读动作,只有等到Irp返回后才能进行过滤。具体的做法就是用完成函数,但是毕竟特殊的一点是书上是用完成函数去激活事件然后在分发例程中等待事件来进行的处理,等待事件后进行绑定。书中的解释是完成函数处于DPC的IRQL,其实我觉得放在完成函数里应该不会有问题吧,又使用了完成函数又使用了等待事件感觉好重复。这种写法以前好像没写过。
1 NTSTATUS FilterAddToMount(PDEVICE_OBJECT DeviceObject,PIRP Irp) 2 { 3 //从DeviceObject中获取VPB,再从VPB中获取卷的信息 4 PIO_STACK_LOCATION irpSP; 5 PDEVICE_OBJECT RealDevice; 6 PDEVICE_OBJECT FilterDevice; 7 KEVENT NotifyEvent; 8 NTSTATUS Status; 9 PVPB vpb; 10 irpSP=IoGetCurrentIrpStackLocation(Irp); 11 RealDevice=irpSP->Parameters.MountVolume.Vpb->RealDevice; 12 IoCreateDevice(FilterDriver, 13 sizeof(DEVICE_EXTENSION), 14 NULL, 15 DeviceObject->DeviceType, 16 0, 17 FALSE, 18 &FilterDevice); 19 KeInitializeEvent(&NotifyEvent, 20 NotificationEvent, 21 FALSE); 22 IoCopyCurrentIrpStackLocationToNext(Irp); 23 IoSetCompletionRoutine(Irp, 24 SetEventRoutine, 25 &NotifyEvent, 26 TRUE, 27 TRUE, 28 TRUE); 29 IoCallDriver(Device,Irp); 30 31 KeWaitForSingleObject(&NotifyEvent, 32 Executive, 33 KernelMode, 34 FALSE, 35 NULL); 36 if(NT_SUCCESS(irpSP->IoStatus.Status)) 37 { 38 vpb=RealDevice->Vpb; 39 Status=IoAttachDeviceToDeviceStack(vpb->DeviceObject,FilterDevice); 40 } 41 IoCompleteRequest(Irp,IO_NO_INCREMENT); 42 return STATUS_SUCCESS; 43 44 45 } 46 47 NTSTATUS SetEventRoutine(PDEVICE_OBJECT DeviceObject, 48 PIRP Irp, 49 PVOID Context) 50 { 51 KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT,FALSE); 52 return STATUS_MORE_PROCESSING_REQUIRED; 53 54 }
到这里已经绑定了卷设备,这时只要设置好分发函数就可以完成过滤了。
接下来要说的就是具体的针对每种信息的处理了,要分类来说了。
0x0 过滤文件的打开
文件打开请求的主功能号是IRP_MJ_CREATE
首先,在Irp栈中可以获得文件对象指针。FileObject=irpsp->FileObject;目录和文件都是用文件对象表示的,直接是看不出来两者的分别的。irpsp->Parameters.Create的结构如下
struct{ PIO_SECURITY_CONTEXT SecurityContext; ULONG Options; USHORT FileAttributes; USHORT ShareAccess; ULONG EaLength; }
注意,打开请求是一种尝试性动作,就是说打开只有当返回成功时才有过滤的意义,获取结果要用完成函数才行。
0x1 过滤文件的删除
文件删除是分三步请求的,所以不是过滤一次就行的。删除的三步如图
打开文件的请求上面已经说过了。设置删除标志是主功能号为IRP_MJ_SET_INFORMATION的设置请求中的一种,其中irpsp->Parameters.SetFile.FileInformationClass为FileDispositionInformation。然后irp->AssociatedIrp.SystemBuffer指向如下结构
typdef struct _FILE_DISPOSITION_INFORMATION{ BOOLEAN DeleteFile; } FILE_DISPOSITION_INFORMATION;
0x2 过滤文件的路径
我们使用文件系统过滤驱动很多时候都是为了根据路径来进行操作,要不就使用磁盘过滤驱动就可以了完全没必要使用FSD过滤驱动。
(未完待续)