前置知识
IRP是什么
IRP的处理机制类似于windows窗口程序中的“消息处理”机制,驱动程序接收到不同类型的IRP后,会进入不同的派遣函数,在派遣函数中IRP得到处理。
派遣函数是什么
用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IRP的数据结构,不同的IRP数据会被“派遣”道不同的派遣函数中,这也是派遣函数名字的由来。
驱动程序并不是“被直接调用”的,而是由操作系统根据 IRP 的类型进行派遣。
每一种 IRP 都有一个“主功能码(Major Function)”,例如:
- IRP_MJ_CREATE:打开设备(CreateFile)
- IRP_MJ_CLOSE:关闭设备(CloseHandle)
- IRP_MJ_DEVICE_CONTROL:设备控制(DeviceIoControl)
驱动通过注册 派遣函数,告诉系统:
“当收到某种类型的 IRP 时,请调用我这个函数来处理。”
用户模式发起操作(如 DeviceIoControl) 操作系统将其转换为 IRP 根据 IRP 类型,进入驱动中对应的派遣函数 驱动在派遣函数中完成处理并返回结果什么是设备对象(DEVICE_OBJECT)?
设备对象是驱动在内核中创建的、用于接收 I/O 请求的内核对象。
在 Windows 中,驱动程序本身并不能直接接收 IRP,
真正接收 IRP 的,是驱动创建的 设备对象(DEVICE_OBJECT)。
在代码中,对应的就是:
IoCreateDevice(...)创建成功后,系统会为该驱动生成一个设备对象,
之后所有发往该设备的 IRP,都会被派遣到这个设备对象所属的驱动中。
对于我们这种 0 环 / 3 环通信驱动来说:
这个设备并不对应真实硬件, 而是一个“纯软件设备”, 用来承载用户态与驱动之间的通信。设备名(\Device\xxx)是做什么的?
设备对象在内核中必须有一个唯一的名字,
这个名字存在于 内核对象命名空间 中,通常以 \Device\ 开头,例如:
\\Device\\MyDevice这个名字的特点是:
只在内核态可见
Ring3 程序不能直接使用这个名字打开设备
它的主要作用是:在内核中唯一标识一个设备对象
为什么还需要符号链接?
既然 Ring3 不能直接访问 \Device\MyDevice,
那用户程序如何找到这个设备呢?
答案是:通过符号链接(Symbolic Link)。
驱动可以创建一个符号链接,例如:
\\??\\MyDevice->\\Device\\\MyDevice代码
ring3
#include<stdio.h>#include<windows.h>#include<winioctl.h>#defineOPER1CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineOPER2CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)/***************************************************************************///打开驱动服务句柄//打开三环链接名:\\\\.\\Device/***************************************************************************/intmain(void){DWORD dwInBuffer=0x123456789;TCHAR szOutBuffer[10]={0};DWORD dw;//1. 通过符号链接,打开设备HANDLE g_hDevice=CreateFileW(L"\\\\.\\MyDevice",// 设备名,例如 "\\\\.\\MyDevice"GENERIC_READ|GENERIC_WRITE,// 读写权限0,// 共享模式:0 表示不共享0,// 安全属性:NULL = 默认OPEN_EXISTING,// 只能打开已存在的设备FILE_ATTRIBUTE_NORMAL,// 普通文件属性(对设备来说一般无所谓)0// 模板句柄:不用,传0);//2. 测试通信DeviceIoControl(g_hDevice,//设备句柄OPER2,//操作码&dwInBuffer,//输入缓冲区地址sizeof(dwInBuffer),//输入缓冲区长度szOutBuffer,//输出缓冲区地址sizeof(szOutBuffer),//输出缓冲区长度&dw,//返回长度NULL);//指向OVERLAPPED 此处为NULLprintf("%x",*(int*)szOutBuffer);//3. 关闭设备CloseHandle(g_hDevice);system("pause");return0;}ring0
#include<ntifs.h>#defineOPER1CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineOPER2CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineDEVICE_NAMEL"\\Device\\MyDevice"// Ring3用CreateFile打开设备时,用"\\\\.\\MyDevice"#defineSYMBOLICLINK_NAMEL"\\??\\MyDevice"// IRP_MJ_CREATE处理函数//参数1:一个驱动可以有多个 DEVICE_OBJECT,所以同一个分发函数可以用 pDevObj 区分“这次是哪个设备收到的 IRP”。//参数2:这次 I/O 请求对应的 IRP 结构体,里面记录了://1.这是一个 Create 请求(MajorFunction = IRP_MJ_CREATE)//2.打开方式、共享模式等参数//3.一个 IO_STATUS_BLOCK:pIrp->IoStatusNTSTATUSIrpCreateProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){DbgPrint("(mydriver)DispatchCreate ... \n");//每个 IRP 里都有一个 IO_STATUS_BLOCK IoStatus//IoStatus.Status:本次 I/O 请求的 NTSTATUS 返回码//IoStatus.Information:与这次请求相关的“额外信息”(比如读/写了多少字节)//你必须显式把 IoStatus.Status 设置成成功或失败,//否则就是“垃圾值”,I/O 管理器按照那个垃圾值判断,于是 Ring3 的 CreateFile 返回失败。pIrp->IoStatus.Status=STATUS_SUCCESS;//这个字段的含义取决于 是什么 IRP://对 IRP_MJ_READ:常常表示“实际读取了多少字节”//对 IRP_MJ_WRITE:常常表示“实际写入了多少字节”//对 IRP_MJ_DEVICE_CONTROL:表示“返回给用户缓冲区的数据长度”//对 IRP_MJ_CREATE / IRP_MJ_CLOSE:一般没有额外数据需要告诉 Ring3,所以设为 0pIrp->IoStatus.Information=0;//告诉 I/O 管理器:“这个 IRP 我处理完了,可以结束这次 I / O 请求了。”//如果不调用IoCompleteRequest,Ring3 那边的 CreateFile 会一直等(同步调用),永远等不到结果,表现就是程序卡死/挂住。IoCompleteRequest(pIrp,IO_NO_INCREMENT);//return STATUS_SUCCESS; 和 IoStatus.Status 的关系//这里返回值也是 NTSTATUS,I / O 管理器确实也会用这个值,但真正传回 Ring3 的是 pIrp->IoStatus.Status。returnSTATUS_SUCCESS;}// IRP_MJ_CLOSE处理函数NTSTATUSIrpCloseProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){DbgPrint("(mydriver)DispatchClose ... \n");pIrp->IoStatus.Status=STATUS_SUCCESS;pIrp->IoStatus.Information=0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}// IRP_MJ_DEVICE_CONTROL处理函数 用来处理与Ring3交互NTSTATUSIrpDeviceControlProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){NTSTATUS status=STATUS_INVALID_DEVICE_REQUEST;ULONG uRead;//当设置交互模式为pDeviceObj->Flags |= DO_BUFFERED_IO;那 I/O 管理器会://1.在内核态分配一块缓冲区:SystemBuffer//2.把 Ring3 的输入缓冲区(InBuff)拷贝过来//3.把 SystemBuffer 指针放到 pIrp->AssociatedIrp.SystemBuffer//4.完成 IRP 时,再把 SystemBuffer 前 IoStatus.Information 个字节拷回 Ring3 的输出缓冲区(OutBuff)//设置临时变量的值PIO_STACK_LOCATION pIrpStack=IoGetCurrentIrpStackLocation(pIrp);// 获取控制码ULONG uIoControlCode=pIrpStack->Parameters.DeviceIoControl.IoControlCode;// 获取缓冲区地址(输入和输出的缓冲区都是一个)PVOID pIoBuffer=pIrp->AssociatedIrp.SystemBuffer;// Ring3 发送数据的长度:用户写进来的数据长度(你最多只能读这么多字节)。ULONG uInLength=pIrpStack->Parameters.DeviceIoControl.InputBufferLength;// Ring0 发送数据的长度:用户预留的输出缓冲区最大长度(你最多填这么多字节)。ULONG uOutLength=pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;switch(uIoControlCode){caseOPER1:{DbgPrint("(mydriver)IrpDeviceControlProc -> OPER1 ... \n");pIrp->IoStatus.Information=0;status=STATUS_SUCCESS;break;}caseOPER2:{DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 接收字节数:%d \n",uInLength);DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 输出字节数:%d \n",uOutLength);// Read From Buffermemcpy(&uRead,pIoBuffer,4);DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 ... %x \n",uRead);// Write To BufferuRead++;memcpy(pIoBuffer,&uRead,4);// Set StatuspIrp->IoStatus.Information=4;status=STATUS_SUCCESS;break;}}// 设置返回状态pIrp->IoStatus.Status=status;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnstatus;}VOIDDriverUnload(PDRIVER_OBJECT pDriver){UNICODE_STRING SymbolicLinkName={0};DbgPrint("(mydriver)驱动程序停止运行了 . \r\n");// 删除符号链接 删除设备RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);IoDeleteSymbolicLink(&SymbolicLinkName);IoDeleteDevice(pDriver->DeviceObject);}NTSTATUSDriverEntry(PDRIVER_OBJECT pDriver,PUNICODE_STRING pReg){DbgPrint("(mydriver)驱动程序开始运行了 . \r\n");//初始化ring0设备名称UNICODE_STRING Devicename;RtlInitUnicodeString(&Devicename,DEVICE_NAME);// 创建设备PDEVICE_OBJECT pDeviceObj=NULL;NTSTATUS status=IoCreateDevice(pDriver,//驱动对象0,//设备扩展大小:为每个设备对象 额外分配的扩展空间大小&Devicename,//设备名称FILE_DEVICE_UNKNOWN,//设备类型FILE_DEVICE_SECURE_OPEN,//设备特性FALSE,//是否“独占设备”:TRUE:同一时间只能有 一个 句柄打开这个设备。第二个进程再 CreateFile 就会失败。//FALSE:多个进程 / 线程可以同时打开。&pDeviceObj);//输出:系统创建好的 PDEVICE_OBJECT 指针,通过它你以后能访问到设备扩展、标志位等等。if(status!=STATUS_SUCCESS){DbgPrint("(mydriver)创建设备失败! \r\n");returnstatus;}//设置交互数据的方式pDeviceObj->Flags|=DO_BUFFERED_IO;// 创建符号链接名称(给ring3用)UNICODE_STRING SymbolicLinkName;RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);// 创建符号链接status=IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);if(status!=STATUS_SUCCESS){DbgPrint("(mydriver)创建符号链接失败! \r\n");IoDeleteDevice(pDeviceObj);returnstatus;}// 设置分发函数和卸载函数pDriver->MajorFunction[IRP_MJ_CREATE]=IrpCreateProc;pDriver->MajorFunction[IRP_MJ_CLOSE]=IrpCloseProc;pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL]=IrpDeviceControlProc;pDriver->DriverUnload=DriverUnload;pDriver->DriverUnload=DriverUnload;return0;}