Windows内核开发之串口过滤
学习了几个月的内核编程,现在对Windows驱动开发又了更加深入的认识,特别是对IRP的分层处理逻辑有了深入认识。
总结起来就几句话:
当irp下来的时候,你要根据实际情况,进行处理
1> 无处理,继续往下传
2> 处理之后 ,往下传
3> 处理之后, 往上传
4> 不做处理,直接丢弃
具体怎么理解,通过一个串口驱动过滤就可以深入理解。
一、串口过滤概念
串口过滤:平时我们看到的主机上的USB、网线口等都属于串口,那么设想一个环境,我去网吧上网,正在通过某宝付款,然后主机后面的USB插着一个串口监控器,把我的数据都获取了,然后我的钱就刷刷刷没了...何等悲哀的事情,所以,一个串口过滤,可以在不影响整个串口通信的功能上,拦截住用户层发送出去的信息,这个对于串口数据监控和安全有很大的作用。
二、串口过滤思路
首先是驱动过滤的原理:
“过滤”(filter)是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真实驱动程序,就加入了新的功能。
也可以说,一个虚拟的设备Attach在一个真实的物理设备上,只要有消息包发送到真实物理设备的方向,则优先进入虚拟设备,而虚拟设备是我们绑定在真实设备上的,因此,优先拿到消息包,并对消息包的内容进行解析,过滤,这里的过滤可以说是对千万的IRP进行if选择,拿到需要的对应设备的IRP,从而,进行分析处理。
为了加深理解,我将逻辑思路做了简要的总结:
1>首先注册IRP的派遣函数(Dispathc Funtion),在这里函数内容做IRP的过滤操作,提取缓冲区,下传IRP等等
2>打开真实的串口设备,以获取到指向串口设备的对象指针,用于下一步的真实虚拟绑定
3>根据真实的串口设备,创建一个虚拟的串口过滤设备(IoCreateDevice)
4>把虚拟串口设备Attach在物理设备上
5>完善派遣函数:对IRP_MJ_WRITE进行过滤,完成过滤操作,将各类IRP做相关的分发处理
总之:OpenCom-----IoCreateFilterDevice-----AttachTo-----IRP_MJ_FUCTION
三、编码测试
一、内核API
1、绑定设备API
NTSTATUS
IoAttachDeviceToDeviceStackSafe(
IN PDEVICE_OBJECT SourceDevice, // 过滤设备
IN PDEVICE_OBJECT TargetDevice, // 要被绑定的设备栈中的设备
IN OUT PDEVICE_OBJECT *AttachedToDeviceObject// 返回最终被绑定的设备
);
SourceDevice是调用者生成的用来过滤的虚拟设备;TargetDevice是要被绑定的目标设备。请注意这里的 TargetDevice并不是一个PDEVICE_OBJECT(DEVICE_OBJECT是设备对象的数据结构,以P开头的是其指针),而是一个字 符串(在驱动开发中字符串用UNICODE_STRING来表示)。实际上,这个字符串是要被绑定的设备的名字。Windows中许多设备对象是有名字的,但是并不是所有的设备对象都有名字。必须是有名字
的设备,才能用这个内核API进行绑定。在Windows中,串口设备是有固定名字的。这里有一个疑问:假设这个函数绑定一个名字所对应的设备,那么如果这个设备已经被其他的设备绑定了,会怎么样呢?如果一个设备被其他设备绑定,它们在一起的一组设备,被称为设备栈(之所以称为栈,是由于和请求的传递方式有关)。实际上,IoAttachDevice总是会绑定设备栈上最顶层的那个设备。
2、创建虚拟设备
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject
);
这个函数看上去很复杂,但是目前使用时,还无须了解太多。DriverObject是本驱动的驱动对象。这个指针是系统提供,从DriverEntry中传入,在最后完整的例子中再解释。DeviceExtensionSize是设备扩展,读者请先简单地传入0。 DeviceName是设备名称。一个规则是:过滤设备一般不需要名称,所以传入NULL即可。DeviceType是设备类型,保持和被绑定的设备类型 一致即可。DeviceCharacteristics是设备特征,在生成设备对象时笔者总是凭经验直接填0,然后看是否排斥,选择FALSE。
值得注意的是,在绑定一个设备之前,应该把这个设备对象的多个子域设置成和要绑定的目标对象一致,包括标志和特征。下面是一个示例的函数,这个函数可以生成一个设备,然后绑定在另一个设备上。
3、绑定设备API
首先要获得真实设备对象的指针
NTSTATUS
IoGetDeviceObjectPointer(
IN PUNICODE_STRING ObjectName,
IN ACCESS_MASK DesiredAccess,
OUT PFILE_OBJECT *FileObject,
OUT PDEVICE_OBJECT *DeviceObject
);
然后绑定串口,返回绑定之后的设备。比如topDev其实是指绑定之后的那一整个串口,可以说一个新的设备对象,通过对这个设备对象的操作,就可以完成对过滤设备的操作
topDev = IoAttachDeviceToDeviceStack(*fltObj, oldDev);
4、处理IRP,获取WRITE缓冲区内容
// 这里的irpsp称为IRP的栈空间,IoGetCurrentIrpStackLocation获得当前栈空间
// 栈空间是非常重要的数据结构
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
// 如果是写…
}
else if(irpsp->MajorFunction == IRP_MJ_READ)
{
// 如果是读…
Get到当前栈,也就是IRP的栈空间,然后通过,IRP->MajorFuction == IRP_MJ_WRITE来过滤出WRITE动作,同样,也能过滤到READ动作,因为这个IRP是从当前IRP来的,而这个IRP在物理层是串口数据发送,而我们的驱动刚好绑定的是真实物理串口设备,因此,能够拦截到这类IRP,也就是说,IRP虽然很多,但是,我们通过绑定设备完成了针对性的IRP拦截,从而达到了过滤作用。
5、请求完成
最终的结局有3种:
(1)请求被允许通过了。过滤不做任何事情,或者简单地获取请求的一些信息。但是请求本身不受干扰,这样系统行为不会有变化。
(2)请求直接被否决了。过滤禁止这个请求通过,这个请求被返回错误了,下层驱动程序根本收不到这个请求。这样系统行为就变了,后果是常常看见上层应用程序弹出错误框提示权限错误或者读取文件失败之类信息。
(3)过滤完成了这个请求。有时有这样的需求,比如一个读请求,我们想记录读到了什么。如果读请求还没有完成,那么如何知道到底会读到什么呢?只有让这个请求先完成再去记录。过滤完成这个请求时不一定要原封不动地完成,这个请求的参数可以被修改(比如把数据都加密一番)。
请求完成后,这里就说到IRP的处理:有四种,分别是
1> 无处理,继续往下传
2> 处理之后 ,往下传
3> 处理之后, 往上传
4> 不做处理,直接丢弃
诸如这样的处理,不做任何处理,直接往下传:
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(s_nextobj[i],irp);
完成IRP后,直接下传
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i],irp);
四、模块代码
驱动入口:
#include "Driver.h"
#include <ntstrsafe.h> /////////////////////////////////////////////////
// 函数名:DriverEntry
// 功能:驱动入口
// 作者:Geons
// 时间:2016年4月14日22:02:54
///////////////////////////////////////////////// extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObj,
IN PUNICODE_STRING RegistryPath)
{
size_t i; // 注册派遣函数
for(i = 0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
{ DriverObj->MajorFunction[i] = ccpDispatch; } // 卸载驱动
DriverObj->DriverUnload = ccpUnload; // 绑定物理设备端口
ccpAttachAlloComs(DriverObj); return STATUS_SUCCESS; }
派遣函数:
// 分发函数
NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i, j; 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 IoCallDriver(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++)
{
KdPrint(("comcap:Send Data: %2x\r\n", buf[j]));
} } // 下发IRP
IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(s_nextobj[i], irp); }
} irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp, IO_NO_INCREMENT); // 返回IRP成功状态
return STATUS_SUCCESS; }
驱动卸载:
// 驱动卸载
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]); } } interval.QuadPart = (5*1000*DELAY_ONE_MILLISECOND); KeDelayExecutionThread(KernelMode, FALSE, &interval); for(int i =0; i<CCP_MAX_COM_ID; i++)
{
if(s_nextobj[i] != NULL)
{ IoDeleteDevice(s_fltobj[i]); }
}
}
创建过滤设备
// 创建过滤设备,虚拟设备和真实设备的参数保持一致
NTSTATUS ccpAttachDevice(PDRIVER_OBJECT driverObj,
PDEVICE_OBJECT oldDev,
PDEVICE_OBJECT *fltObj,
PDEVICE_OBJECT *next)
{
NTSTATUS status;
PDEVICE_OBJECT topDev = NULL; status = IoCreateDevice(driverObj,
0,
NULL,
oldDev->DeviceType,
0,
FALSE,
fltObj); if(status != STATUS_SUCCESS)
{
return status; } if(oldDev->Flags & DO_BUFFERED_IO)
{
(*fltObj)->Flags |= DO_BUFFERED_IO; }
else if(oldDev->Flags & DO_DIRECT_IO)
{
(*fltObj)->Flags |= DO_DIRECT_IO;
}
else if(oldDev->Characteristics & FILE_DEVICE_SECURE_OPEN)
{
(*fltObj)->Characteristics |= FILE_DEVICE_SECURE_OPEN; }
else
{ } (*fltObj)->Flags |= DO_POWER_PAGABLE; topDev = IoAttachDeviceToDeviceStack(*fltObj, oldDev); if(topDev == NULL)
{
IoDeleteDevice(*fltObj);
*fltObj = NULL;
status = STATUS_SUCCESS;
return status; } *next = topDev; // 设置设备已经启动
(*fltObj)->Flags = (*fltObj)->Flags & ~DO_DEVICE_INITIALIZING; return STATUS_SUCCESS; }
打开串口,获得串口对象指针
// 打开Serial端口
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; memset(name, 0, sizeof(WCHAR)*32);
RtlInitUnicodeString(&name_str, L"\\Device\\Serial0");
KdPrint(("the test serial is %w", name_str)); // 打开设备对象
*status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileObj, &devObj); // 打开文件成功后,解除文件对象的引用
if(*status == STATUS_SUCCESS)
{
ObDereferenceObject(fileObj); }
return devObj; }
绑定串口
// 绑定已有的串口
void ccpAttachAlloComs(PDRIVER_OBJECT driverObj)
{
ULONG i;
PDEVICE_OBJECT com_obj;
NTSTATUS status;
for(i =0; i<DPFLTR_TCPIP_ID;i++)
{
com_obj = ccpOpenCom(i, &status); if(com_obj == NULL)
{
continue; }
ccpAttachDevice(driverObj, com_obj, &s_fltobj[i], &s_nextobj[i]);
}
}
测试截图:
Windows内核开发之串口过滤的更多相关文章
- Windows内核开发-3-内核编程基础
Windows内核开发-3-内核编程基础 这里会深入讲解kernel内核的API.结构体.和一些定义.考察代码在内核驱动中运行的机制.最后把所有知识合在一起写一个有用的驱动. 本章学习要点: 1:通用 ...
- Windows内核开发-5-(2)-内核模式调试
Windows内核开发-5-(2)-内核模式调试 普通用户模式的调试,采取的是给进程添加一个线程来挂起断点,作为一个调试器的线程在进程中使用.照这样来类推,对操作系统调试相当于添加一个进程来限制操作系 ...
- windows内核开发环境的简易搭建
一.windows内核开发需要的软件 1.WDK 2.WinDbg 3.virtualKD 4.DebugView 5.Visual C++ 6.0 6.VMware Workstation 二.wi ...
- Windows内核开发-2-开始内核开发-2-内核开发入门
Windows内核开发-2-开始内核开发-2- 第一个驱动程序: 直接采用vs2019中的Empty WDM Driver 模块创建: 初始的项目文件夹中有一个Driver Files里面会有一个.i ...
- Windows内核开发-4-内核编程基础
Windows内核开发-4-内核编程基础 这里会构建一个简单但是完整的驱动程序和一个客户端,部署内核执行一些平时user下无法执行的操作. 将通过以下内容进行讲解: 1 介绍 2 驱动初始化 3 Cr ...
- Windows内核开发-6-内核机制 Kernel Mechanisms
Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...
- Windows内核开发-9-32位和64位的区别
Windows内核开发-9-32位和64位的区别 32位的应用程序可以完美再64位的电脑上运行,而32位的内核驱动无法再64位的电脑上运行,或者64位的驱动无法在32位的应用程序上运行.这是为什么呢. ...
- Windows内核开发-10-监听对象
Windows内核开发-10-监听对象 Windows内核除了可以监听进程,线程.dll还可以监听特定的对象和注册表.这里先讲一下监听对象. 监听对象 内核提供了一种可以监听对特定的对象类型的句柄进行 ...
- Windows内核开发-Windows内部概述-2-
Windows内部概述-2- 线程: 执行代码的实体是线程.一个线程的包含在进程里面的,线程使用进程提供的资源来运行代码. 一个线程拥有以下的内容: 1:明确的运行模式,用户态或者内核态. 2:执行的 ...
随机推荐
- 复制虚拟机vmware centos搭建集群节点过程中网络配置eth0和eth1遇到的问题以及NAT模式下虚拟机静态IP配置方法
在centos中安装完第一个虚拟机后,一般习惯通过克隆的方式创建其它虚拟机,开后vmware无法发现网卡信息,系统认为这是重新安装,所以重新创建了一个新的网卡叫eth1. 并且用IFCONFIG-a查 ...
- Cause: dx.jar is missing
Cause: dx.jar is missing 解决方案 方案一 copy dx.jar到目标编译版本 查找相应的buildToolsVersion版本下是否有dx.jar存在 如果不存在则可以co ...
- redis4.0.13主从、哨兵、集群3种模式的 Server端搭建、启动、验证
本文使用的是redis-4.0.13.tar.gz版本. 两个centos7系统虚拟机:192.168.10.140.192.168.10.150 redis各版本下载地址:http://downlo ...
- spring @Configuration的使用
参考博客:https://www.cnblogs.com/duanxz/p/7493276.html spring中的@Scope注解 https://www.cnblogs.com/loneclo ...
- Ubuntu16.04下安装Hyperledger Fabric 1.0.0
系统环境 * Ubuntu: 16.04 * Go: 1.9.2 * NodeJS: v6.12.0 * Docker: 17.09.0-ce * HyperLedger Fabric: 1.0.0 ...
- 7.6 chcount.c -- 使用逻辑与运算符
include <stdio.h> #define PERIOD '.' int main(void) { char ch; int charcount = 0; while ((ch = ...
- python作业(二)实现注册功能和登陆功能
#1.实现注册功能 输入:username.passowrd,cpassowrd #最多可以输错3次 #3个都不能为空 #用户名长度最少6位, 最长20位,用户名不能重复 #密码长度最少8位,最长15 ...
- 数据库-PLSQL登录oracle数据库卡死(未响应)解决方法
上次重装系统后重新安装了oracle和PLSQL,哪知道PLSQL登录不了,一登录就未响应,但如果不登录就不卡死,直接就进去了.在网上查了很多解决方法,都没有用. 后来在百度文库找到解决办法,重启Or ...
- 刘志梅201771010115.《面向对象程序设计(java)》第十六周学习总结
实验十六 线程技术 实验时间 2017-12-8 1.实验目的与要求 (1)当线程的run方法执行方法体中最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将 ...
- 图表相同数据会自动合并问题(finereport)
finereport中,对于图表的操作,当遇到需要显示多个重复分类下的多个值时,由于自动合并相同数据,无法达到效果反复查询手册无果后,困扰好久,终想到了一个解决的办法:1.给查询的数据添加个列序号,每 ...