使用IO控制码进行驱动间通信

驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动的一个实例被内核加载,并且内核对一个驱动只加 载一个实例。确切地说,是由内核中的I/O管理器负责加载的。

每个驱动程序会创建一个或多个设备对象,用DEVICE_OBJECT数据结构表示。每个设备对象都会有一个指 针指向下一个设备对象,因此就形成一个设备链。设备链的第一个设备是由上一节介绍的DRIVER_OBJECT 结构体中指明的。设备对象保存设备特征和状态的信息。

DriverA(Server)
定义IO控制码
1 2 3 4 5 6 7
| #define IOCTRL_BASE 0x800 #define IOCTRL_CODE1(i)\ CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_IN_DIRECT,FILE_ANY_ACCESS) #define IOCTRL_CODE2(i)\ CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_OUT_DIRECT,FILE_ANY_ACCESS) #define DEVICE_IRP_READ IOCTRL_CODE1(1) #define DEVICE_IRP_WRITE IOCTRL_CODE2(2)
|
CTL_CODE(DeviceType, Function, Method, Access)
- DeviceType:指定设备类型。在这里使用
FILE_DEVICE_UNKNOWN
,意味着设备类型未知或不重要。
- Function:提供功能码,与
IOCTRL_BASE
相加用于生成特定的控制码。
- Method:指定数据传输方法。这里使用
METHOD_IN_DIRECT
和METHOD_OUT_DIRECT
,分别用于输入和输出操作,其中直接I/O允许数据在用户空间和内核空间之间直接传输,减少拷贝开销。
- Access:定义访问权限。这里使用
FILE_ANY_ACCESS
,表示任何人都可以访问该控制码。
进行通信(DriverEntry调用)
- 初始化和获取设备对象
- 首先,函数初始化状态变量
Status
为STATUS_UNSUCCESSFUL
,并准备输入输出缓冲区。
- 使用
IoGetDeviceObjectPointer
根据设备名称获取设备对象(DeviceObject
)和文件对象(FileObject
)。这一步是必要的,因为后续操作需要这些对象来构建IRP(I/O请求包)。
- 构建和发送IRP
- 函数通过
IoBuildDeviceIoControlRequest
构建一个设备IO控制请求的IRP,指定IO控制代码(在这里是DEVICE_IRP_READ
),输入输出缓冲区,以及一个事件对象Event
用于同步操作。
- 在IRP的堆栈位置中设置文件对象,这对于设备驱动处理IRP时可能是必需的。
- 调用
IoCallDriver
将IRP发送给设备驱动。
- 等待操作完成
- 如果
IoCallDriver
返回值表示操作未立即成功完成,代码使用KeWaitForSingleObject
等待之前创建的事件被设备驱动信号化。这表示操作已完成。
- 完成后,通过
DbgPrint
打印调试信息和操作结果。
- 清理资源
- 在操作完成后,使用
ObDereferenceObject
减少文件对象的引用计数。注意,不需要为设备对象做这一步,因为IoGetDeviceObjectPointer
只增加了文件对象的引用计数。
由于函数立即返回,NTSTAUS一般是失败的。可以使用GetLastError继续判断。操作完成的标准是KEVENT事件授信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| void Sub_1() {
NTSTATUS Status = STATUS_UNSUCCESSFUL; IO_STATUS_BLOCK IoStatusBlock = { 0 }; TCHAR InputBuffer[100] = { 0 }; TCHAR OutputBuffer[100] = { 0 }; PFILE_OBJECT FileObject; PDEVICE_OBJECT DeviceObject; do { UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\device\\DriverB"); Status = IoGetDeviceObjectPointer(&DeviceName, FILE_ALL_ACCESS, &FileObject, &DeviceObject); if (!NT_SUCCESS(Status)) break; LARGE_INTEGER offset = { 0 }; KEVENT Event = { 0 }; KeInitializeEvent(&Event, SynchronizationEvent, FALSE); size_t InputSize = (wcslen(L"[A]--DEVICE_IRP_READ -->我要开始读取了") + 1) * 2; RtlStringCbCopyNW(InputBuffer, sizeof(InputBuffer), L"[A]--DEVICE_IRP_READ -->我要开始读取了", InputSize); PIRP Irp = IoBuildDeviceIoControlRequest( DEVICE_IRP_READ, DeviceObject, InputBuffer, 100, OutputBuffer, 100, FALSE, &Event, &IoStatusBlock);
PIO_STACK_LOCATION IoStackLocation = IoGetNextIrpStackLocation(Irp); IoStackLocation->FileObject = FileObject; Status = IoCallDriver(DeviceObject, Irp);
if (!NT_SUCCESS(Status)) {
DbgPrint("[A]---> 模拟调用 DeviceIoControl 成功\r\n"); KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, 0); DbgPrint("[A]---> 数据读取完成\r\n"); DbgPrint("[A] --> read value is %ws \r\n", OutputBuffer); } else { DbgPrint("[A]---> 模拟调用 DeviceIoControl 失败\r\n"); } } while (0); ObDereferenceObject(FileObject); return; }
|
同样测试Write控制码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| void Sub_2() { NTSTATUS Status = STATUS_UNSUCCESSFUL; IO_STATUS_BLOCK IoStatusBlock = { 0 }; TCHAR InputBuffer[100] = { 0 }; TCHAR OutputBuffer[100] = { 0 }; PFILE_OBJECT FileObject; PDEVICE_OBJECT DeviceObject; do { UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\device\\DriverB"); Status = IoGetDeviceObjectPointer(&DeviceName, FILE_ALL_ACCESS, &FileObject, &DeviceObject); if (!NT_SUCCESS(Status)) break; LARGE_INTEGER offset = { 0 }; KEVENT Event = { 0 }; KeInitializeEvent(&Event, SynchronizationEvent, FALSE); size_t InputSize= (wcslen(L"[A]--DEVICE_WRITE -->我要开始读取了") + 1) * 2; RtlStringCbCopyNW(InputBuffer, sizeof(InputBuffer), L"[A]--DEVICE_IRP_WRITE -->我要开始读取了", InputSize);
PIRP Irp = IoBuildDeviceIoControlRequest( DEVICE_IRP_WRITE, DeviceObject, InputBuffer, 100, OutputBuffer, 100, FALSE, &Event, &IoStatusBlock);
PIO_STACK_LOCATION IoStackLocation = IoGetNextIrpStackLocation(Irp); IoStackLocation->FileObject = FileObject; Status = IoCallDriver(DeviceObject, Irp);
if (!NT_SUCCESS(Status)) {
DbgPrint("[A]---> 模拟调用 DeviceIoControl 成功\r\n"); KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, 0); DbgPrint("[A]---> 数据读取完成\r\n"); DbgPrint("[A] --> read value is %ws \r\n", OutputBuffer); } else { DbgPrint("[A]---> 模拟调用 DeviceIoControl 失败\r\n"); } } while (0);
ObDereferenceObject(FileObject); return; }
|
DriverUnload不需要做任何操作。但需要定义。
DriverB(Client)
需要做好一样的定义。
1 2 3 4 5 6 7
| #define IOCTRL_BASE 0x800 #define IOCTRL_CODE1(i)\ CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_IN_DIRECT,FILE_ANY_ACCESS) #define IOCTRL_CODE2(i)\ CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_OUT_DIRECT,FILE_ANY_ACCESS) #define DEVICE_IRP_READ IOCTRL_CODE1(1) #define DEVICE_IRP_WRITE IOCTRL_CODE2(2)
|
DriverEntry
- 创建驱动设备对象
- 所有MajorFunction初始化为默认派遣例程
- IRP_MJ_DEVICE_CONTROL关联IoDispatchControl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS Status; int i = 0;
DriverObject->DriverUnload = DriverUnload; Status = CreateDevice(DriverObject);
for (i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++) { DriverObject->MajorFunction[i] = PassThroughDispatch; }
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoDispatchControl; return Status; }
|
创建驱动设备对象
- 使用
UNICODE_STRING
结构和RtlInitUnicodeString
函数初始化设备名称为\\Device\\DriverB
。这个名称在内核命名空间中唯一标识了驱动程序创建的设备。
- 调用
IoCreateDevice
函数创建设备对象。这个函数需要驱动程序对象、设备名称、设备类型(这里为FILE_DEVICE_UNKNOWN
),以及是否独占访问(这里为TRUE
)等参数。
- 设定
DeviceObject
的Flags
属性,启用直接I/O(DO_DIRECT_IO
)。这意味着数据传输将直接在用户缓冲区和设备之间进行,而不是使用中间缓冲区。
- 初始化符号链接名称为
\\??\\DriverB
,然后调用IoCreateSymbolicLink
函数创建从符号链接到设备对象的映射。这样,用户模式应用程序可以通过符号链接访问设备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| NTSTATUS CreateDevice( IN PDRIVER_OBJECT DriverObject) { NTSTATUS Status; PDEVICE_OBJECT DeviceObject;
UNICODE_STRING DeviceName; RtlInitUnicodeString(&DeviceName, L"\\Device\\DriverB");
Status = IoCreateDevice(DriverObject,NULL, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject); if (!NT_SUCCESS(Status)) return Status;
DeviceObject->Flags |= DO_DIRECT_IO;
UNICODE_STRING LinkName; RtlInitUnicodeString(&LinkName, L"\\??\\DriverB"); Status = IoCreateSymbolicLink(&LinkName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(DeviceObject); return Status; }
return STATUS_SUCCESS; }
|
IO派遣例程
- 初始化I/O控制码(
IoControlCode
)、输入和输出缓冲区的大小(InputSize
和OutputSize
),以及状态变量(Status
)。调用IoGetCurrentIrpStackLocation
获取当前IRP的堆栈位置,以便访问其中的参数。
- 函数使用
switch
语句根据IRP中的IO控制码的子功能码(IoControlCode
)分别处理读写请求(DEVICE_IRP_READ
和DEVICE_IRP_WRITE
)。
- 对于读请求(
DEVICE_IRP_READ
),函数尝试从MDL(内存描述列表)获取输入缓冲区地址,并将一段预定义的文本复制到系统缓冲区中作为输出。
- 对于写请求(
DEVICE_IRP_WRITE
),函数从系统缓冲区读取输入数据,并尝试将一段预定义的文本复制到通过MDL获取的输出缓冲区中。
- 最后都要完成IO请求(
IoCompleteRequest
),并对IOSB成员进行设置。
除了“缓冲区”方式读写设备外,另外一种方式是直接方式读写设备。这种方式需要创建完设备对象后, 在设置设备属性的时候,设置为DO_DIRECT_IO,而不是设置DO_BUFFERED_IO属性。操作系统先将用户模式的地址锁定后,操作系统用内存描述符表(MDL数据结构)记录这段内存。

