0%

驱动开发-文件操作

Ring3和Ring0的通信DeviceIoControl

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

操作码

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

  * IOCTL_Device_Function:生成的IRP的MinorFunction
  * DeviceType:设备对象的类型。一般以FILE_DEVICE_XXX的形式,但是这主要用于基于硬件的驱动程序,对于像本例子这样的软件驱动程序来说,这部分并不是很重要。微软规定第三方的值应该从0x8000开始。
  * 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_BUFFERED**,表示系统将应用程序提供缓冲区的数据复制到内核模式下的地址中,这个地址用pIrp->AssociatedIrp.SystemBuffer来记录。因此这种方式的通信比较安全和方便,但是效率较低,适合数据量比较小的时候使用。

image-20220812161136566

第二种直接方式读写METHOD_IN/OUT_DIRECT,与第一种方式不同,操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式和内核模式的缓冲区指向的是同一区域的物理内存。操作系统将用户模式地址锁定后,会用内存描述符表(MDL)记录这段内存。

这种方式效率高,但是单独占用物理页面,无法再进行其它操作(例如文件读写) ,适合数据量较大时使用。

image-20220812162552303

第三种是**其他读写方式METHOD_NEITHER **,派遣函数直接读写应用程序提供的缓冲区地址,在驱动程序中,直接操作应用程序的缓冲区地址是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。

驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIoControl.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。在读写前使用ProbeForReadProbeForWrite函数探测地址是否可读和可写。

image-20220812163159811

通信流程:

  * 驱动程序和应用程序自定义好IO控制码
  * 驱动程序定义驱动设备名,符号链接名, 将符号链接名与设备对象名称关联 ,等待IO控制码(IoCreateDeviceIoCreateSymbolicLink
  * 应用程序由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用DeviceIoControl通过这个设备句柄发送控制码给派遣函数。

总之,三环函数->NTDLL->封装IRP->由驱动进行接收处理->最后返回给R3

代码

1.首先需要和驱动程序一样定义操作码,以便通信

image-20220815102456775

2.然后打开符号链接

image-20220815101857369

3.通过DeviceIoControl将操作码等数据发给驱动,表明要执行什么操作,注意Write以及Read操作需要调用该函数两次,Write一次发送操作路径,一次发送操作数据,Read一次发送操作路径,返回文件大小;一次发送操作长度,返回文件内容。

image-20220815102859789

image-20220815183826750

Ring0层

驱动程序首先需要创建设备名称和符号链接,并且需要创建设备对象,目的就是为了接受R3层的IRP数据,符号链接主要是为设备对象创建的,创建了符号链接才能再yR3层看到驱动。并且只有驱动内部含有符号链接名,应用层才能以文件形式打开这个驱动。

驱动和设备的关系

驱动: 驱动则是用来操作设备的.

设备: 设备则是我们常说的外设. 比如键盘. 显示器.鼠标等等。

驱动和设备之间的关系是一对多的关系,驱动可以操作很多设备。

依据上面的数据关系来说,设备对象中肯定会存储驱动对象结构体的指针,驱动对象做外键存储到设备对象中.

代码

1.创建设备名称和符号链接

image-20220813131057472

2.注册回调函数

image-20220815101449066

3.主要的操作函数,用来处理与Ring3的交互

image-20220815184356417

因为采用的是缓冲区的方式读写,所以按照前面说的,输入输出其实都是一个缓冲区。