文件系统过滤驱动开发(一)—Win32底层开发小组
声明:本文无太多新意,只是介绍下学习经验,大神级人物(如总监大人)请略过,谢谢合作>_<
吐槽一下:学驱动算起来也是从上学期9月份开始吧,之前在家买了<Windows驱动开发技术详解>这本书,搭了个环境之后,其实也没碰很多,编了个经典的Hello,World!之后就无太多后续动作,暑假嘛,你们懂的,学习无压力.上学期断断续续算是把基础啃完了(其实也只是啃完-__-),就到了万恶的期末考试复习月.寒假开始着手过滤驱动这一块的学习,然后学期一开始就忙着这个SIG的项目.到现在的感觉就是,思路是有,但是有一些地方找不到切入点,光查资料就花很多时间.现在分享一下我的学习心得,不对之处,敬请指出.
我们小组主要想完成的东西是一个类似SandBoxIE的软件,就是沙盒,现在360和微软的浏览器当中都加入了沙盒的功能.当然我也没试用过,因为搜狗有教育网加速- -,我个人对沙盒的理解是对一些操作的重定向,如文件写入操作,注册表写入操作,当然更高级的话还要加入内存保护.既然有了对写入操作的重定向,那么对于读取操作也应该进行重定向,因为需要读取修改过的文件或者注册表值.简单来说,这个沙盒像是一个容器,而我们可以在这个容器里面运行其他程序,这个程序对我们操作系统做的修改会被重定向,结果是这些修改操作不会对系统有任何的影响,徐志摩同学很好的描述了这一过程:悄悄的我走了,正如我悄悄的来,我挥一挥衣袖,不带走一片云彩...再比如在浏览器当中加入沙盒功能,假设我们系统访问了一个被挂马的网站中了木马,那么当浏览器关闭的时候,木马执行的破坏操作都是无效的(当然还是有泄漏帐号密码的危险,这是另一个话题).
由于驱动开发涉及底层,所以各个版本的Windows都会有所不同,我们针对的操作系统是WindowsXP,对于Windows2K而言,下面有些说的实现方法可能无法实现(底层的支持问题).对于文件操作的拦截,可以通过文件系统过滤来实现.现在一般杀毒软件的核心都需要一个文件过滤驱动,这样才能实时监控用户的文件是否安全.关于磁盘过滤驱动和文件系统过滤驱动,磁盘过滤驱动比文件系统过滤驱动更为底层一点,直接过滤磁盘可以实现对硬盘操作的还原,这样也可以实现我们想要的功能,但是相对来说粒度太大,我们需要的针对特定程序的监控,区分文件目录而非无差别的任意磁盘读写操作.这一类的软件有影子系统,雨过天晴多点还原系统.
接下来进入正题,如何开发文件系统过滤驱动,可能你需要对驱动对象,设备对象和IRP派遣函数有所了解.对于文件系统过滤驱动的框架而言有两种选择,一种是利用Minifilter,这个是文件系统微过滤驱动,微软为Windows内核开发者开发了一个新的驱动,叫做过滤管理器,提供一些接口供开发用户使用,这样就屏蔽了底层细节,提高了用户开发的效率,同时也利于在不同系统间的移植和解决兼容性问题.在减少代码依赖性的同时,由于开发者不需要了解太多的底层细节,只能通过提供的接口进行开发,可能在实现某些特定功能的时候会实现不了.另一种就是传统型的文件过滤驱动,微软提供了Sfilter的例子,细节需要开发者自己实现.出于学习底层知识的目的,我们选用的是Sfilter的框架,虽然比较繁琐,但是能学的多一点.
过滤驱动的一般思路是绑定相关的设备对象,之后就能得到该设备的IRP,进行先手和后手的处理.在文件系统过滤驱动当中,我们会生成三种设备:1,过滤驱动自身的控制设备;2,文件系统控制设备的过滤设备;3,文件系统卷设备的过滤设备.对于过滤驱动自身的控制设备主要是用来与应用层进行交互,剩下两个是用来绑定文件系统的设备,像FAT32,NTFS这样的文件系统主要生成两类设备,一种是控制设备(CDO),一种是卷设备(VDO),我们需要绑定这两种设备.绑定文件系统的控制设备我们就可以得到类似卷挂载/解挂载等操作的通知从而进行相应的处理,而绑定卷设备就能过滤相关的文件操作.这里的卷设备是指文件系统的卷设备,卷设备有两类,一类是由卷管理器生成的,这类设备有名字,我们常见的C:,D:是这类卷设备的符号链接,其设备名是\Device\harddiskVolume1\,\Device\harddiskVolume2\,这些是由卷设备管理器生成的,绑定这些设备无法得到文件操作的.我们关心的文件系统卷设备是没有名字的,需要通过其他方式获得.
如何获取控制设备并绑定它,可以通过注册文件系统变动回调来实现,原型如下:
1: NTSTATUS
2: IoRegisterFsRegistrationChange(
3: IN PDRIVER_OBJECT DriverObject,
4: IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine
5: );
回调函数的原型如下:
1: VOID
2: (*PDRIVER_FS_NOTIFICATION) (
3: IN struct _DEVICE_OBJECT *DeviceObject,
4: IN BOOLEAN FsActive
5: );
通过注册回调函数,可以得到系统已经安装了的文件系统控制设备对象也就是*DeviceObject,FsActive是表示文件系统是激活还是卸载,当文件系统激活时我们绑定他,卸载则解绑定.文件系统的回调函数是用于通知文件系统的变更,在XP下,当注册文件系统回调之后系统会调用回调函数用以通知已经激活的文件系统,而在较早版本下面如Win2K+SP4之前的版本是不会,所以只能将文件过滤驱动做成静态加载的形式在系统启动早期加载.在回调函数中,我们根据FsActive判断是绑定还是解绑定:
1: if (FsActive)
2: {
3: LzsbfAttachToFileSystemDevice(pDeviceObject, &DeviceName);
4: }
5: else
6: {
7: LzsbfDetachFromFileSystemDevice(pDeviceObject);
8: }
需要注意的是,这个回调函数并不通知Raw文件系统,如果需要关注此类文件系统需要自己判断,我们只关心FAT32和NTFS文件系统,故无视之.
在回调函数当中,我们还需要加入对文件系统识别器的判断.Windows在加载文件系统驱动的时候并非一开始就加载完整驱动,IO管理器是利用识别器来识别新加入物理介质的文件系统,当识别成功则卸载掉识别器,加载真正的驱动.我们要绑定的是真正的文件系统驱动设备,对于部分的文件系统识别器是由驱动"\FileSystem\Fs_Rec"生成的,注意只是部分,我们对于这部分的设备通过判断其驱动对象(识别器是一个设备对象)的名字,如果是"\FileSystem\Fs_Rec"跳过不进行绑定,对于不是由Fs_Rec产生的识别器,我们在IRP的派遣函数MarjorFunctions[IRP_MJ_FILE_SYSTEM_CONTROL]当中进行处理,我们能够得到识别器卸载要求加载真正文件系统驱动的通知.
通过文件系统回调函数提供的文件系统控制设备我们就可以绑定文件系统的控制设备,之后根据控制设备对象得到文件系统驱动对象,进而可以枚举其相关的卷设备,然后进行一一绑定.具体的实现代码如下:
1: NTSTATUS
2: LzsbfEnumerateFileSystemVolumes(
3: IN PDEVICE_OBJECT pFSDeviceObject,
4: IN PUNICODE_STRING pFSName
5: )
6: {
7: PDEVICE_OBJECT pNewDeviceObject;
8: PLZSBFILTER_DEVICE_EXTENSION pNewDeviceExtension;
9: PDEVICE_OBJECT *DeviceList;
10: PDEVICE_OBJECT pStorageStackDeviceObject;
11: NTSTATUS status;
12: ULONG NumberOfDevice;
13: ULONG i;
14: BOOLEAN IsShadowCopyVolume;
15:
16: PAGED_CODE();
17:
18: status = IoEnumerateDeviceObjectList(pFSDeviceObject->DriverObject,
19: NULL,
20: 0,
21: &NumberOfDevice
22: );
23: if (!NT_SUCCESS(status))
24: {
25: ASSERT(STATUS_BUFFER_TOO_SMALL == status);
26: NumberOfDevice += 8;
27: DeviceList = ExAllocatePoolWithTag(NonPagedPool,
28: (NumberOfDevice * sizeof(PDEVICE_OBJECT)),
29: LZSB_POOL_TAG
30: );
31: if (NULL == DeviceList)
32: {
33: KdPrint(("Fail to allocate DeviceList\n"));
34: return STATUS_INSUFFICIENT_RESOURCES;
35: }
36:
37: status = IoEnumerateDeviceObjectList(pFSDeviceObject->DriverObject,
38: DeviceList,
39: (NumberOfDevice * sizeof(PDEVICE_OBJECT)),
40: &NumberOfDevice
41: );
42: if (!NT_SUCCESS(status))
43: {
44: KdPrint(("Fail to call IoEnumerateDeviceObjectList()\n"));
45: ExFreePool(DeviceList);
46: return status;
47: }
48:
49: for (i = 0; i < NumberOfDevice; i++)
50: {
51: pStorageStackDeviceObject = NULL;
52: __try
53: {
54: if ((DeviceList[i] == pFSDeviceObject) ||
55: (DeviceList[i]->DeviceType != pFSDeviceObject->DeviceType) ||
56: LzsbfIsAttachedToDevice(DeviceList[i], NULL))
57: {
58: KdPrint(("Not to attach this!\n"));
59: __leave;
60: }
61:
62: LzsbfGetBaseDeviceObjectName(DeviceList[i], pFSName);
63: if (pFSName->Length > 0)
64: {
65: KdPrint(("It has a name, do not attach!\n"));
66: __leave;
67: }
68:
69: status = IoGetDiskDeviceObject(DeviceList[i], &pStorageStackDeviceObject);
70: if (!NT_SUCCESS(status))
71: {
72: KdPrint(("Fail to get disk device object!\n"));
73: __leave;
74: }
75:
76: status = LzsbfIsShadowCopyVolume(pStorageStackDeviceObject, &IsShadowCopyVolume);
77: if (NT_SUCCESS(status) && IsShadowCopyVolume)
78: {
79: KdPrint(("It is shadow copy volume, do not attach!\n"));
80: __leave;
81: }
82:
83: status = IoCreateDevice(gLZSBFilterDriverObject,
84: sizeof(LZSBFILTER_DEVICE_EXTENSION),
85: NULL,
86: DeviceList[i]->DeviceType,
87: 0,
88: FALSE,
89: &pNewDeviceObject
90: );
91: if (!NT_SUCCESS(status))
92: {
93: KdPrint(("Fail to create new device!\n"));
94: __leave;
95: }
96:
97: pNewDeviceExtension = pNewDeviceObject->DeviceExtension;
98: pNewDeviceExtension->LZSBDeviceType = FILESYSTEM_VOLUME_DEVICE;
99: pNewDeviceExtension->pStorageStackDeviceObject = pStorageStackDeviceObject;
100: RtlInitEmptyUnicodeString(&pNewDeviceExtension->DeviceName,
101: pNewDeviceExtension->DeviceNameBuffer,
102: sizeof(pNewDeviceExtension->DeviceNameBuffer)
103: );
104: LzsbfGetObjectName(pStorageStackDeviceObject,
105: &pNewDeviceExtension->DeviceName);
106:
107: ExAcquireFastMutex(&gLZSBFilterAttachLock);
108: if (!LzsbfIsAttachedToDevice(DeviceList[i], NULL))
109: {
110: status = LzsbfAttachToMountedDevice(DeviceList[i], pNewDeviceObject);
111: if (!NT_SUCCESS(status))
112: {
113: KdPrint(("Fail to attach volume device\n"));
114: LzsbfCleanupMountedDevice(pNewDeviceObject);
115: IoDeleteDevice(pNewDeviceObject);
116: }
117: }
118: else
119: {
120: KdPrint(("Is attached already\n"));
121: LzsbfCleanupMountedDevice(pNewDeviceObject);
122: IoDeleteDevice(pNewDeviceObject);
123: }
124: ExReleaseFastMutex(&gLZSBFilterAttachLock);
125:
126: }
127: __finally
128: {
129: if (pStorageStackDeviceObject != NULL)
130: {
131: ObDereferenceObject(pStorageStackDeviceObject);
132: }
133: ObDereferenceObject(DeviceList[i]);
134: }
135: }
136: ExFreePool(DeviceList);
137: }
138:
139: KdPrint(("LZSBFilter!lzsbfEnumerateFileSystemVolumes() success!\n"));
140: return STATUS_SUCCESS;
141: }
完成这一过程最主要的调用是IoEnumerateDeviceObjectList,原型如下,同样也是Win2K+SP4和WinXP之后才有的调用.这个函数能够枚举指定驱动的设备链并返回到一个设备对象指针数组当中,需要注意的是,此时返回的对象包括文件系统的控制设备(我们枚举的是文件系统驱动的设备对象),所以在绑定时需要进行判断是否是控制设备.
1: NTSTATUS
2: IoEnumerateDeviceObjectList(
3: IN PDRIVER_OBJECT DriverObject,
4: IN PDEVICE_OBJECT *DeviceObjectList,
5: IN ULONG DeviceObjectListSize,
6: OUT PULONG ActualNumberDeviceObjects
7: );
在枚举设备对象时,除了判断是否为控制设备,还需要判断是否为卷影,卷影是用于磁盘数据恢复的一种特殊设备,对于卷影我们也是跳过不进行绑定.
稍微总结一下思路:1.注册文件系统变动回调函数;2.在回调函数中绑定文件系统的控制设备,绑定控制设备时需要注意判断是否是标准文件识别器;3.绑定控制设备之后,枚举文件系统驱动(通过控制设备对象得到文件系统驱动对象指针)的设备链,排除卷影和控制设备本身,绑定其余的卷设备.
至此,大概的绑定设备思路就有了,剩下的就是一些实现细节,下次再分享..
最后,秉承有图有码有真相的原则.附上LOG截图一张- -
to be continue....