Windows文件过滤驱动开发

由于项目需要,这十天做了个Windows文件过滤驱动, 感觉windows下的驱动也不是那么的神秘, Mirosoft可以说是把API做到了极致(尽管有时真的是没必要), 他们喜欢把API包装了又包装, 不让你看清楚底层的东西. 不过另一方面, 调用这些API也是挺方便的(当然了,API多了,有些API会有些Bug也是在所难免的,比如ObQueryNameString). 由于是零基础开始做这个驱动的, 所以这里稍微把过程记录的详细些!

1. 搭建编译环境及WDM介绍.
这里给几个link:
ref1
WDM开发之路


编译环境搭建好了后, 在WDK的编译环境中, 输入build -cZg(或者直接build), 就可以编译生成驱动文件的.sys文件了. Debug版本的编译会在objchk_wxp_x86下, Release版本的编译会在objfre_wxp_x86下面.

2. 具体写程序的时候, 网上参考多的是 "楚狂人" 的"Windows文件系统过滤驱动开发教程(第二版)", 有很多地方都可以下载, 大家可以自己找找. 这个教程确实不错, 不过, 我对初学者的建议是:
(1) 对照代码看这个教程好些, 仅仅直接看教程,有些地方可能不太了解.
(2) 里面说了很多, 如果有些地方不理解, 自己写代码尝试,可能是最好的理解方式.
另外, 写程序时, 不能不提到DDK的帮助文档, 和安装路径下,src文件夹中的参考代码, 他们太好了!
读代码, 写代码,再调试, 是最好的编程提高方式!
噢,说到调试, 内核级的调试,必须小心, 一不小心就会造成蓝屏, 另外, 可以找DbgView.exe 和KmdManager.exe来帮助调试, 更高级的就要Windbg了, 不过, 我个人感觉, 如果是自己写的程序, 又不是特别的大,只要自己想清楚了, 靠KdPrint(())就可以解决问题, 用不赵Windbg这样的高级专业工具, 当然, 如果会使用Windbg那是更好不过了.

3 .开发总结感受:
当你对FileSpy,sfileter, FileMon,RegMon 等的源码到了一定程序 , 并做了足够的一些程序练习和调试后, 这里探讨些,开发的细节问题. 首先介绍个不错的 windows文件过滤驱动经验总结 . 这里对获取文件路径这点上, 我补充下是: 根据我的经验, 只能在IRP_MJ_CREATE中获取到文件的全路径, 其它地方, 比如说, 要对文件增,删,改的时候,即使自己构造IRP包, 仍然是不能得到全路径,仅仅能得到"去掉盘符"后的文件地址(即相对某个分区盘的相对地址).
如下的地方, 我会搞一些代码的snippet,来分析:
(1)在驱动中取得盘符和路径是分开的.取得盘符是用RtlVolumeDeviceToDosName , 而路径则是,首先用IoGetCurrentIrpStackLoca tion(Irp)获得当前的IO_STACK_LOCATION , 然后获取里面的FileObject对象指针, 再获得其FileName字段
(2)禁止访问目录:
实现禁止访问目录是比较简单的,有很多的方法。我是在IRP_MJ_DIRECTORY_CONTROL
中判断是不是要禁止的目录,然后拦截。拦截的操作我就不多说了,基本一样:
Irp->IoStatus.Status = STATUS_ACCESS_DENIED;
Irp->IoStatus.Information = 0;
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
(3)设置目录为只读
由于禁止目录访问的方法很容易,所以很容易让人觉得禁止写是不是就是在IRP_MJ_WRITE中
判断目录路径然后直接拦截了。我测试后发现不能这样实现,论坛上的人告我将目录设置为只读
实际上是把目录下所有文件设置为只读,即有一目录C:\\Project你想设置为只读,实际的操作是:
对于该目录下任一文件xx.xx,当该文件想进行写的时候,驱动会得到路径\\Project\\xx.xx,此时
判断文件的父目录是不是\\Project并拦截之,即可实现\\Project\\xx.xx的只读控制。
到目前我实现就是使用这样的方法,是不是可以直接对目录进行拦截,我不知道。
(4)禁止删除(重命名)
也还算简单,在IRP_MJ_SET_INFORMATION中,
FileDispositionInformati on == currentIrpStack->Parameters.SetFile.FileInformationClass
                            || FileRenameInformation == currentIrpStack->Parameters.SetFile.FileInformationClass

