Ring3/Ring0的四种通信方式

来源:互联网 发布:算法结构 编辑:程序博客网 时间:2024/05/18 15:06

21.1.5  DeviceIoControl函数与IoControlCode

打开驱动设备后,Ring3还要和驱动进行通讯或调用驱动的派遣例程,这需要用到一个非常重要的函数:DeviceIoControl。

  1. BOOL DeviceIoControl(  
  2.   HANDLE hDevice,           //设备句柄  
  3.   DWORD dwIoControlCode,            //Io控制号  
  4.   LPVOID lpInBuffer,            //输入缓冲区指针  
  5.   DWORD nInBufferSize,          //输入缓冲区字节数  
  6.   LPVOID lpOutBuffer,           //输出缓冲区指针  
  7.   DWORD nOutBufferSize,         //输出缓冲区字节数  
  8.   LPDWORD lpBytesReturned,      //返回输出字节数  
  9.   LPOVERLAPPED lpOverlapped     //异步调用时指向的OVERLAPPED指针  
  10. );  

该函数共有8个参数,hDevice是要通信的设备句柄;dwIoControlCode是Io控制号;lpInBuffer是输入缓冲区指针;nInBufferSize是输入缓冲区字节数;lpOutBuffer是输出缓冲区指针;nOutBufferSize是输出缓冲区字节数;lpBytesReturned是返回输出字节数;lpOverlapped是异步调用时指向的OVERLAPPED指针。

其中的第二个参数IoControlCode尤为重要,由宏CTL_CODE构造而成:

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

IoControlCode由四部分组成:DeviceType、Access、Function、Method,如图21.1.11所示。

 图21.1.11  IoControlCode的四个组成部分

DeviceType表示设备类型;

Access表示对设备的访问权限;

Function表示设备IoControl的功能号,0~0x7ff为微软保留,0x800~0xfff由程序员自己定义;

Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:

  1. #define METHOD_BUFFERED                0  
  2. #define METHOD_IN_DIRECT               1  
  3. #define METHOD_OUT_DIRECT              2  
  4. #define METHOD_NEITHER                  3  

最值得关注的也就是Method,如果使用了METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。

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

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

21.1.5节中提到了Ring3/Ring0通信的四种内存访问方式分别为:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT和METHOD_NEITHER。

METHOD_BUFFERED可称为"缓冲方式",是指Ring3指定的输入、输出缓冲区的内存读和写都是经过系统的"缓冲",具体过程如图21.1.12所示。

这种方式下,首先系统会将Ring3下指定的输入缓冲区(UserInputBuffer)数据,按指定的输入长度(InputBufferLen)复制到Ring0中事先分配好的缓冲内存(SystemBuffer,通过pIrp->AssociatedIrp.SystemBuffer得到)中。驱动程序就可以将SystemBuffer视为输入数据进行读取,当然也可以将SystemBuffer视为输出数据的缓冲区,也就是说SystemBuffer既可以读也可以写。驱动程序处理完后,系统会按照pIrp->IoStatus->Information指定的字节数,将SystemBuffer上的数据复制到Ring3指定的输出缓冲区(UserOutputBuffer)中。可见这个过程是比较安全的,避免了驱动程序在内核态直接操作用户态内存地址的问题,这种方式是推荐使用的方式。

 (点击查看大图)图21.1.12  METHOD_BUFFERED方式的内存访问

METHOD_NEITHER可称为"其他方式",这种方式与METHOD_BUFFERED方式正好相反。METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲,而METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,如图21.1.13所示。

 图21.1.13  METHOD_NEITHER方式的内存访问

驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。

由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。

这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。

 图21.1.14  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。
0 0
原创粉丝点击