MDL记录这段虚拟内存,这段虚拟内存的大小存储在mdl->ByteCount里,这段虚拟内存的第一个页地址是 mdl->StartVa,这段虚拟内存的首地址对于第一个页地址的偏移量是mdl->ByteOffset。因此,这段虚拟 内存的首地址应该是mdl->StartVa+mdl->ByteOffset。DDK提供了几个宏方便程序员得到这几个数值。
1 2 3
| #define MmGetMdlByteCount(Mdl) ((Mdl)->ByteCount) #define MmGetMdlByteOffset(Mdl) ((Mdl)->ByteOffset) #define MmGetMdlVirtualAddress(Mdl) \ ((PVOID) ((PCHAR) ((Mdl)->StartVa) + (Mdl)->ByteOffset))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| NTSTATUS IoDispatchControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
if (1) {
if (NULL == Irp) return STATUS_UNSUCCESSFUL; BOOLEAN IsOk = FALSE; ULONG IoControlCode = 0; ULONG InputSize = 0; ULONG OutputSize = 0; NTSTATUS Status = STATUS_UNSUCCESSFUL;
PIO_STACK_LOCATION IrpStackLocation = NULL; do { IrpStackLocation = IoGetCurrentIrpStackLocation(Irp); IoControlCode = IrpStackLocation->Parameters.DeviceIoControl.IoControlCode; InputSize = IrpStackLocation->Parameters.DeviceIoControl.InputBufferLength; OutputSize = IrpStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
switch (IoControlCode) { case DEVICE_IRP_READ: { PVOID InputBuffer = NULL; PVOID OutputBuffer = NULL; InputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);; OutputBuffer = Irp->AssociatedIrp.SystemBuffer; size_t v7 = (wcslen(L"[B]-->Read") + 1) * 2; if (InputBuffer != NULL) { DbgPrint("[B]---> 获取到的输入缓冲区为: %ws\r\n", InputBuffer); RtlCopyMemory(OutputBuffer, L"[B]-->Read", v7); DbgPrint("[B]---> 设置的输出缓冲区为: %ws\r\n", OutputBuffer); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = v7; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } } break; case DEVICE_IRP_WRITE: { PVOID InputBuffer = NULL; PVOID OutputBuffer = NULL; InputBuffer = Irp->AssociatedIrp.SystemBuffer; OutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);; size_t v7 = (wcslen(L"[B]-->Write") + 1) * 2; if (InputBuffer != NULL) { DbgPrint("[B]---> 获取到的输入缓冲区为: %ws\r\n", InputBuffer); RtlCopyMemory(OutputBuffer, L"[B]-->Write", v7); DbgPrint("[B]---> 设置的输出缓冲区为: %ws\r\n", OutputBuffer); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = v7; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } } break; default: break; } } while (FALSE);
if (!IsOk) { Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } } Irp->IoStatus.Status = STATUS_UNSUCCESSFUL; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
|
默认派遣例程
1 2 3 4 5 6 7
| NTSTATUS PassThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
|
驱动卸载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { PDEVICE_OBJECT v1; PDEVICE_OBJECT v2; v1 = DriverObject->DeviceObject; UNICODE_STRING LinkName; RtlInitUnicodeString(&LinkName, L"\\??\\DriverB"); IoDeleteSymbolicLink(&LinkName); while (v1 != NULL) { v2 = v1->NextDevice; IoDeleteDevice(v1); v1 = v2; } }
|