(5)禁止创建文件
这里主要是区别一下新建文件和打开文件.
我的理解是:当我们调用CreateFile并且希望创建一个文件的时候,系统会首先发送一个标志为FILE_OPEN的请求,并且判断底层文件系统的返回值,如果返回成功,则表明文件存在并且已经成功打开,否则如果返回结果是NO SUCH FILE,则紧接着创建一个FILE_OPEN_IF请求,得以将文件创建,所以如果我们在Create的Options当中发现了 FILE_CREATE,FILE_OPEN_IF和FILE_OVERWRITE_IF三个标志,则表明一定是在创建而不是打开.
代码如下:
CreateDisposition = (irpSp->Parameters.Create.Options>> 24) & 0x000000ff;
if(CreateDisposition==FILE_CREATE||CreateDisposition==FILE_OPEN_IF
||CreateDisposition==FILE_OVERWRITE_IF)
{
DbgPrint("It is a CREATE FILE operation\\n");
Irp->IoStatus.Status = STATUS_ACCESS_DENIED;
Irp->IoStatus.Information = 0;
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}

(6)怎么自己构建一个IRP请求(从你的当前driver层, 往下传, 并使用回调,这样不会出现重入的问题)
代码如下:
BOOLEAN
MyGetFullPathNameByIRP(
       PDEVICE_OBJECT DeviceObject,
       PFILE_OBJECT FileObject,
       FILE_INFORMATION_CLASS FileInformationClass,
       PVOID FileQueryBuffer,
       ULONG FileQueryBufferLength
       )
{
       PIRP irp;
       KEVENT event;
       IO_STATUS_BLOCK IoStatusBlock;
       PIO_STACK_LOCATION ioStackLocation;
     
       KdPrint(("Getting file name for %x\n", FileObject));

       //
       // Initialize the event
       //
       KeInitializeEvent(&event, SynchronizationEvent, FALSE);

       //
       // Allocate an irp for this request. This could also come from a
       // private pool, for instance.
       //
       irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
       if(!irp) {

               //
               // Failure!
               //
               return FALSE;
       }

       //
       // Build the IRP's main body
       //
       irp->AssociatedIrp.SystemBuffer = FileQueryBuffer;
       irp->UserEvent = &event;
       irp->UserIosb = &IoStatusBlock;
       irp->Tail.Overlay.Thread = PsGetCurrentThread();
       irp->Tail.Overlay.OriginalFileObject = FileObject;
       irp->RequestorMode = KernelMode;
       irp->Flags = 0;

       //
       // Set up the I/O stack location.
       //
       ioStackLocation = IoGetNextIrpStackLocatio n(irp);
       ioStackLocation->MajorFunction = IRP_MJ_QUERY_INFORMATION;
       ioStackLocation->DeviceObject = DeviceObject;
       ioStackLocation->FileObject = FileObject;
       ioStackLocation->Parameters.QueryFile.Length = FileQueryBufferLength;
       ioStackLocation->Parameters.QueryFile.FileInformationClass = FileInformationClass;

       //
       // Set the completion routine.
       //
       IoSetCompletionRoutine(irp, MyQueryFileComplete, 0, TRUE, TRUE, TRUE);

       //
       // Send it to the FSD
       //
       (void) IoCallDriver(DeviceObject, irp);

       //
       // Wait for the I/O
       //
       KeWaitForSingleObject(&event, Executive, KernelMode, TRUE, 0);

       //
       // Done! Note that since our completion routine frees the IRP we cannot
       // touch the IRP now.
       //
     
       return NT_SUCCESS( IoStatusBlock.Status );
}

这里的回调函数MyQueryFileComplete实现如下:
MyQueryFileComplete(
       PDEVICE_OBJECT DeviceObject,
       PIRP Irp,
       PVOID Context
       )
{
       //
       // Copy the status information back into the "user" IOSB.
       //
       *Irp->UserIosb = Irp->IoStatus;
       if( !NT_SUCCESS(Irp->IoStatus.Status) ) {

               KdPrint(("     ERROR ON IRP: %x\n", Irp->IoStatus.Status ));
       }
     
       //
       // Set the user event - wakes up the mainline code doing this.
       //
       KeSetEvent(Irp->UserEvent, 0, FALSE);
     
       //
       // Free the IRP now that we are done with it.
       //
       IoFreeIrp(Irp);
     
       //
       // We return STATUS_MORE_PROCESSING_REQUIRED because this "magic" return value
       // tells the I/O Manager that additional processing will be done by this driver
       // to the IRP - in fact, it might (as it is in this case) already BE done - and
       // the IRP cannot be completed.
       //
       return STATUS_MORE_PROCESSING_REQUIRED;
}

