转 Windows串口过滤驱动程序的开发
在Windows系统上与安全软件相关的驱动开发过程中,“过滤(filter)”是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件和下层的真实驱动,就加入了新的功能。
过滤的概念和基础
1.设备绑定的内核API之一
进行过滤的最主要方法是对一个设备对象(Device Object)进行绑定。通过编程生成一个虚拟设备,并“绑定”(Attach)在一个真实的设备上。一旦绑定,则本来操作系统发送给真实设备的请求,就会首先发送的这个虚拟设备。
在WDK中,有多个内核API能实现绑定功能。下面是其中一个函数的原型:
- NTSTATUS
- IoAttachDevice(
- IN PDEVICE_OBJECT SourceDevice,
- IN PUNICODE_STRING TargetDevice,
- OUT PDEVICE_OBJECT *AttachedDevice
- );
IoAttachDevice的参数如下:
SouceDevice是调用者生成的用来过滤的虚拟设备;而TargetDevice是要被绑定的目标设备。请注意这里的TargetDevice并不是一个PDEVICE_OBJECT,而是设备的名字。
如果一个设备被其他设备绑定了,他们在一起的一组设备,被称为设备栈。实际上,IoAttachDevice总会绑定设备栈最上层的那个设备。
AttachedDevice是一个用来返回的指针的指针。绑定成功后,被绑定的设备的指针返回到这个地址。
下面这个例子绑定串口1。之所以这里绑定很方便,是因为在Windows中,串口设备有固定的名字。第一个串口名字为“\Device\Serial0”,第二个为“\Device\Serial1”,以此类推。请注意实际编程时C语言中的“\”要写成“\\”。
- UNICODE_STRING com_name = RLT_CONSTANT_STRING(L"\\Device\\Serial0");
- NTSTATUS status = IoAttachDevice(
- com_filter_device, //生成的过滤设备
- &com_device_name, //串口的设备名
- &attached_device //被绑定的设备指针返回到这里
- );
2.绑定设备的内核API之二
并不是所有设备都有设备名字,所以依靠IoAttachDevice无法绑定没有名字的设备。另外还有两个API:一个是IoAttachDeviceToDeviceStack,另一个是IoAttachDeivceToDeviceStackSafe。这两个函数功能一样,都是根据设备对象的指针(而不是名字)进行绑定;区别是后者更加安全,而且只有在Windows2000SP4和Windows XP以上的系统中才有。
- NTSTATUS
- IoAttachDeviceToDeviceStackSafe(
- IN PDEVICE_OBJECT SourceDevice, //过滤设备
- IN PDEVICE_OBJECT TargetDevice, //要被绑定的设备
- IN OUT PDEVICE_OBJECT *AttachedToDeviceObject //返回最终绑定的设备
- );
和第一个API类似,只是TargetDevice换成了一个指针。另外,AttachedToDeviceObject同样也是返回最终被绑定的设备,实际上也就是之前设备栈上最顶端的那个设备。
3.生成过滤设备并绑定
在绑定一个设备之前,先要知道如何生成一个用于过滤的过滤设备。函数IoCreateDevice被用于生成设备:
- NTSTATUS
- IoCreateDevice(
- IN PDRIVER_OBJECT DriverObject,
- IN ULONG DeviceExtensionSize,
- IN PUNICODE_STRING DeviceName,
- IN DEVICE_TYPE DeviceType,
- IN ULONG DeviceCharacteristics,
- IN BOOLEAN Exclusive,
- OUT PDEVICE_OBJECT *DeviceObject
- );
DriverObject:输入参数,每个驱动程序中会有唯一的驱动对象与之对应,但每个驱动对象会有若干个设备对象。DriverObject指向的就是驱动对象指针。
DeviceExtensionSize:输入参数,指定设备扩展的大小,I/O管理器会根据这个大小,在内存中创建设备扩展,并与驱动对象关联。
DeviceName:输入参数,设置设备对象的名字。一个规则是,过滤设备一般不需要设备名,传入NULL即可
DeviceType:输入参数,设备类型,保持和被绑定的设备类型一致即可。
DeviceCharacterristics:输入参数,设备对象的特征。
Exclusive:输入参数,设置设备对象是否为内核模式下使用,一般设置为TRUE。
DeviceObject:输出参数,I/O管理器负责创建这个设备对象,并返回设备对象的地址。
但值得注意的是,在绑定一个设备之前,应该把这个设备对象的多个子域设置成和要绑定的目标对象一致,包括标志和特征。下面是一个示例函数,这个函数可以生成一个设备,并绑定到另一个设备上。
- NTSTATUS
- ccpAttachDevice(
- PDRIVER_OBJECT driver,
- PDEVICE_OBJECT oldobj,
- PDEVICE_OBJECT *fltobj,
- PDEVICE_OBJECT *next)
- {
- NTSTATUS status;
- PDEVICE_OBJECT topdev = NULL;
- //生成设备然后绑定
- status = IoCreateDevice(driver,
- 0,
- NULL,
- oldobj->DeviceType,
- 0,
- FALSE,
- fltobj);
- if (status != STATUS_SUCCESS)
- {
- return status;
- }
- //拷贝重要标志位
- if (oldobj->Flags & DO_BUFFERED_IO)
- {
- (*fltobj)->Flags |= DO_BUFFERED_IO;
- }
- if(oldobj->Flags & DO_DIRECT_IO)
- {
- (*fltobj)->Flags |= DO_DIRECT_IO;
- }
- if (oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
- {
- (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
- }
- (*fltobj)->Flags |= DO_POWER_PAGABLE;
- //将一个设备绑定到另一个设备
- topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj);
- if (topdev == NULL)
- {
- //如果绑定失败了,销毁设备,返回错误
- IoDeleteDevice(*fltobj);
- *float = NULL;
- status = STATUS_UNSUCCESSFUL;
- return status;
- }
- *next = topdev;
- //设置这个设备已经启动
- (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
- return STATUS_SUCCESS;
- }
4.从名字获得设备对象
在知道一个设备名字的情况下,使用IoGetDeviceObjectPointer可以获得这个设备对象的指针。这个函数的原型如下:
- NTSTATUS
- IoGetDeviceObjectPointer(
- IN PUNICODE_STRING ObjectName,
- IN ACCESS_MASK DesiredAccess,
- OUT PFILE_OBJECT *FileObject,
- OUT PDEVICE_OBJECT *DeviceObject
- );
其中ObjectName就是设备名字。
DesireAccess是期望访问的权限。实际使用时不要顾虑那么多,直接填写FILE_ACCESS_ALL即可。
FileObject是一个返回参数,即获得设备对象的同时会得到一个文件对象(File Object)。就打开串口这件事而言,这个文件对象没有什么用处。但必须注意:在使用这个函数之后必须把这个文件对象“解除引用”,否则会引起内存泄露。
要得到的设备对象就返回在参数DeviceObject中了。
- //打开一个端口设备
- PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)
- {
- //外面输入的是串口的id,这里会改写成字符串的形式
- UNICODE_STRING name_str;
- static WCHAR name[32] = {0};
- PFILE_OBJECT fileObj = NULL;
- PDEVICE_OBJECT devObj = NULL;
- //根据id转换成串口的名字
- memset(name,0,sizeof(WCHAR)*32);
- RtlStringCchPrintfW(
- name,32,
- L"\\Device\\Serial%d",id);
- RtlInitUnicodeString(&name_str,name);
- //打开设备
- *status = IoGetDeviceObjectPointer(
- &name_str,
- FILE_ALL_ACCESS,
- &fileObj,&devObj);
- //如果打开成功了,记得一定要把文件对象解除引用
- if (*status == NT_SUCCESS)
- {
- ObDereferenceObject(fileObj);
- }
- //返回设备对象
- return devObj;
- }
5.绑定所有端口
下面是一个简单的函数,实现了绑定本机上所有串口的功能。这个函数用到了前面提供的ccpOpenCom和ccpAttachDevice这两个函数
- //计算机上最多只有32个串口,这里是笔者的假定
- #define CCP_MAX_COM_IO 32
- //保存所有过滤设备指针
- static PDEVICE_OBJECT s_fltObj[CCP_MAX_COM_IO] = {0};
- //保存所有真实设备指针
- static PDEVICE_OBJECT s_nextObj[CCP_MAX_COM_IO] = {0};
- //这个函数绑定所有的串口
- void ccpAttachAllComs(PDRIVER_OBJECT driver)
- {
- ULONG i;
- PDEVICE_OBJECT com_ob;
- NTSTATUS status;
- for(i = 0 ; i < CCP_MAX_COM_IO ; i++)
- {
- //获得object引用
- com_ob = ccpOpenCom(i,&status);
- if (com_ob == NULL)
- {
- continue;
- }
- //在这里绑定,并不管绑定是否成功
- ccpAttachDevice(driver,com_ob,&s_fltObj[i],&s_nextObj[i]);
- }
- }
没必要关心这个绑定是否成功,就算失败,看下一个s_fltObj即可。这个数组中不为NULL的成员表示已经绑定,为NULL的成员则是没有绑定成功或者绑定失败的。这个函数需要一个DRIVER_OBJECT的指针。
获得实际数据
1.请求的区分
Windows的内核开发者们确定了很多数据结构,例如:驱动对象(DriverObject),设备对象(DeviceObject),文件对象(FileObject)等,需要了解的是:
(1)每个驱动程序只有一个驱动对象
(2)每个驱动程序可以生成若干个设备对象,这些设备对象从属于一个驱动对象
(3)若干个设备(他们可以属于不同的驱动)依次绑定形成一个设备栈,总是最顶端的设备先接收到请求。
(4)IRP是上层设备之间传递请求的常见数据结构,但不是唯一的数据结构
串口设备接收到的都是IRP,因此只要对所有IRP进行过滤,就可以得到串口流过的数据。请求可以通过IRP的主功能号区分。例如下面代码:
- PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
- if (irpsp->MajorFunction == IRP_MJ_WRITE)
- {
- //如果是写....
- }
- else if (irpsp->MajorFunction == IRP_MJ_READ)
- {
- //如果是读....
- }
2.请求的结局
对请求的过滤,最终的结局有3种:
(1)请求被通过了,过滤不做任何事情,或者简单的获取请求的一些信息。但是请求本身不受干扰。
(2)请求直接被否决了,下层驱动根本收不到这个请求。
(3)过滤完成了这个请求。
串口过滤要捕获两种数据:一种是发送出的数据(也就是写请求的数据),另一种是接收的数据(也就是读请求的数据)。为了简单起见,我们只捕获发送出去的请求。这样,只需要采取第一种处理方法即可。
这种处理最为简单。首先调用IoSkipCurrentIrpStackLocation跳到当前栈空间;然后调用IoCallDriver把这个请求发送给真实的设备。请注意:因为真实的设备已经被过滤设备绑定,所以首先接收到IRP的是过滤设备对象。代码如下:
- //跳到当前栈空间
- IoSkipCurrentIrpStackLocation(irp);
- status = IoCallDriver(s_nextObj[i],irp);
3.写请求的数据
那么,一个写请求(也就是串口一次发送的数据)保存在哪呢?IRP的结构中有三个地方可以描述缓冲区:一个是irp->MDLAddress,一个是irp->UserBuffer,一个是irp->AssociatedIrp.SystemBuffer.三种结构的具体区别参见(Windows驱动技术开发详解__派遣函数)。
回到串口的问题,那么串口的写请求到底是用哪种方式呢?我们不知道,但是可以用下面方法获得:
- PBYTE buffer = NULL;
- if (IRP->MdlAddress != NULL)
- buffer = (PBYTE)MmGetSystemAddressForMdlSafe(IRP->MdlAddress);
- else
- buffer = (PBYTE)IRP->UserBuffer;
- if (buffer == NULL)
- buffer = (PBYTE)IRP->AssociatedIrp.SystemBuffer;
完整的代码
1.完整的分发函数(派遣函数)
- NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
- {
- PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
- NTSTATUS status;
- ULONG i,j;
- //首先得知道发送给哪个设备,设备一共最多CCP_MAX_COM_ID个
- //是前面的代码保存好的你都在s_fltObj中
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if (s_fltObj[i] == device)
- {
- //所有电源操作全部直接放过
- if (irpsp->MajorFunction == IRP_MJ_POWER)
- {
- //直接发送,然后返回说已被处理
- PoStartNextPowerIrp(irp);
- IoSkipCurrentIrpStackLocation(irp);
- return PoCallDriver(s_nextObj[i],irp);
- }
- }
- //此外我们只过滤写请求
- if (irpsp->MajorFunction == IRP_MJ_WRITE)
- {
- //如果是写,先获得长度
- ULONG len = irpsp->Parameters.Write.Length;
- //然后获得缓冲区
- PUCHAR buf = NULL;
- if(irp->MdlAddress != NULL)
- buf = (PUCHAR)
- MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
- else
- buf = (PUCHAR)irp->UserBuffer;
- if(buf == NULL)
- buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
- //打印内容
- for (j = 0 ; j < len ; j++)
- {
- DbgPrint("comcap:Send Data:%2x\r\n",buf[j]);
- }
- }
- //这些请求直接下发即可
- IoSkipCurrentIrpStackLocation(irp);
- return IoCallDriver(s_nextObj[i],irp);
- }
- //如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误
- irp->IoStatus.Information = 0;
- irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
- IoCompleteRequest(irp,IO_NO_INCREMENT);
- return STATUS_SUCCESS;
- }
2.动态卸载
前面说了如何绑定,但是没说如何解除绑定。如果要把这个模块做成可以动态卸载的模块,则必须提供一个卸载函数。我们应该在卸载函数中完成解除绑定的功能,否则,一旦卸载一定会蓝屏。
- #define DELAY_ONE_MICROSECOND (-10)
- #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
- #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
- VOID ccpUnload(PDRIVER_OBJECT drv)
- {
- ULONG i;
- LARGE_INTEGER interval;
- //首先解除绑定
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if(s_nextObj[i] != NULL)
- IoDeleteDevice(s_nextObj[i]);
- }
- //睡眠5秒,等待所有IRP处理结束
- interval.QuadPart = (5*1000 *DELAY_ONE_MICROSECOND);
- KeDelayExecutionThread(KernelMode,FALSE,&interval);
- //删除这些设备
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if(s_fltObj[i] != NULL)
- IoDeleteDevice(s_fltObj[i]);
- }
- }
DriverEntry函数代码:
- NTSTATUS DriverEntry(
- IN OUT PDRIVER_OBJECT DriverObject,
- IN PUNICODE_STRING RegistryPath
- )
- {
- DbgPrint("Enter Driver\r\n");
- size_t i;
- //所有分发函数都设置成一样的
- for (i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION ; i++)
- {
- DriverObject->MajorFunction[i] = ccpDispatch;
- }
- //支持动态卸载
- DriverObject->DriverUnload = ccpUnload;
- //绑定所有的串口
- ccpAttachAllComs(DriverObject);
- return STATUS_SUCCESS;
- }
测试效果:
转载于:https://www.cnblogs.com/dancheblog/p/6050665.html
转 Windows串口过滤驱动程序的开发相关推荐
- 寒江独钓Windows内核安全编程__一个简单的Windows串口过滤驱动程序的开发
在Windows系统上与安全软件相关的驱动开发过程中,"过滤(filter)"是极其重要的一个概念.过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而 ...
- Windows文件系统过滤驱动开发教程(0,1,2)
0. 作者,楚狂人自述 我长期网上为各位项目经理充当"技术实现者"的角色.我感觉Windows文件系统驱动的开发能找到的资料比较少.为了让技术经验不至于遗忘和引起大家交流的兴趣我以 ...
- Window XP驱动开发(二十一) 过滤驱动程序
转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎大家拍砖 参考书籍<<Windows驱动开发技术详解>> 过滤驱动程序的开发十分 ...
- Windows内核开发之串口过滤
学习了几个月的内核编程,现在对Windows驱动开发又了更加深入的认识,特别是对IRP的分层处理逻辑有了深入认识. 总结起来就几句话: 当irp下来的时候,你要根据实际情况,进行处理 1> 无处 ...
- Windows CE下驱动程序开发基础
我想即使读者看过微软的关于驱动开发的培训教材和CE帮助文档中的驱动部分,头脑中仍然一片茫然.要想真正了解驱动程序必须结合一些驱动程序源码,在此我以串口驱动程序(COM16550)中初始化过程为线索简单 ...
- Windows 文件系统过滤驱动开发教程 (第二版)
Windows 文件系统过滤驱动开发教程 (第二版) 楚狂人-2007-上海 (MSN:walled_river@hotmail.com) -1. 改版序....... ...
- Windows文件系统过滤驱动开发教程(4)
Windows文件系统过滤驱动开发教程 4.设备栈,过滤,文件系统的感知 前边都在介绍文件系统驱动的结构,却还没讲到我们的过滤驱动如何能捕获所有发给文件系统驱动的irp,让我们自己来处理?前面已经解释 ...
- PCI设备WINDOWS驱动程序的开发
PCI设备WINDOWS驱动程序的开发 摘要:本文主要介绍了在Windows9x操作系统下开发PCI设备驱动程序的方法. 关键词:PCI设备 驱动程序 PCI设备概述 近几年来,随着诸如图形处理.图像 ...
- 寒江独钓第3章——串口过滤
一.过滤的概念: 过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真是驱动程序,就加入了新的功能. 1.1 设备绑定的内核API 进行过滤 ...
最新文章
- Python OpenCV学习笔记之:图像读取,显示及保存
- 聊聊数据库中的那些锁
- Top-1 Error 和 Top-5 Error
- c语言在程序中显示现在星期几,C语言程序设计: 输入年月日 然后输出是星期几...
- (16):Silverlight 2 数据与通信之JSON
- 问题解决 Visual Studio 2015 无法复制文件“D:\swapfile.sys”
- python 之简单聊聊类的只读和只写特性
- 最有效的更改linux 系统时区的方法
- windows7环境下VS2010中文版本配置MPI开发环境图文教程
- android 驱动程序,安卓设备安装USB驱动程序教程
- 仿网易云音乐的YY音乐微信小程序源码
- linux无法识别raid,linux – 无法从失败的RAID中恢复
- 图片实现水平垂直居中的方法
- C#拆分Excel工作表
- Linux版本有哪些
- 编程(代码、软件)规范(适用嵌入式、单片机、上位机等)
- 笔记本建立WIFI热点的bat命令
- Xilinx vivado 常用IP核使用
- UR5机器人学习之TCP/IP通讯
- 视觉3D目标检测 | 从视觉几何到BEV检测
热门文章
- Windows xp 定时关机命令
- 三款200万像素照相手机测评
- 超大Sql文件_超大文件_mysql数据导入到mycat数据库_亲测好用---Linux运维工作笔记053
- link标签中的integrity和crossorigin字段---web前端工作笔记015
- axios的介绍与页面配置---axios工作笔记003
- MQTT工作笔记0005---CONNECT控制报文2
- PostGreSql工作笔记003---在Navicat中创建数据库时报错rolcatupdate不存在_具体原因看其他博文_这里使用pgAdmin4创建管理postgre
- python数据结构剑指offer-从尾到头打印链表
- 深度理解cnn 网络
- make: *** [.build_release/lib/libcaffe.so] 错误 1