zoukankan      html  css  js  c++  java
  • 应用程序与驱动程序通信 DeviceIoControl

      之前写过一篇关于通过DeviceIoControl函数来使应用程序与驱动程序通信的博客,这次再通过这个完整的代码来简要疏通总结一下。

       这种通信方式,就是驱动程序和应用程序自定义一种IO控制码,然后调用DeviceIoControl函数IO管理器产生一个MajorFunction 为IRP_MJ_DEVICE_CONTROL(DeviceIoControl函数会产生此IRP),MinorFunction 为自己定义的控制码的IRP,系统就调用相应的处理IRP_MJ_DEVICE_CONTROL的派遣函数,你在派遣函数中判断MinorFunction ,是自定义的控制码你就进行相应的处理

      一.先谈一下这个定义IO控制码 ,其实可以看作是一种通信协议。

           看看CTL_CODE原型:

      #define CTL_CODE( DeviceType, Function, Method, Access ) (
      ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
      )

       可以看到,这个宏四个参数,自然是一个32位分成了4部分,高16位存储设备类型,14~15位访问权限,2~13位操作功能,最后0,1两位就是确定缓冲区是如何与I/O和文件系统数据缓冲区进行数据传递方式,最常见的就是METHOD_BUFFERED。

           自定义CTL_CODE:

      #define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

      IOCTL_Device_Function:生成的IRP的MinorFunction

      DeviceType:设备对象的类型。设备类型可参考:http://blog.csdn.net/liyun123gx/article/details/38058965

      Function :自定义的IO控制码。自己定义时取0x800到0xFFF,因为0x0到0x7FF是微软保留的。

      Method :数据的操作模式。

                  METHOD_BUFFERED:缓冲区模式

                  METHOD_IN_DIRECT:直接写模式

                  METHOD_OUT_DIRECT:直接读模式

                  METHOD_NEITHER :Neither模式

    Access:访问权限,可取值有:

                FILE_ANY_ACCESS:表明用户拥有所有的权限

                FILE_READ_DATA:表明权限为只读

                FILE_WRITE_DATA:表明权限为可写

                也可以 FILE_WRITE_DATA | FILE_READ_DATA:表明权限为可读可写,但还没达到FILE_ANY_ACCESS的权限。

      

      

           继续介绍这个缓冲区数据传递方式Method:

      Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:
      #define METHOD_BUFFERED                0  
      #define METHOD_IN_DIRECT               1  
      #define METHOD_OUT_DIRECT              2  
      #define METHOD_NEITHER                  3  


           (1)如果使用METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer缓冲,因此这种方式的通信比较安全

      METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲

           METHOD_BUFFERED方式(借图):

           


      (2)如果使用METHOD_IN_DIRECTMETHOD_OUT_DIRECT方式,表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。

      METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。
    这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
     
      METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问
      8METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。

      METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式(借图)

           


      (3)如果使用METHOD_NEITHER方式,"其他方式",虽然通信的效率提高了,但是不够安全。驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。

      METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址

      驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。
      由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

      METHOD_NEITHER方式(借图)

      


      二 .定义驱动设备名,符号链接名
           定义好了IO控制码CTL_CODE,第二步驱动程序还要准备驱动设备名和符号链接名。     

        关于在Ring0层中要设置驱动设备名的同时还要设置符号链接名的原因,是因为只有符号链接名才可以被用户模式下的应用程序识别

        windows下的设备是以"Device[设备名]”形式命名的。例如磁盘分区的c盘,d盘的设备名称就是"DeviceHarddiskVolume1”,"DeviceHarddiskVolume2”, 当然也可以不指定设备名称。                               如果IoCreateDevice中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。例如"Device0000001"。Device[设备名],不容易记忆,通常符号链接可以理解为设备的别名,更重要的是设备名,只能被内核模式下的其他驱动所识别,而别名可以被用户模式下的应用程序识别,例如c盘,就是名为"c:"的符号链接,其真正的设备对象是"DeviceHarddiskVolume1”,所以在写驱动时候,一般我们创建符号链接,即使驱动中没有用到,这也算是一个好的习惯吧。

        驱动中符号链接名是这样写的
        L"\??\HelloDDK" --->??HelloDDK

        或者
        L"\DosDevices\HelloDDK"--->DosDevicesHelloDDK


        在应用程序中,符号链接名:
        L"\\.\HelloDDK"-->\.HelloDDK

        DosDevices的符号链接名就是??, 所以"\DosDevices\XXXX"其实就是\??\XXXX

                  

    #define DEVICE_OBJECT_NAME  L"\Device\BufferedIODeviceObjectName"
    //设备与设备之间通信
    #define DEVICE_LINK_NAME    L"\DosDevices\BufferedIODevcieLinkName"
    //设备与Ring3之间通信
    

      三.将符号链接名与设备对象名称关联 ,等待IO控制码

        驱动程序要做的最后一步,先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink符号链接名与设备对象名称关联 ,大功告成,等待IO控制码。

        

            //创建设备对象名称
    	RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
    	//创建设备对象
    	Status = IoCreateDevice(DriverObject,NULL,
    		&DeviceObjectName,
    		FILE_DEVICE_UNKNOWN,
    		0, FALSE,
    		&DeviceObject);
    	if (!NT_SUCCESS(Status))
    	{
    		return Status;
    	}
    
    	//创建设备连接名称
    	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
    	//将设备连接名称与设备名称关联 
    	Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
    
    	if (!NT_SUCCESS(Status))
    	{
    		IoDeleteDevice(DeviceObject);
    		return Status;
    	}        
    

      

      四.应用程序获取设备句柄,发送IO控制码。

        驱动程序铺垫打理好之后,应用程序就可以由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个DeviceHandle发送控制码了。

        先看看这两个函数:

       

    BOOL WINAPI DeviceIoControl(
      _In_         HANDLE hDevice,       //CreateFile函数打开的设备句柄
      _In_         DWORD dwIoControlCode,//自定义的控制码
      _In_opt_     LPVOID lpInBuffer,    //输入缓冲区
      _In_         DWORD nInBufferSize,  //输入缓冲区的大小
      _Out_opt_    LPVOID lpOutBuffer,   //输出缓冲区
      _In_         DWORD nOutBufferSize, //输出缓冲区的大小
      _Out_opt_    LPDWORD lpBytesReturned, //实际返回的字节数,对应驱动程序中pIrp->IoStatus.Information。
      _Inout_opt_  LPOVERLAPPED lpOverlapped //重叠操作结构指针。同步设为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计
    );
    
    
    
    
    
    
    
    HANDLE CreateFile(
      LPCTSTR lpFileName,                         //打开的文件名
      DWORD dwDesiredAccess,                    //访问权限
      DWORD dwShareMode,                      //共享模式
      LPSECURITY_ATTRIBUTES lpSecurityAttributes,   //安全属性
      DWORD dwCreationDisposition,               //文件存在与不存在时的文件创建模式
      DWORD dwFlagsAndAttributes,                //文件属性设定(隐藏、只读、压缩、指定为系统文件等)
      HANDLE hTemplateFile                       //文件副本句柄
    );
    

      

      最后总结一下DeviceIoControl的通信流程:

        1.驱动程序和应用程序自定义IO控制码 (CTL_CODE宏 四个参数,32位,4部分,存储设备类型,访问权限,操作功能,缓冲区数据传递方式(四种))

        2.驱动程序定义驱动设备名,符号链接名, 将符号链接名与设备对象名称关联 ,等待IO控制码(IoCreateDevice,IoCreateSymbolicLink)

        3.应用程序由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个设备句柄发送控制码给派遣函数

      源代码:

      BufferedIO.h

      

    #pragma once
    #include <ntifs.h>
    
    
    #define CTL_SYS 
    	CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
    
    
    #define DEVICE_OBJECT_NAME  L"\Device\BufferedIODeviceObjectName"
    //设备与设备之间通信
    #define DEVICE_LINK_NAME    L"\DosDevices\BufferedIODevcieLinkName"
    //设备与Ring3之间通信
    VOID DriverUnload(PDRIVER_OBJECT DriverObject);
    NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);
    NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);
    

      

    BufferedIO.c

    #include "BufferedIO.h"
    
    
    NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
    {
    	NTSTATUS Status = STATUS_SUCCESS;
    	PDEVICE_OBJECT  DeviceObject = NULL;
    	UNICODE_STRING  DeviceObjectName;
    	UNICODE_STRING  DeviceLinkName;
    	ULONG			i;
    	//   栈
    	//   堆
    	//   全局(global Static Const)
    	DriverObject->DriverUnload = DriverUnload;
    
    	//创建设备对象名称
    	RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
    
    	//创建设备对象
    	Status = IoCreateDevice(DriverObject,NULL,
    		&DeviceObjectName,
    		FILE_DEVICE_UNKNOWN,
    		0, FALSE,
    		&DeviceObject);
    	if (!NT_SUCCESS(Status))
    	{
    		return Status;
    	}
    	//创建设备连接名称
    	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
    
    	//将设备连接名称与设备名称关联 
    	Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
    
    	if (!NT_SUCCESS(Status))
    	{
    		IoDeleteDevice(DeviceObject);
    		return Status;
    	}
    	//设计符合我们代码的派遣历程	
    	for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
    	{
    		DriverObject->MajorFunction[i] = PassThroughDispatch;   //函数指针
    	}
    	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;
    	return Status;
    }
    //派遣历程 
    NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject,PIRP Irp)
    {
    	Irp->IoStatus.Status = STATUS_SUCCESS;     //LastError()
    	Irp->IoStatus.Information = 0;             //ReturnLength 
    	IoCompleteRequest(Irp, IO_NO_INCREMENT);   //将Irp返回给Io管理器
    	return STATUS_SUCCESS;
    }
    NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)
    {
    	NTSTATUS Status;
    	ULONG_PTR Informaiton = 0;
    	PVOID InputData = NULL;
    	ULONG InputDataLength = 0;
    	PVOID OutputData = NULL;
    	ULONG OutputDataLength = 0;
    	ULONG IoControlCode = 0;
    	PIO_STACK_LOCATION  IoStackLocation = IoGetCurrentIrpStackLocation(Irp);  //Irp堆栈	
    	IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
    	InputData  = Irp->AssociatedIrp.SystemBuffer;
    	OutputData = Irp->AssociatedIrp.SystemBuffer;
    	InputDataLength  = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
    	OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
    	switch (IoControlCode)
    	{
    	case CTL_SYS:
    	{
    		if (InputData != NULL&&InputDataLength > 0)
    		{
    			DbgPrint("%s
    ", InputData);
    		}
    		if (OutputData != NULL&&OutputDataLength >= strlen("Ring0->Ring3") + 1)
    		{
    			memcpy(OutputData, "Ring0->Ring3", strlen("Ring0->Ring3") + 1);
    			Status = STATUS_SUCCESS;
    			Informaiton = strlen("Ring0->Ring3") + 1;
    		}
    		else
    		{
    			Status = STATUS_INSUFFICIENT_RESOURCES;   //内存不够
    			Informaiton = 0;
    		}
    		break;
    	}
    	default:
    		break;
    	}
    	Irp->IoStatus.Status = Status;             //Ring3 GetLastError();
    	Irp->IoStatus.Information = Informaiton;
    	IoCompleteRequest(Irp, IO_NO_INCREMENT);  //将Irp返回给Io管理器
    	return Status;                            //Ring3 DeviceIoControl()返回值
    }
    VOID DriverUnload(PDRIVER_OBJECT DriverObject)
    {
    	UNICODE_STRING  DeviceLinkName;
    	PDEVICE_OBJECT	v1 = NULL;
    	PDEVICE_OBJECT  DeleteDeviceObject = NULL;
    	
    	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
    	IoDeleteSymbolicLink(&DeviceLinkName);
    
    	DeleteDeviceObject = DriverObject->DeviceObject;
    	while (DeleteDeviceObject != NULL)
    	{
    		v1 = DeleteDeviceObject->NextDevice;
    		IoDeleteDevice(DeleteDeviceObject);
    		DeleteDeviceObject = v1;
    	}
    }
    

      

    IO.cpp

    // 缓冲区IO.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <windows.h>
    #define DEVICE_LINK_NAME    L"\\.\BufferedIODevcieLinkName"
    
    
    #define CTL_SYS 
    	CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
    int main()
    {
    	HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,
    		GENERIC_READ | GENERIC_WRITE,
    		FILE_SHARE_READ | FILE_SHARE_WRITE,
    		NULL,
    		OPEN_EXISTING,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);
    	if (DeviceHandle==INVALID_HANDLE_VALUE)
    	{
    		return 0;
    	}
    	char BufferData = NULL;
    	DWORD ReturnLength = 0;
    	BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
    		"Ring3->Ring0",
    		strlen("Ring3->Ring0")+1,
    		(LPVOID)BufferData,
    		0,
    		&ReturnLength,
    		NULL);
    	if (IsOk == FALSE)
    	{
    		int LastError = GetLastError();
    
    		if (LastError == ERROR_NO_SYSTEM_RESOURCES)
    		{
    			char BufferData[MAX_PATH] = { 0 };
    			IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
    				"Ring3->Ring0",
    				strlen("Ring3->Ring0") + 1,
    				(LPVOID)BufferData,
    				MAX_PATH,
    				&ReturnLength,
    				NULL);
    
    			if (IsOk == TRUE)
    			{
    				printf("%s
    ", BufferData);
    			}
    		}
    	}
    	if (DeviceHandle != NULL)
    	{
    		CloseHandle(DeviceHandle);
    		DeviceHandle = NULL;
    	}
    	printf("Input AnyKey To Exit
    ");
    
    	getchar();
        return 0;
    }
    

      

           

  • 相关阅读:
    DNS智能双向解析怎么做?
    高防服务器如何选择性价比最高?
    20192416 实验四《Python程序设计》综合实践报告
    20192416实验三 《Python程序设计》实验报告
    20192416 《Python程序设计》实验二报告
    20192416 《Python程序设计》实验一报告
    2019-2020-1学期 20192416《网络空间安全专业导论》第十二周学习总结
    2019-2020-1学期 20192416《网络空间安全专业导论》第十一周学习总结
    2019-2020-1学期 20192416《网络空间安全专业导论》第十周学习总结
    2019-2020-1学期 20192416《网络空间安全专业导论》第九周学习总结
  • 原文地址:https://www.cnblogs.com/lsh123/p/7354573.html
Copyright © 2011-2022 走看看