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]);
	}
}

测试截图:

时间: 2024-10-13 22:22:50

Windows内核开发之串口过滤的相关文章

构建WDK 8.1 Update内核开发环境

Windows内核开发总算有了“官方”的IDE,调试也集成到了Visual Studio 2013,应该简单些了 一.开发环境构建 工具: 1.Windows 8.1 x64 2.WDK 8.1 Update (for Windows 8.1, 8, and 7 drivers) 3.Visual Studio 2013 操作步骤: 1.下载安装Visual Studio 2013 2.下载安装WDK 8.1 Update 3.下载安装符号文件(Windows 7_x86),设置环境变量 _NT

寒江独钓Windows内核编程——串口过滤

一.过滤的概念: 过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真是驱动程序,就加入了新的功能. 1.1 设备绑定的内核API 进行过滤的最主要的方法是对一个设备对象(Device Object)进行绑定.通过编程可以生成一个虚拟设备对象,并“绑定”(Attach)在一个真实的设备上.一旦绑定,则本来操作系统发送给真实设备的请求,就会首先发送到这个虚拟设备. 在WDK中,有多个内核API能实现绑定的功能.以下三个绑定API是从WDK

Windows内核安全与驱动开发

这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定C语言和操作系统基础知识的编程爱好者阅读. 内容简介 本书的前身是<天书夜读--从汇编语言到Windows内核编程>和<寒江独钓--Windows内核安全编程>.与Windows客户端安全软件开发相关的驱动程序开发是本书的主题.书中的程序使用环境从32位到64位,从Windows XP

Windows 驱动开发基础(九)内核函数

Windows 驱动开发基础系列,转载请标明出处:http://blog.csdn.net/ikerpeng/article/details/38849861 这里主要介绍3类Windows的内核函数:字符串处理函数,文件操作函数, 注册表读写函数.(这些函数都是运行时函数,所以都有Rtl字样) 1 字符串处理函数 首先驱动程序中,常用的字符串包括4种:CHAR (打印的时候注意小写%s), WCHAR(打印的时候注意大写%S), ANSI_STRING, UNICODE_STRING.后面两种

Windows驱动开发(中间层)

Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385.1) 地址:https://msdn.microsoft.com/en-us/windows/hardware/hh852365 2.下载InstDrv软件(用于安装.启动.停止.卸载驱动) 界面如下: 注:srvinstw.exe 也可以安装.卸载sys文件,但需要手动开启.关闭,即在cmd命令窗口下执行

Windows驱动开发(一)

笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓--内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程序可分为两类三种: 第一类:传统型驱动 传统型驱动的特点就是所有的IRP都需要自己去处理,自己实现针对不同IRP的派发函数.其可以分为以下两种: 1. Nt式驱动:此驱动通过注册系统服务来加载,并且不支持即插即用功能(即没有处理IRP_MJ_PNP这个IRP). 2. WDM驱动:此驱动不通过注册系统服务来加

Windows Kernel Way 1:Windows内核调试技术

掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功之后便可以调试,此时即可以调试Windows内核启动过程,又可以在Windows启动之后调试某内核组件或应用程序.或使用Windbg的Kernel debugging of the local mechine功能,在Windows系统完全启动之后,调试Windows内核组件或应用程序.这种方式需要配

Windows驱动开发(二)

本节主要介绍驱动开发的一些基础知识. 1. 驱动程序的基本组成 1.1. 最经常见到的数据结构 a. DRIVER_OBJECT驱动对象 [cpp] view plaincopy // WDK中对驱动对象的定义 // 每个驱动程序都会有一个唯一的驱动对象与之对应 // 它是在驱动加载时被内核对象管理程序创建的 typedef struct _DRIVER_OBJECT { CSHORT Type; CSHORT Size; // // The following links all of the

【转】Windows驱动开发如何入门

1.http://blog.csdn.net/charlessimonyi/article/details/50904854 (2016年03月16日 14:55:36) 2. 搞Windows驱动开发是一件痛苦的事情,特别是初学Windows驱动开发.有的人觉得Windows驱动开发就是把开发包WDK下载下来,然后只要掌握了C/C++语言,接下来无非就是类库调来调去,像调用MFC.QT之类的库那样,看着书和MSDN上的文档来就行了.等真正接触以后才发现根本不是那么一回事,痛苦源于以下几点: 痛