Windows文件过滤驱动开发
来源:互联网 发布:python源代码分享 编辑:程序博客网 时间:2024/04/29 07:23
原文地址:http://blog.sina.com.cn/s/blog_7785041f0101jekb.html
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, 而路径则是,首先用IoGetCurrentIrpStackLocation(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中,
FileDispositionInformation == 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 = IoGetNextIrpStackLocation(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;
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
(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
(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(
{
}
这里的回调函数MyQueryFileComplete实现如下:
MyQueryFileComplete(
{