• 《Windows内核安全编程》第三章 串口的过滤


    1. 细碎知识点

    1. Windows中串口设备具有固定的名字,第一个串口叫"\Device\Serial0",第二个叫"\Device\Serial1"
    2. 快速构造UNICODE_STRING:UNICODE_STRING com_name = RTL_CONSTANT_STRING(L"\Device\Serial0");
    3. 过滤设备一般不需要名称,IoCreateDevice传入NULL即可
    4. 一个驱动中生成从属于其他驱动的设备对象,理论上是可以的
    5. IRP是上层设备之间传递请求的常见数据结构,但并非唯一的数据结构
    6. 串口设备接受到的请求都是IRP
    7. 设备卸载的相关API:IoDetachDevice、IoDeleteDevice、KeDelayExecutionThread

    2. 设备绑定的API

    IoAttachDevice绑定有名设备
    NTSTATUS IoAttachDevice (
    	IN PDEVICE_OBJECT SourceDevice,
    	IN PUNICODE_STRING TargetDevice,
    	OUT PDEVICE_OBJECT *AttachDevice
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SourceDevice:调用者生成的用来过滤的虚拟设备
    TargetDevice字符串,要被绑定的设备的名字
    AttachDevice:返回被绑定的设备对象的指针

    IoAttachDevice只能用来绑定具有名称的设备,且总是会绑定设备栈上最顶层的那个设备

    IoAttachDeviceToDeviceStack、IoAttachDeviceToDeviceStackSafe绑定无名设备
    NTSTATUS IoAttachDeviceToDeviceStackSafe( 
    	IN PDEVICE_OBJECT SourceDevice, // 过滤设备
    	IN PDEVICE_OBJECT TargetDevice, // 要被绑定的设备栈中的设备
    	IN OUT PDEVICE_OBJECT *AttachedToDeviceObject// 返回最终被绑定的设备
    ); 
    
    PDEVICE_OBJECT IoAttachDeviceToDeviceStack( 
    	IN PDEVICE_OBJECT SourceDevice, 
    	IN PDEVICE_OBJECT TargetDevice 
    ); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    TargetDevice:指针,要被绑定的设备的指针
    返回值:返回最终被绑定的设备

    IoAttachDeviceToDeviceStack只能再windows2000下使用,推荐使用IoAttachDeviceToDeviceStackSafe

    3. 生成过滤设备并绑定

    NTSTATUS IoCreateDevice( 
    	IN PDRIVER_OBJECT DriverObject, 			//本驱动的驱动对象,DriverEntry传入
    	IN ULONG DeviceExtensionSize, 				//设备扩展
    	IN PUNICODE_STRING DeviceName OPTIONAL, 	//设备名,过滤驱动一般传入NULL即可
    	IN DEVICE_TYPE DeviceType, 					//设备类型,与要过滤的设备保持一致
    	IN ULONG DeviceCharacteristics, 			//凭经验直接填0
    	IN BOOLEAN Exclusive, 						//是否独占,一般FALSE
    	OUT PDEVICE_OBJECT *DeviceObject 			//输出创建好的设备对象
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    绑定示例代码

    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); 
    	*fltobj = NULL; 
    	status = STATUS_UNSUCCESSFUL; 
    	return status; 
    } 
    *next = topdev; 
    // 设置这个设备已经启动
    (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING; 
    return STATUS_SUCCESS; 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    从名字获得设备对象

    NTSTATUS 
    IoGetDeviceObjectPointer( 
    	IN PUNICODE_STRING ObjectName, 			//要获取设备对象的设备名
    	IN ACCESS_MASK DesiredAccess, 			//期望获得的权限
    	OUT PFILE_OBJECT *FileObject, 			//获得设备对象同时获得的文件对象
    	OUT PDEVICE_OBJECT *DeviceObject 		//获得的设备对象
    ); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    FileObject在获得后不用时,必须通过ObDereferenceObject解除引用,否则内存泄漏

    IRP请求的区分

    IRP都具有一个主功能号和一个次功能号

    // 这里的 irpsp 称为 IRP 的栈空间,IoGetCurrentIrpStackLocation 获得当前栈空间
    // 栈空间是非常重要的数据结构
    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp); 
    if(irpsp->MajorFunction == IRP_MJ_WRITE) 
    { 
     // 如果是写…
    } 
    else if(irpsp->MajorFunction == IRP_MJ_READ) 
    { 
     // 如果是读…
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    IRP的过滤行为

    1. 请求被允许通过,过滤不做任何事情
      // 跳过当前栈空间
      IoSkipCurrentIrpStackLocation(irp); 
      // 将请求发送到对应的真实设备。记得我们前面把真实设备都保存在 s_nextobj 
      // 数组中。那么这里 i 应该是多少?这取决于现在的 IRP 发到了哪个
      // 过滤设备上。后面讲解分发函数时读者将了解到这一点
      status = IoCallDriver(s_nextobj[i],irp); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 请求直接被否决,过滤禁止这个请求通过
    3. 过滤完成了请求,但对请求进行了修改

    从串口中获取IRP传递的数据

    //缓冲区的获取
    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; 
    
    //缓冲区大小的获取
    ULONG length = irpsp->Parameters.Write.Length;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    串口过滤的完整分发函数

    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_PARAMETER; 
    	IoCompleteRequest(irp,IO_NO_INCREMENT); 
    	return STATUS_SUCCESS; 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    动态卸载串口过滤

    #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) 
    		IoDetachDevice(s_nextobj[i]); 
    	} 
    	// 睡眠 5 秒。等待所有 IRP 处理结束
    	interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND); 
    	KeDelayExecutionThread(KernelMode,FALSE,&interval); 
    	// 删除这些设备
    	for(i=0;i<CCP_MAX_COM_ID;i++) 
    	{ 
    		if(s_fltobj[i] != NULL) 
    		IoDeleteDevice(s_fltobj[i]); 
    	} 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    如何使用python绘制ROC曲线?
    SQL语法基础-其他函数V
    Android Studio的代码笔记--关于类的注释和函数的注释,以及USER值的修改。
    Java 类加载器 详解
    linux笔记(2):vscode插件remote WSL远程使用交叉编译工具链(全志D1-H)
    Windows11搭建FTP服务器后,无法访问,提示:ftp: connect :连接超时
    药师帮再冲刺上市:研发远低于营销,债务高企,张步镇为董事长
    3D孪生场景搭建:模型阵列摆放
    有效的字母异位词 C
    开源机密计算平台:蓬莱-OpenHarmony
  • 原文地址:https://blog.csdn.net/shang_cm/article/details/127541502