Windows文件系统过滤驱动开发教程.docx
《Windows文件系统过滤驱动开发教程.docx》由会员分享,可在线阅读,更多相关《Windows文件系统过滤驱动开发教程.docx(51页珍藏版)》请在冰豆网上搜索。
Windows文件系统过滤驱动开发教程
Windows文件系统过滤驱动开发教程
0.作者,楚狂人自述
我长期网上为各位项目经理充当“技术实现者”的角色。
我感觉Windows文件系统驱动的开发能找到的资料比较少。
为了让技术经验不至于遗忘和引起大家交流的兴趣我以我的工作经验撰写本教程。
1. 概述,钻研目的和准备
我经常在网上碰到同行请求开发文件系统驱动。
windows的pc机上以过滤驱动居多。
其目的不外乎有以下几种:
一是用于防病毒引擎。
希望在系统读写文件的时候,捕获读写的数据内容,然后检测其中是否含有病毒代码。
二是用于加密文件系统,希望在文件写过程中对数据进行加密,在读的过程中进行解密。
三是设计透明的文件系统加速。
读写磁盘的时候,合适的cache算法是可以大大提高磁盘的工作效率。
windows本身的cache算法未必适合一些特殊的读写
磁盘操作(如流媒体服务器上读流媒体文件)。
设计自己的cache算法的效果,我已在工作中有所感受。
如果你刚好有以上此类的要求,你可以阅读本教程。
文件系统驱动是windows系统中最复杂的驱动种类之一。
不能对ifsddk中的帮助抱太多希望,以我的经验看来,文件系统相关的ddk帮助极其简略,很多重要的部分仅仅轻描淡写的带过。
如果安装了ifsddk,应该阅读src\filesys\OSR_docs下的文档。
而不仅仅是ddk帮助。
文件系统驱动开发方面的书籍很少。
中文资料我仅仅见过侯捷翻译过的一本驱动开发的书上有两三章涉及,也仅仅是只能用于9x的vxd驱动。
NT文件系统我见过一本英文书。
我都不记得这两本书的书名了。
如果您打算开发9x或者nt文件系统驱动,建议你去网上下载上文提及的书。
那两本书都有免费的电子版本下载。
如果你打算开发Windows2000\WindowsXP\Window2003的文件系统驱动,你可以阅读本教程。
虽然本教程仅仅讲述文件系统过滤驱动。
但是如果您要开发一个全新的文件系统驱动的话,本教程依然对你有很大的帮助。
学习文件系统驱动开发之前,应该在机器上安装ifsddk。
ddk版本越高级,其中头文件中提供的系统调用也越多。
经常有人询问如xpddk编译的驱动能不能在2000上运行等等的问题。
我想可以这样解释:
高级版本的ddk应该总是可以编译低级驱动的代码,而且得到的二进制版本也总是可以在低级系统上运行。
但是 反过来就未必可以了。
如果在高级系统上编写用于低级系统上的驱动,要非常认真的注意仅仅调用低级系统上有的系统调用。
ifsddk可以在某些ftp上免费下载。
我的使用的是ifs ddk for xp,但是我实际用来开发的两台机器有一台是windows 2000,另一台是windows 2003.我尽量使我编译出来的驱动,可以在2000\xp\2003三种系统上都通过测试。
安装配置ddk和在vc中开发驱动的方法网上有很多的介绍。
ifsddk安装之后,src目录下的filesys目录下有文件系统驱动的示例。
阅读这些代码你就可以快速的学会文件系统驱动开发。
filter目录下的sfilter是一个文件系统过滤驱动的例子。
另一个filespy完全是用这个例子的代码加工得更复杂而已。
如何用ddk编译这个例子请自己查看相关的资料。
文件系统过滤驱动编译出来后你得到的是一个扩展名为sys的文件。
同时你需要写一个.inf文件来实现这个驱动的安装。
我这里不讨论.inf文件的细节,你可以直接用sfilter目录下的inf文件修改。
对inf文件点鼠标右键弹出菜单选择“安装”,即可安装这个过滤驱动。
但是必须重新启动系统才生效。
如果重启后蓝屏无法启动,可以用其他方式引导系统后到system32\drivers目录下删除你的.sys文件再重启即可。
我尝试这种情况下用安全模式结果还 是蓝屏。
所以我后来不得不在机器上装了两个2000系统。
双系统情况下,一个系统崩溃了用另一个系统启动,删除原来的驱动即可。
如果要调试代码,请安装softice.
打开sfilter目录下的文件sources(这个文件没有扩展名),加入一行
BROWSER_INFO=1
然后打开Symbol Loader,File->Open选中你编译出来的xxx.sys,Modul->Load,Modul->Translate,然后就可以调试了。
打开softice,输入file *就可以看见代码。
如果准备好了,我们就可以开始琢磨windows文件系统过滤驱动的开发了。
Windows文件系统过滤驱动开发教程
2.hello world,驱动对象与设备对象
这里所说的驱动对象是一种数据结构,在DDK中名为DRIVER_OBJECT。
任何驱动程序都对应一个DRIVER_OBJECT.如何获得本人所写的驱动对应的DRIVER_OBJECT呢?
驱动程序的入口函数为DriverEntry,因此,当你写一个驱动的开始,你会写下如下的代码:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath ){
}
这个函数就相当与喜欢c语言的你所常用的main().IN是无意义的宏,仅仅表明后边的参数是一种输入,而对应的OUT则代表这个参数是一种返回。
这里没有使用引用,因此如果想在参数中返回结果,一律传入指针。
DriverObject就是你所写的驱动对应的DRIVER_OBJECT,是系统在加载你的驱动时候所分配的。
RegisteryPath是专用于你记录你的驱动相关参数的注册表路径。
DriverObject重要之处,在于它拥有一组函数指针,称为dispatch functions。
开发驱动的主要任务就是亲手撰写这些dispatch functions.当系统用到你的驱动,会向你的DO发送IRP(这是windows所有驱动的共同工作方式)。
你的任务是在dispatch function中处理这些请求。
你可以让irp失败,也可以成功返回,也可以修改这些irp,甚至可以自己发出irp。
设备对象则是指DEVICE_OBJECT.下边简称DO。
但是实际上每个irp都是针对DO发出的。
只有针对由该驱动所生成的DO的IRP,才会发给该驱动来处理。
当一个应用程序打开文件并读写文件的时候,windows系统将这些请求变成irp发送给文件系统驱动。
文件系统过滤驱动将可以过滤这些irp.这样,你就拥有了捕获和改变文件系统操作的能力。
象Fat32,NTFS这样的文件系统(File System,简称FS),可能生成好几种设备。
首先文件系统驱动本身往往生成一个控制设备(CDO).这个设备的主要任务是修改整个驱动的内部配置。
因此一个Driver只对应一个CDO。
另一种设备是被这个文件系统Mount的Volume。
一个FS可能有多个Volume,也可能一个都没有。
解释一下,如果你有C:
D:
E:
F:
四个分区。
C:
D:
为NTFS,E:
F:
为Fat32。
那么C:
,D:
则是Fat的两个Volume设备对象。
实际上"C:
"是该设备的符号连接(Symbolic Link)名。
而不是真正的设备名。
可以打开Symbolic Links Viewer,能看到:
C:
DeviceHarddiskVolume1
因此该设备的设备名为“DeviceHarddiskVolume1”.
这里也看出来,文件系统驱动是针对每个Volume来生成一个DeviceObject,而不是针对每个文件的。
实际上对文件的读写的irp,都发到Volume设备对象上去了。
并不会生成一个“文件设备对象”。
掌握了这些概念的话,我们现在用简单的代码来生成我们的CDO,作为我们开发文件系统驱动的第一步牛刀小试。
我不喜欢用微软风格的代码。
太长而且难看。
我对大部分数据结构和函数进行了重定义。
为此我写了一个名为wdf.h的头文件帮助我转换。
有兴趣的读者可以发邮件向索取这个文件。
没有也没有关系,我总是会写出wd_xxx系列的东西在DDK中的原形。
// -----------------wdf_filter.c中的内容-------------------------
#include "wdf.h"
wd_stat wdff_cdo_create(in wd_drv *driver, in wd_size exten_len,
in wd_ustr *name, out wd_dev **device)
{
return wd_dev_create(driver, exten_len, name, wd_dev_disk_fs,
wdf_dev_secure_open, wd_false, device);
}
wd_stat wd_main(in wd_drv* driver, in wd_ustr* reg_path)
{
wd_ustr name;
wd_stat status = wd_stat_suc;
// 然后我生成控制设备,虽然现在我的控制设备什么都不干
wd_ustr_init(&name,L"\FileSystem\Filters\our_fs_filter");
status = wdff_cdo_create(driver,0,&name,&g_cdo);
if(!
wd_suc(status))
{
if(status == wd_stat_path_not_found)
{
// 这种情况发生于FileSystemFilters路径不存在。
这个路径是
// 在xp上才加上的。
所以2000下会运行到这里
wd_ustr_init(&name,L"\FileSystem\our_fs_filter");
status = wdff_cdo_create(driver,0,&name,&g_cdo);
};
if(!
wd_suc(status))
{
wd_printf0("error:
create cdo failed.rn");
return status;
}
}
wd_printf0("success:
create cdo ok.rn");
return status;
}
为了让代码看起来象上边的那样,我不得不做了很多转换。
如
#define DriverEntry wd_main
一种爽的感觉,终于可以在写看起来更象是main()的函数中工作了。
wd_dev_create 这个函数内部调用的是IoCreateDevice.而wd_suc实际上是SUCCESS()这样的宏。
// ----------------------wdf.h中的内容------------------------------
#include "ntifs.h"
#define in IN
#define out OUT
#define optional OPTIONAL
#define wd_ustr UNICODE_STRING
#define wdp_ustr PUNICODE_STRING
#define wd_main DriverEntry
// 设备、驱动对象类型
typedef DRIVER_OBJECT wd_drv;
typedef DEVICE_OBJECT wd_dev;
typedef PDRIVER_OBJECT wd_pdrv;
typedef PDEVICE_OBJECT wd_pdev;
enum {
wd_dev_disk_fs = FILE_DEVICE_DISK_FILE_SYSTEM,
wd_dev_cdrom_fs = FILE_DEVICE_CD_ROM_FILE_SYSTEM,
wd_dev_network_fs = FILE_DEVICE_NETWORK_FILE_SYSTEM
};
// 状态相关的类型和宏
typedef NTSTATUS wd_stat;
enum {
wd_stat_suc = STATUS_SUCCESS,
wd_stat_path_not_found = STATUS_OBJECT_PATH_NOT_FOUND,
wd_stat_insufficient_res = STATUS_INSUFFICIENT_RESOURCES,
wd_stat_invalid_dev_req = STATUS_INVALID_DEVICE_REQUEST,
wd_stat_no_such_dev = STATUS_NO_SUCH_DEVICE,
wd_stat_image_already_loaded = STATUS_IMAGE_ALREADY_LOADED,
wd_stat_more_processing = STATUS_MORE_PROCESSING_REQUIRED,
wd_stat_pending = STATUS_PENDING
};
(inline的使用是有所限制的,inline 函数一般必须在头文件内,inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while switch,并且不能内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。
)
_inline wd_bool wd_suc(wd_stat state) //内聯函數
{
return NT_SUCCESS(state);
}
#define wd_printf0 DbgPrint
_inline wd_void wd_ustr_init(in out wd_ustr* str,
in const wd_wchar* chars)
{
RtlInitUnicodeString(str,chars);
};
_inline wd_void wd_ustr_init_em(in out wd_ustr *str ,in wd_wchar *chars,
in wd_size size)
{
RtlInitEmptyUnicodeString(str,chars,size);
};
wdf.h这个文件我仅仅节选了需要的部分。
以上您已经拥有了一个简单的“驱动”的完整的代码。
它甚至可以编译,安装(请修改sfilter.inf文件,其方法不过是将多处sfilter改为"our_fs_filter",希望这个过程中您不会出现问题)。
然后把wdf.h和wdf_filter.c放在您新建立的目录下,这个目录下还应该有另两个文件。
一个是Makefile,请从sfilter目录下拷贝。
另一个是SOURCES,请输入如下内容:
TARGETNAME=our_fs_filter
TARGETPATH=obj
TARGETTYPE=DRIVER
DRIVERTYPE=FS
BROWSER_INFO=1
SOURCES=wdf_filter.c
使用DDK编译之后您将得到our_fs_filter.sys.把这个文件与前所描述的 inf 文件同一目录,按上节所叙述方法安装。
这个驱动不起任何作用,但是你已经成功的完成了"hello world".
Windows文件系统过滤驱动开发教程
3.分发例程,fast io
上一节仅仅生成了控制设备对象。
但是不要忘记,驱动开发的主要工作是撰写分发例程(dispatch functions.).接上一接,我们已经知道自己的DriverObject保存在上文代码的driver中。
现在我写如下一个函数来指定一个默认的dispatch function给它。
//-----------------wdf.h中的代码----------------------
typedef PDRIVER_DISPATCH wd_disp_fuc;
_inline wd_void wd_drv_set_dispatch(in wd_drv* driver,
in wd_disp_fuc disp)
{
wd_size i;
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
driver->MajorFunction[i] = disp;
}
在前边的wd_main中,我只要加
wd_drv_set_dispatch(driver,my_dispatch_func);
就为这个驱动指定了一个默认的Dispatch Function.所有的irp请求,都会被发送到这个函数。
但是,我可能不希望这个函数处理过于复杂,而希望把一些常见的请求独立出来,如Read,Write,Create,Close,那我又写了几个函数专门用来设置这几个Dispatch Functions.
//-----------------wdf.h中的代码----------------------
_inline wd_void wd_drv_set_read(
in wd_drv* driver,
in wd_disp_fuc read)
{
driver->MajorFunction[IRP_MJ_READ] = read;
}
_inline wd_void wd_drv_set_write(
in wd_drv* driver,
in wd_disp_fuc write)
{
driver->MajorFunction[IRP_MJ_WRITE] = write;
}
wd_void wd_drv_set_create(in wd_drv* driver,
in wd_disp_fuc create)
{
driver->MajorFunction[IRP_MJ_CREATE] = create;
driver->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = create;
driver->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = create;
}
wd_void wd_drv_set_file_sys_control(in wd_drv* driver,
in wd_disp_fuc control)
{
driver->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = control;
}
wd_void wd_drv_set_clean_up(in wd_drv* driver,
in wd_disp_fuc clean_up)
{
driver->MajorFunction[IRP_MJ_CLEANUP] = clean_up;
}
wd_void wd_drv_set_close(in wd_drv* driver,
in wd_disp_fuc close)
{
driver->MajorFunction[IRP_MJ_CLOSE] = close;
}
别看我罗列n多代码,其实就是在设置driver->MajorFunction这个数组而已。
因此在wd_main对dispatch functions的设置,就变成了下边这样的:
// 开始设置几个分发例程
wd_drv_set_dispatch(driver,my_disp_default);
wd_drv_set_create(driver,my_disp_create);
wd_drv_set_clean_up(driver,my_disp_clean_up);
wd_drv_set_file_sys_control(driver,my_disp_file_sys_ctl);
wd_drv_set_close(driver,my_disp_close);
wd_drv_set_read(driver,my_disp_read);
wd_drv_set_write(driver,my_disp_write);
下面的任务都在写my_xxx系列的这些函数了。
但是对于这个DriverObject的设置,还并不是仅仅这么简单。
由于你的驱动将要绑定到文件系统驱动的上边,文件系统除了处理正常的IRP之外,还要处理所谓的FastIo.FastIo是Cache Manager调用所引发的一种没有irp的请求。
换句话说,除了正常的Dispatch Functions之外,你还得为DriverObject撰写另一组Fast Io Functions.这组函数的指针在driver->FastIoDispatch.我不知道这个指针留空会不会导致系统崩溃。
在这里本来是没有空间的,所以为了保存这一组指针,你必须自己分配空间。
下面是我常用的内存分配函数。
//-----------------wdf.h中的代码----------------------
// 最简单的分配内存的函数,可以指定分页非分页
_inline wd_pvoid wd_malloc(wd_bool paged,wd_size size)
{
if(paged)
return ExAllocatePool(PagedPool,size);
else
return ExAllocatePool(NonPagedPool,size);
}
// 释放内存
_inline wd_void wd_free(wd_pvoid point)
{
ExFreePool(point);
}
_inline wd_void wd_memzero(
wd_pvoid point,
wd_size size)
{
RtlZeroMemory(point,size);
}
有了上边的基础,我就可以自己写一个初始化FastIoDispatch指针的函数。
//-----------------wdf.h中的代码----------------------
wd_bool wd_fio_disp_init(wd_drv *driver,wd_ulong size)
{
wd_fio_disp *disp = wd_malloc(wd_false,size);
if(disp == wd_null)
return wd_false;
wd_memzero((wd_pvoid)disp,size);
driver->FastIoDispatch = disp;
driver->FastIoDispatch->SizeOfFastIoDispatch = size;
return wd_true;
}
这个函数为FastIoDispacth指针分配足够的空间并填写它的大小。
下面是再写一系列的函数来设置这个函数指针数组。
实际上,FastIo接口函数实在太多了,所以我仅仅写出这些设置函数的几个作为例子:
//-----------------wdf.h中的代码----------------------
_inline wd_void wd_fio_disp_set_query_standard(