1.设备对象
我们在开发窗口程序的时候,消息被封装成一个结构体:MSG,在内核开发时,消息被封装成另外一个结构体:IRP(I/O Request Package)。
在窗口程序中,能够接收消息的只能时窗口对象。在内核中,能够接收IRP消息的只能是设备对象。
2.创建设备对象
//创建设备名称
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName,L"\\Device\\testDevice");
//创建设备
IoCreateDevice(
pDriver, //当前设备所属的驱动对象
0,
&Devicename, //设备对象的名称
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDeviceObj //设备对象指针
);
3.设置交互数据的方式
pDeviceObj->Flags |= DO_BUFFERED_IO;
缓冲区方式读写(DO_BUFFERED_IO) : 操作系统将应用程序提供的缓冲区的数据复制到内核模式下的地址中。
直接方式读写(DO_DIRECT_IO):操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址在映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。缺点就是要单独占用物理页面。
其他方式读写(在调用IoCreateDevice 创建设备后对pDevObj->Flags 即不设置DO_BUFFERED_IO 也不设置DO_RECT_IO,此时就是其他方式):在使用其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程序的缓冲区地址是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。
4. 创建符号链接
//创建符号链接名称
RtlInitUnicodeString(&SymbolicLinkName,L"\\??\\MyTestDriver");
//创建符号链接
IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);
特别说明:
- 1.设备名称的作用是给内核对象用的,如果要在Ring3 访问,必须要有符号链接,其实就是一个别名,没有这个别名,在Ring3 不可见。
- 2.内核模式下,符号链接时以"\??\"开头的,如C盘就是“\??\C:”
- 3.在用户模式下,则是以“\\.\”开头的,如C盘就是"\\.\C:"
5.IRP 与 派遣函数
IRP的类型
1.当应用层通过CreateFile、ReadFile 、WriteFile、CloseHandle 等函数打开、从设备读取数据、向设备写入数据、关闭设备的时候,会使操作系统产生出IRP_MJ_CREATE、IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_CLOSE等不同的IRP。
2.其他类型的IRP
IRP类型 | 来源 |
IRP_MJ_DEVICE_CONTROL | DeviceControl函数会产生此IRP |
IRP_MJ_POWER | 在操作系统处理电源消息时,产生次IRP |
IRP_MJ_SHUTDOWN | 关闭系统前会产生此IRP |
7.派遣函数在哪里注册呢 ?
kd> dt _DRIVER_OBJECT
nt!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
....
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void //卸载函数
+0x038 MajorFunction : [28] Ptr32 long //派遣函数
8.注册派遣函数
NTSTATUS DriverEntry( 。。。。)
{
//设置卸载函数
pDriverObject->DriverUnload = 卸载函数;
//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = 派遣函数1;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 派遣函数2;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = 派遣函数3;
pDriverObject->MajorFunction[IRP_MJ_READ] = 派遣函数4;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = 派遣函数5;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = 派遣函数6;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 派遣函数7;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = 派遣函数8;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 派遣函数9;
}
IRP_MJ_MAXIMUM_FUNCTION 派遣函数的最大值
9.派遣函数的格式
/派遣函数的格式:
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
//处理自己的业务...
//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
10.通过IRP_MJ_DEVICE_CONTROL交互数据
应用层调用DeviceControl函数会产生此IRP.
1.IOCTRL_CODE 控制代码的几种IO
a. METHOD_BUFFERED
在R3 可以传递控制码给内核层,其中需要指明读写方式
如下:
#define KPH_CTL_CODE(x) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 + x, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define KPH_OPENPROCESS KPH_CTL_CODE(50)
4个参数:
参数1: 驱动的类型
参数2: 驱动的控制码.
参数3: 以哪种缓冲方式进行通讯
参数4: 权限
其中参数3就是需要指定什么方式进行通讯
METHOD_BUFFERED 方式,数据都会封装在IRP头部的SystemBuffer 中
pIrp->AssociatedIrp.SystemBuffer
b . METHOD_IN_DIRECT :只读缓冲 ,METHOD_DIRECT 只写缓冲
METHOD_IN_DIRECT 只读缓冲的方式,则缓冲区还是会封装到IRP头部的SystemBuffer 中。
如上,R3 传输的数据都会放在SystemBuffer 中。
METHOD_OUT_DIRECT 只写缓冲,这种方式也会封装到IRP头部,但是会设置在IRP头部的MdlAddress 中,输出的数据都要放在MDL中。
如下:
OUT PVOID pOutBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
MDL是R3 虚拟地址,映射到内核中的物理地址,不能直接使用。
比如通过MmgetSystemAddressForMdlSafe 函数,将映射的物理地址,转换为内核中的“虚拟地址”可以这样理解,然后对这个内存进行输出即可,R3 则可以接收到数据。
区别:
如果是只读权限打开设备的时候METHOD_IN_DIRECT则会成功,METHOD_OUT_DIRECT则会失败。
如果读写的方式,则都会成功。
c. METHOD_NEITHER 其他方式
使用其他方式,则R3 发送过来的数据会在IRP堆栈中。
获取R3 的数据时,如下:
PIRPSTACK_LOCATIO pIrpStack;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer
R0输出的数据要放在IPR头部的UserBuffer 中传递给R3
pIrp->UserBuffer