Windows内核(4)——R0,R3通讯,交换数据
Windows内核
Windows 组件概述
调用例如CreateFile这个NTDll的API,这个API会进入0环,在0环里面找到相关的服务,将信息先传给IO Mgr,被打包为IRP,然后传递给驱动,等待驱动完成请求,处理完以后,才将结果返回给Ring3
WriteFile => ntdll.dll => kernel => I/O Manager => AllocIrp => Driver
一些介绍
IoCompleteRequest
是 Windows 内核模式驱动程序开发中的一个关键函数,用于完成 I/O 请求并通知 I/O 管理器和请求的发起者(通常是用户模式应用程序或其他内核模式组件)操作已经完成。这个函数会更新 IRP(I/O 请求包)的状态,并根据请求的优先级和完成状态触发相应的后续操作。
1 | VOID IoCompleteRequest( |
使用方法
IoCompleteRequest
通常在驱动程序完成对 I/O 请求的处理后调用。它通常在以下几种情况下使用:
- IRP 处理完成: 驱动程序完成对 IRP 的处理,例如读写操作已完成。
- 错误处理: 驱动程序在处理 IRP 时遇到错误,需要终止请求并返回错误状态。
- 中断处理: 驱动程序在处理中断请求时,完成对中断的处理并通知系统。
ProbeForWrite
是 Windows 内核模式下的一个函数,用于验证用户模式传递的缓冲区是否可以写入。它在处理来自用户模式的数据时非常重要,因为内核模式代码不能直接信任用户模式传递的指针。这是因为用户模式的应用程序可以传递一个无效或不合法的指针,导致内核模式代码在访问该指针时引发异常,甚至导致系统崩溃(蓝屏)。 (注意是用户模式传递的缓冲区!!!!)一般也就判断Irp->UserBuffer
1 | VOID ProbeForWrite( |
Address
: 用户模式缓冲区的起始地址。
Length
: 缓冲区的长度(字节数)。
Alignment
: 缓冲区必须对齐的边界(以字节为单位),通常设置为 1 表示不需要特别的对齐。
MmIsAddressValid
是 Windows 驱动程序开发中常用的一个函数,用于检查指定的内存地址是否有效,即是否在当前进程的虚拟地址空间范围内。
1 | BOOLEAN MmIsAddressValid( |
参数:
VirtualAddress
:要检查的虚拟内存地址。
返回值:
TRUE
:如果指定的虚拟地址在当前进程的虚拟地址空间范围内且有效。FALSE
:如果指定的虚拟地址无效或不在当前进程的虚拟地址空间范围内。
Irp->IoStatus.Status = STATUS_SUCCESS;
和 return STATUS_SUCCESS;
在驱动程序开发中有不同的作用和意义,尽管它们都使用了相同的状态代码 STATUS_SUCCESS
。下面解释它们的区别和各自的作用:
Irp->IoStatus.Status = STATUS_SUCCESS;
这行代码设置了当前 I/O 请求包(IRP)的状态。IRP 是操作系统用来描述和跟踪 I/O 操作的结构。当驱动程序处理一个 I/O 请求时,它会设置 Irp->IoStatus.Status
字段来指示操作的结果。
作用:
- 通过设置
Irp->IoStatus.Status
,驱动程序通知操作系统这个 I/O 请求的处理状态(成功或失败)。 - 这个字段的值会在
IoCompleteRequest
调用之后返回给发起 I/O 请求的代码(通常是用户模式应用程序或内核模式组件)。
- 通过设置
return STATUS_SUCCESS;
这行代码是函数返回值的一部分,用来指示函数执行的结果状态。它与设置 IRP 状态不同,因为它是函数本身的返回值。
R3往R0写入数据WriteFile
驱动的代码这样写:
1 | // MyWrite |
注意注意:
1 | RtlCopyMemory(g_GlobalBuffer, lpBuf, wcslen((TCHAR*)lpBuf)*sizeof(TCHAR)+1); |
这里要多一个字节,否则没有 ‘\x00’ 结尾容易出大问题
MFC代码:(和下面读出数据用的是同一个):
1 |
|
效果图:
R3从R0读出数据ReadFile
驱动这样写:
1 | NTSTATUS MyRead(PDEVICE_OBJECT DeviceObject, PIRP Irp) { |
完整驱动程序代码:
1 |
|
效果图:
IRP_MJ_DEVICE_CONTROL_DeviceIoControl
例如要读一个设备(例如某个光驱)里面的文件,但是因为IRP_MJ_WRITE和IRP_MJ_READ这俩事件是没有传递参数的,这是一个痛点,没办法传递额外的参数
于是就有了去处理这个事件
1 |
DeviceIoControl
是 Windows 平台上用于与驱动程序通信的重要函数之一,它允许用户应用程序与设备驱动程序进行数据交换和控制操作。以下是关于 DeviceIoControl
函数的详细介绍:
1 | BOOL DeviceIoControl( |
参数:
hDevice
:设备的句柄,通过CreateFile
函数获取。dwIoControlCode
:指定设备控制码,用于标识要执行的操作。lpInBuffer
:输入数据的缓冲区指针,用于向设备传递数据。nInBufferSize
:输入数据缓冲区的大小,以字节为单位。lpOutBuffer
:输出数据的缓冲区指针,用于从设备获取数据。nOutBufferSize
:输出数据缓冲区的大小,以字节为单位。lpBytesReturned
:指向DWORD
类型的变量的指针,用于接收实际传输的字节数。lpOverlapped
:指向OVERLAPPED
结构的指针,支持异步 I/O 操作。可以传递NULL
进行同步操作。
DeviceIoControl
比WriteFile和ReadFile相比,WriteFile既能IN也能Out
DeviceIoControl
提供了对设备的多种控制功能,不仅限于数据的读写。可以通过控制码(IOCTL)执行特定的设备操作,如配置设备、查询状态、执行诊断命令等。
允许用户定义自定义控制码,使驱动程序能够支持特定的功能和操作,这些是 WriteFile
和 ReadFile
所无法实现的。
在驱动程序中,那么IRP_MJ_DEVICE_CONTROL 事件里面,Buffer有俩,Length也有俩,这就很尴尬了。
实际上,输入的Buffer还是Irp->UserBuffer
其他的参数在IRP堆栈里面拿:
1 | PIO_STACK_LOCATION pIrpstack = IoGetCurrentIrpStackLocation(Irp); |
还有一个参数IoControlCode
:指定设备控制码,用于标识要执行的操作。
框架:
1 |
|
CTL_CODE
: CTL_CODE
是一个宏,用于定义 Windows 设备控制码(IOCTL)
参数说明
- DeviceType:设备类型,通常使用
FILE_DEVICE_*
类型定义,如FILE_DEVICE_UNKNOWN
、FILE_DEVICE_KEYBOARD
等。 - Function:功能码,用于标识具体的控制操作,应该是一个唯一的值,用于识别设备支持的不同功能。
- Method:传输方法,指定数据传输的方式,可以是
METHOD_BUFFERED
、METHOD_IN_DIRECT
、METHOD_OUT_DIRECT
或METHOD_NEITHER
中的一个。(这个很重要,涉及到参数去哪里拿)
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 switch (METHOD_FROM_CTL_CODE(ioControlCode)) {
case METHOD_BUFFERED:
inputBuffer = Irp->AssociatedIrp.SystemBuffer;
outputBuffer = Irp->AssociatedIrp.SystemBuffer;
break;
case METHOD_IN_DIRECT:
inputBuffer = Irp->AssociatedIrp.SystemBuffer;
if (Irp->MdlAddress) {
outputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
}
break;
case METHOD_OUT_DIRECT:
inputBuffer = Irp->AssociatedIrp.SystemBuffer;
if (Irp->MdlAddress) {
outputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
}
break;
case METHOD_NEITHER:
inputBuffer = irpSp->Parameters.DeviceIoControl.Type3InputBuffer;
outputBuffer = Irp->UserBuffer;
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
- Access:访问权限,指定谁可以访问设备控制码。常见的值包括
FILE_ANY_ACCESS
、FILE_READ_ACCESS
和FILE_WRITE_ACCESS
。
驱动程序代码:
1 |
|
Ring3 MFC代码:
1 |
|
效果如图:
共享内存实现通信
内核层:
基本思路就是:
在内核层,创建一个共享段,然后分别映射到内核空间和用户空间(用户空间要指定内存地址),然后内核空间和用户空间实际上是映射到了同一个内存
1 |
|
用户层:
1 |
|