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

      在“进程内存管理器中”的一个Ring0,Ring3层通信问题,之前也见过这样的代码,这次拆分出来详细总结一下。

      先通过CreateFile函数得到设备句柄,CreateFile函数原型:

      

    HANDLE CreateFile(
        LPCTSTR lpFileName,                         // 文件名/设备路径 设备的名称
        DWORD dwDesiredAccess,                      // 访问方式
        DWORD dwShareMode,                          // 共享方式
        LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指针
        DWORD dwCreationDisposition,                // 创建方式
        DWORD dwFlagsAndAttributes,                 // 文件属性及标志
        HANDLE hTemplateFile                        // 模板文件的句柄
    );

    打开:createFile

    关闭:closehandle

    与普通文件名有所不同,设备驱动的“文件名”(常称为“设备路径”)形式固定为“\.DeviceName”(注意写法为“\\.\DeviceName”),DeviceName必须与设备驱动程序内定义的设备名称一致。

    在“进程内存管理器”中:

    Ring0层的kProcessMemory.h

    #define DEVICE_NAME L"\Device\KProcessMemoryDeviceName"
    #define LINK_NAME L"\DosDevices\KProcessMemoryLinkName"

    Ring3层的ProcessMemoryManager.cpp

    OpenDeviceObject(L"\\.\KProcessMemoryLinkName");
    
    BOOL OpenDeviceObject(LPCTSTR DeviceFullPathData)
    {
    m_DeviceHandle = CreateFile(DeviceFullPathData,
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
    
    if (m_DeviceHandle == INVALID_HANDLE_VALUE)
    {
    return FALSE;
    }
    
    return TRUE;
    
    }

    可以看到在kProcessMemory.h中define了一个驱动设备名和一个符号链接名

    驱动设备名是调用IoCreateDevice时使用的,IoCreateDevice函数原型:

    NTSTATUS IoCreateDevice(
      _In_      PDRIVER_OBJECT DriverObject,
      _In_      ULONG DeviceExtensionSize,
      _In_opt_  PUNICODE_STRING DeviceName,
      _In_      DEVICE_TYPE DeviceType,
      _In_      ULONG DeviceCharacteristics,
      _In_      BOOLEAN Exclusive,
      _Out_     PDEVICE_OBJECT *DeviceObject
    );

    驱动程序中调用IoCreateDevice函数:

    RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
    Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

    关于在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

    winobj和DeviceTree可以用来查看这些信息。

    关于驱动设备名和符号链接名,可以参考这篇博客:

    http://www.cnblogs.com/findumars/p/5636505.html

    接着回到CreateFile函数上来,它的第二个参数,dwDesireAceess访问方式,一般设置为0或GENERIC_READ|GENERIC_WRITE共享方式参数设置为FILE_SHARE_READ|FILE_SHARE_WRITE创建方式参数设置为OPEN_EXISTING,其它参数一般设置为0或NULL。

    Ring3层的CreateFile函数获取了设备句柄后,将使用DeviceIoControl函数向指定的设备驱动发送一个IO控制码,驱动程序通过这个控制码来完成特定的工作。该函数原型如下:

    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将进行阻塞调用;否则,应在编程时按异步操作设计
    );

      先介绍IO控制码,驱动程序可以通过CTL_CODE宏来组合定义一个控制码,并在IRP_MJ_DEVICE_CONTROL的实现中进行控制码的操作。在驱动层,IoStackLocation->Parameters.DeviceIoControl.IoControlCode表示了这个控制码。发送不同的控制码,可以调用设备驱动程序的不同类型的功能。在头文件winioctl.h中,预定义的标准设备控制码,都以IOCTL或FSCTL开头。例如,IOCTL_DISK_GET_DRIVE_GEOMETRY是对物理驱动器取结构参数(介质类型、柱面数、每柱面磁道数、每磁道扇区数等)的控制码,FSCTL_LOCK_VOLUME是对逻辑驱动器的卷加锁的控制码。

    lpInBuffer

    由用户层发送的缓冲区数据。在“进程内存管理器“程序中,我们是通过进程ID来查询进程内存,故传入的是进程ID.在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表。

    传输类型 位置
    METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
    METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
    METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
    METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer

    nInBufferSize

    由用户层发送的缓冲区大小。在驱动层,这个值是IoStackLocation->Parameters.DeviceIoControl.InputBufferLength

    lpOutBuffer

    由用户层指定,用于接收驱动层返回数据的缓冲区。在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表。

    传输类型 位置
    METHOD_IN_DIRECT irp->MdlAddress
    METHOD_OUT_DIRECT irp->MdlAddress
    METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
    METHOD_NEITHER irp->UserBuffer

    nOutBufferSize

    由用户层指定,用于接收驱动层返回数据的缓冲区大小。在驱动层,这个值是IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength

    lpBytesReturned

    由用户层指定,用于接收驱动层实际返回数据大小。在驱动层,这个值是irp->IoStatus->Information

    lpOverlapped

    用于异步操作。

    程序中的派遣函数:

    
    

    #define CTL_QUERY_PROCESS_MEMORY
    CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_NEITHER,FILE_ANY_ACCESS)

    
    

    #define CTL_READ_PROCESS_MEMORY
    CTL_CODE(FILE_DEVICE_UNKNOWN,0x831,METHOD_NEITHER,FILE_ANY_ACCESS)

    
    

    #define CTL_WRITE_PROCESS_MEMORY
    CTL_CODE(FILE_DEVICE_UNKNOWN,0x832,METHOD_NEITHER,FILE_ANY_ACCESS)

    ......

    RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
    Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

    ......

    RtlInitUnicodeString(&LinkName, LINK_NAME);
    Status = IoCreateSymbolicLink(&LinkName, &DeviceName);

    ......

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlDispatch;

    NTSTATUS DeviceControlDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
    {
    
        NTSTATUS Status = STATUS_SUCCESS;
        ULONG    IoControlCode = 0;
        ULONG    InputLength = 0;
        SIZE_T   OutputLength = 0;
        PVOID    InputData = NULL;
        PVOID    OutputData = NULL;
    
        PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
        IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
        //BufferIO
        //InputData = OutputData = Irp->AssociatedIrp.SystemBuffer;  
        /*
        直接方式DO_DIRECT_IO / 非直接方式(缓冲方式)DO_BUFFERD_IO
            1) 在buffered(AssociatedIrp.SystemBuffer)方式中,I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。
               I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。
            2) 在direct(MdlAddress)方式中,I/O管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。
               因此你的驱动程序将使用MDL工作。
            3) 在neither(UserBuffer)方式中,I/O管理器仅简单地把用户模式的虚拟地址传递给你。
               而使用用户模式地址的驱动程序应十分小心。
        */
        //Neither方式提高了通信效率,但是不够安全,在读写之前应使用ProbeForRead和ProbeForWrite函数探测地址是可读和可写
        //详见eDiary笔记中“Driver——DeviceIoControl函数与IoControlCode”
        InputData = IoStackLocation->Parameters.DeviceIoControl.Type3InputBuffer;//得到Ring3的输入缓冲区地址
        OutputData = Irp->UserBuffer;                                            //得到Ring3的输出缓冲区地址
        InputLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
        OutputLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
    
        switch (IoControlCode)   //IO控制码
        {
        case CTL_QUERY_PROCESS_MEMORY:
        {
            if (!MmIsAddressValid(OutputData))
            {
                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                Irp->IoStatus.Information = 0;
                break;
            }
            if (InputLength != sizeof(ULONG) || InputData == NULL)
            {
                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                Irp->IoStatus.Information = 0;
                break;
            }
            __try
            {
    
                ProbeForWrite(OutputData, OutputLength, 1);  //检测内存是否可写,这个函数需要在用户模式下使用,详见MSDN:
                //If Irp->RequestorMode = KernelMode, the Irp->AssociatedIrp.SystemBuffer and Irp->UserBuffer fields do not contain user-mode addresses,
                //and a call to ProbeForWrite to probe a buffer pointed to by either field will raise an exception.
    
                Status = RtlQueryVirtualMemory(*(PULONG)InputData, OutputData, OutputLength);
                Irp->IoStatus.Information = 0;
                Irp->IoStatus.Status = Status;
    
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                Irp->IoStatus.Information = 0;
                Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            }
            break;
        }
        case CTL_READ_PROCESS_MEMORY:
        {
    
            if (!MmIsAddressValid(OutputData) || OutputLength>MAX_LENGTH)
            {
                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                Irp->IoStatus.Information = 0;
                break;
            }
    
            if (InputLength != sizeof(READ_OPERATION) || InputData == NULL)
            {
                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                Irp->IoStatus.Information = 0;
    
                break;
            }
            __try
            {
    
                ProbeForWrite(OutputData, OutputLength, 1);
                Status = RtlReadVirtualMemory(InputData, OutputData, OutputLength);
                Irp->IoStatus.Information = 0;
                Irp->IoStatus.Status = Status;
    
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                Irp->IoStatus.Information = 0;
                Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            }
    
            break;
        }
        case CTL_WRITE_PROCESS_MEMORY:
        {
    
            if (InputLength < sizeof(WRITE_OPERATION) || InputData == NULL)
            {
                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                Irp->IoStatus.Information = 0;
    
                break;
            }
            __try
            {
                Status = RtlWriteVirtualMemory(InputData);
                Irp->IoStatus.Information = 0;
                Irp->IoStatus.Status = Status;
    
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                Irp->IoStatus.Information = 0;
                Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            }
            break;
        }
        default:
        {
    
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            break;
        }
        }
    
    
        IoCompleteRequest(Irp, IO_NO_INCREMENT);       //将Irp返回给IO管理器
        return Status;
    }
  • 相关阅读:
    费曼学习法
    Ubuntu修改系统默认编码
    如何在Ubuntu 18.04上安装和使用PostgreSQL
    Bash简介 & Bash是如何处理命令的
    ubuntu环境变量的三种设置方法
    psql 工具详细使用介绍
    使用ubuntu server18.04 搭建odoo12运行环境
    Ubuntu修改时区和更新时间
    Ubuntu18.04修改apt-get源
    对表内数据间隔特定的长度求和
  • 原文地址:https://www.cnblogs.com/lsh123/p/6890797.html
Copyright © 2011-2022 走看看