(7)怎么构造自己的一个Map
在文件名的保存上,我们可能需要构造一个Map, Map构造和数据结构中的一样, 其基本思想是:
首先定义个Map桶长, 然后定义一个Hash的算法, 对Map中的元素对,提供一个全局数组进行管理(也可以是个链表,数组更方便), 当entry冲突的时候, 就在改entry上再挂一个链表, 链表的总长不超过前面定义的Map桶长.
这里有一个实现:更多Hash算法的实现访问 这里
//
// Hash table for keeping names around. This is necessary because
// at any time the name information in the fileobjects that we
// see can be deallocated and reused. If we want to print accurate
// names, we need to keep them around ourselves.
//
PHASH_ENTRY                      HashTable[NUMHASH];

//
// Reader/Writer lock to protect hash table.
//
ERESOURCE       HashResource;

//
// Hash function. Basically chops the low few bits of the file object
//
#if defined(_IA64_)
#define HASHOBJECT(_fileobject)              (((ULONG_PTR)_fileobject)>>5)%NUMHASH
#else
#define HASHOBJECT(_fileobject)              (((ULONG)_fileobject)>>5)%NUMHASH
#endif


PHASH_ENTRY                 hashEntry, newEntry;
     
//
       // Lookup the object in the hash table to see if a name
       // has already been generated for it
       //
       KeEnterCriticalRegion();
       ExAcquireResourceSharedL ite( &HashResource, TRUE );

       hashEntry = HashTable[ HASHOBJECT( fileObject ) ];
       while( hashEntry && hashEntry->FileObject != fileObject ) {

               hashEntry = hashEntry->Next;
       }

       //
       // Did we find an entry?
       //
       if( hashEntry ) {

               //
               // Yes, so get the name from the entry.
               //
               strcpy( fullPathName, hashEntry->FullPathName );
               ExReleaseResourceLite( &HashResource );
               KeLeaveCriticalRegion();
               return;
       }

       ExReleaseResourceLite( &HashResource );
       KeLeaveCriticalRegion();

注,这里使用的是CriticalRegion,这个是微软进行线程同步的一种手段, 一般的, 我们还可以使用mutex来实现:
#define MUTEX_INIT(v)           KeInitializeMutex( &v, 0 )
#define MUTEX_WAIT(v)           KeWaitForMutexObject( &v, Executive, KernelMode, FALSE, NULL )
#define MUTEX_RELEASE(v)     KeReleaseMutex( &v, FALSE )
调用的时候, 就是,整个程序初时化的时候,进行init;
程序中使用的时候:
MUTEX_WAIT( HashMutex );
......
MUTEX_RELEASE( HashMutex );

4. 关于INF文件的书写
INF是用来安装我们的驱动的, 最simple的方法是从DDK的src中拷贝一个出来, 然后更改成我们的驱动所需要的INF文件!
INF文件中, 有个StartType值, 它的意义是何时启动驱动程序。可为:
SERVICE_BOOT_START (0x0)
由操作系统 loader 启动。使用此值仅用于操作系统基本服务。
SERVICE_SYSTEM_START (0x1)
操作系统初始化式启动。
SERVICE_AUTO_START (0x2)
SCM 在系统启动期间启动
SERVICE_DEMAND_START (0x3)
SCM 根据需要启动
SERVICE_DISABLED (0x4)
此服务不可被启动

有了INF之后, 有这几个命令比较有用:
sc delete //在command下删除一个driver服务
net start //在command下启动一个driver服务
net stop //command下关闭一个driver服务

另外如果是minifile, 如果要手动启动一个driver服务, 需要在命令提示行中使用 “fltmc load 你的驱动名字” 才能真正载入minifile驱动。其他两个值的话,系统启动时会自动加载,
当然也可以使用 fltmc load 来加载了。相应的卸载命令是 “fltmc unload 你的驱动名字”。

附录:
理解常见的IRP消息意义!  这里
如果写键盘驱动,可以参考 这里
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值