本质上是驱动加载完成时会产生一块共享内存用于R3和R0数据交换,控制码用于控制读写哪块内存。
三种方式通信:
SystenBuffer
(对应r3和r0的3种通信方式之一:buffer io)MdlAddress
(对应r3和r0的3种通信方式之一:direct io)UserBuffer
(对应r3和r0的3种通信方式之一:neither io)
buffered io
- 在内核层分配一块缓存,io管理器负责把应用层/内核层copy到buffer,io管理器负责把buffer拷贝到io管理器负责把内核层/应用层。
- 优点:安全简单,因为不会操作应用层的内存,buffer是来自内核态的,应用层无法改内核层的数据,所以是安全的。
- 缺点:效率低,因为一次通信有两次拷贝,一般传输数据量是不大的,buffer io是够用的,但如果是类似3d渲染,数据量大,direct io更适合;
direct io
- io管理器通过
MPL
把应用层/内核层的虚拟地址
映射成物理地址
,然后lock
,防止被这块内存切换出去(pageout),io管理器通过MPL把同一物理地址
映射成内核层/应用层的物理地址
。 - 优点效率是最高的,一次通信只有一次拷贝
- 但稍复杂
neither io
内核层直接访问应用层的数据,前提是应用层和内核层同处于一个进程上下文
(因为应用层内存地址是私有的,应用层进程切换之后内存就失效了),要对内核层传入的内存地址要做检查(ProbeForRead
/ProbeForWrite
),否则会有提取漏洞
- Q3:三种通信方式中,应用层发下来的内存和内核层把数据返回给应用层的内存对应Irp的什么位置? 应用层发下来给内核层的
内存
对应Irp的栈
;内核层把数据返回给应用层的内存
对应Irp的头部SystenBuffer
、MdlAddress
、UserBuffer
分发函数处理
#define ReadCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x803,METHOD_BUFFERED,FILE_ANY_ACCESS) //读控制码
#define WriteCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x804,METHOD_BUFFERED,FILE_ANY_ACCESS) //写控制码
#define RWCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x805,METHOD_BUFFERED,FILE_ANY_ACCESS) //读写控制码
NTSTATUS IRP_CALL(PDEVICE_OBJECT device, PIRP pirp)
{
device;
KdPrint(("进入派遣函数"));
PIO_STACK_LOCATION irpStackL;
//获取R3传来的参数(控制码)
irpStackL = IoGetCurrentIrpStackLocation(pirp); //获取应用层传来的参数
switch (irpStackL->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL: //DeviceIoControl
{
KdPrint(("用户层调用了 DeviceIoControl \n"));
UINT32 CtlCode = irpStackL->Parameters.DeviceIoControl.IoControlCode;
KdPrint(("IRP_MJ_DEVICE_CONTROL R0控制码:%X \n", CtlCode));
if (CtlCode == ReadCtl)
{
KdPrint(("IRP_MJ_DEVICE_CONTROL ReadCtl R0控制码:%X \n", CtlCode));
// IRP_IO_Read(pirp); //这里写入到共享缓冲剂即可,打印R3访问共享缓冲区打印
char* buff = (char*)pirp->AssociatedIrp.SystemBuffer;
//将R0读取到的数据写入到向共享缓冲区
char R0returnbuf[] = "R0 read data \n";
ULONG len = sizeof(R0returnbuf);
memcpy_s(buff, len, R0returnbuf, len);
KdPrint(("read data to SystemBuffer \n"));
pirp->IoStatus.Information = len; // 缓冲区长度
}
else if (CtlCode == WriteCtl)
{
KdPrint(("IRP_MJ_DEVICE_CONTROL WriteCtl R0控制码:%X \n", CtlCode));
//取出R3缓冲区的数据
//根据控制代码来选择使用AssociatedIrp.SystemBuffer的读缓冲区还是写缓冲区
char* R3buff = (char*)pirp->AssociatedIrp.SystemBuffer;
KdPrint(("IRP_MJ_DEVICE_CONTROL R0缓冲区:%s \n", R3buff));
}
else if (CtlCode == RWCtl)
{
KdPrint(("IRP_MJ_DEVICE_CONTROL RWCtl R0控制码:%X \n", CtlCode));
}
break;
}
case IRP_MJ_CREATE: //CreateFile
{
KdPrint(("用户层调用了 CreateFile \n"));
break;
}
case IRP_MJ_CLOSE: //CloseHandle
{
KdPrint(("用户层调用了 CloseHandle \n"));
break;
}
}
pirp->IoStatus.Status = STATUS_SUCCESS;
// pirp->IoStatus.Information = 4;//返回给DeviceIoControl中的 倒数第二个参数lpBytesReturned
IoCompleteRequest(pirp, IO_NO_INCREMENT);//调用方已完成所有I/O请求处理操作 并且不增加优先级
KdPrint(("离开派遣函数 \n"));
return STATUS_SUCCESS; //0 返回成功
}
R3层
DeviceIoControl(
DeviceHandle, //CreateFile 打开驱动设备返回的句柄
WriteCtl, //控制码 CTL_CODE 与IRP事件对应
WriteData, //输入缓冲区 &inBuf
sizeof(inBuf), //输入缓冲区大小
&OutBuf, //输出缓冲区
sizeof(OutBuf), //输出缓冲区大小
&dwRetSize, //返回字节数
NULL);