背景
用户层程序使用 DeviceIoControl 将 IOCTL 控制码、输入缓冲区、输出缓冲区传入到内核;内核响应 IRP_MJ_DEVICE_CONTRL 消息,并从 IRP 中获取传入的 IOCTL 控制码、输入缓冲区、输出缓冲区,以此实现数据的交互。
但是,当内核层想主动传递数据到用户层,用户层又怎样才能知道呢?因为只有用户层知道内核层有数据输出的时候,它才会调用 DeviceIoControl 函数去获取数据。所以,本文要介绍的就是基于事件 EVENT 实现的同步框架,可以解决这个的问题。
函数介绍
// 创建或打开一个事件对象。如果想为对象指定一个访问掩码,应当使用CreateEventEx函数。
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes, // 安全属性
BOOLbManualReset, // 复位方式
BOOLbInitialState, // 初始状态
LPCTSTRlpName // 对象名称
);
// 提供对象句柄访问许可。如果访问被允许,返回相应的对象体的指针。
NTSTATUS ObReferenceObjectByHandle(
_In_ HANDLE Handle, // 句柄。
_In_ ACCESS_MASK DesiredAccess, // 访问对象的类型。
_In_opt_ POBJECT_TYPE ObjectType, // 指向对象类型。
_In_ KPROCESSOR_MODE AccessMode, // 访问模式。
_Out_ PVOID *Object, // 指向映射句柄对象的指针。
_In_opt_ POBJECT_HANDLE_INFORMATION HandleInformation // 驱动程序设置为 NULL。
);
实现原理
我们通过事件 EVENT 实现用户层与内核层的同步操作,具体的实现原理如下:
- 首先,我们在用户层程序中调用 CreateEvent 函数创建事件 EVENT 并获取事件 EVENT 的句柄。事件初始状态为无信号,而且自动复原。
- 然后,用户层程序调用 DeviceIoControl 函数将上述创建的 EVENT 句柄传递到内核层驱动程序中,并调用 WaitForSingleObject 函数等待事件 EVENT 的响应。直到事件 EVENT 响应后,程序才会进行下一步操作。
- 接着,内核层程序就通过 IRP_MJ_DEVICE_CONTROL 消息响应函数获取从用户层传入的事件 EVENT 的句柄。调用 ObReferenceObjectByHandle 内核函数获取内核事件 EVENT 对象。
- 然后,内核驱动程序可以调用 PsCreateSystemThread 创建多线程,继续执行操作。要想通知用户层程序进行下一步操作,只需调用 KeSetEvent 内核函数,将事件 EVENT 对象的状态设置为信号状态。那么,用户层程序中的事件 EVENT 就是一个有信号状态,WaitForSingleObject 函数就不会阻塞,而是继续往下执行。这样,就可以成功从内核层通知到用户层进行操作了。
- 最后,用户层调用 CloseHandle 关闭事件 EVENT 句柄;内核层调用 ObDereferenceObject 释放内核事件 EVENT 对象。
这个框架的核心原理就是,将用户层的事件句柄传入内核层的驱动程序中,并有内核驱动程序设置事件对象的信号状态,以此触发用户层的响应。
编码实现
用户层代码:
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hEvent = NULL;
HANDLE hDevice = NULL;
char szOutput[MAX_PATH] = { 0 };
DWORD dwOutput = MAX_PATH;
DWORD dwRet = 0;
BOOL bRet = FALSE;
// 创建事件, 设置自动复位,初始状态为无信号
hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == hEvent)
{
printf("CreateEvent Error[%d]\n", ::GetLastError());
}
// 打开设备
hDevice = ::CreateFile(SYM_NAME, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE, NULL);
if (INVALID_HANDLE_VALUE == hDevice)
{
printf("CreateFile Error[%d]\n", ::GetLastError());
}
// 数据交互, 向内核层中传入事件句柄
bRet = ::DeviceIoControl(hDevice, IOCTL_MY_TEST, &hEvent, sizeof(hEvent), szOutput, dwOutput, &dwRet, NULL);
if (FALSE == bRet)
{
printf("DeviceIoControl Error[%d]\n", ::GetLastError());
}
// 一直等待事件的响应
::WaitForSingleObject(hEvent, INFINITE);
// 数据交互, 从内核层中获取数据
bRet = ::DeviceIoControl(hDevice, IOCTL_MY_OUTPUT, NULL, 0, szOutput, dwOutput, &dwRet, NULL);
if (FALSE == bRet)
{
printf("DeviceIoControl Error[%d]\n", ::GetLastError());
}
// 显示
printf("[From Kernel Output]%s\n", szOutput);
// 关闭设备句柄
::CloseHandle(hEvent);
::CloseHandle(hDevice);
system("pause");
return 0;
}
内核层代码:
// IRP_MJ_DEVICE_CONTROL 消息处理函数
NTSTATUS DriverControlHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
// 获取当前 IRP 栈空间数据
PIO_STACK_LOCATION pIoStackLocation = IoGetCurrentIrpStackLocation(pIrp);
// 获取输入/输出缓冲区
PVOID pBuffer = pIrp->AssociatedIrp.SystemBuffer;
// 获取输入缓冲区数据长度
ULONG ulInputLength = pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
// 获取输出缓冲区数据长度
ULONG ulOutputLength = pIoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
// 实际输出数据长度
ULONG ulInfo = 0;
// 获取控制码
ULONG ulControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode;
// 根据操作码分别进行操作
switch (ulControlCode)
{
case IOCTL_MY_TEST:
{
// 获取传入的事件句柄
HANDLE hUserEvent = *(HANDLE *)pBuffer;
// 处理类型32位、64位下类型不匹配的情况
if (4 == ulInputLength)
{
hUserEvent = (HANDLE)((SIZE_T)hUserEvent & 0x0FFFFFFFF);
}
// 根据事件句柄获取内核事件对象
status = ObReferenceObjectByHandle(hUserEvent, EVENT_MODIFY_STATE, *ExEventObjectType, KernelMode, (PVOID)(&g_pKernelEvent), NULL);
if (!NT_SUCCESS(status))
{
DbgPrint("ObReferenceObjectByHandle Error[0x%X]\n", status);
g_pKernelEvent = NULL;
break;
}
// 创建多线程, 执行操作, 执行完毕后, 发送事件通知用户层
HANDLE hThread = NULL;
PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, ThreadProc, g_pKernelEvent);
break;
}
case IOCTL_MY_OUTPUT:
{
RtlCopyMemory(pBuffer, g_szOutputBuffer, 50);
ulInfo = 50;
break;
}
default:
break;
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = ulInfo;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
// 多线程处理函数
VOID ThreadProc(PVOID StartContext)
{
DbgPrint("Enter ThreadProc\n");
// 获取内核对象
PKEVENT pKernelEvent = (PKEVENT)StartContext;
// 设置输出缓冲区
RtlCopyMemory(g_szOutputBuffer, "I am DemonGan From Kernel Event.", (1 + strlen("I am DemonGan From Kernel Event.")));
// 发送事件, 将事件对象设置为信号状态
if (NULL != pKernelEvent)
{
KeSetEvent(pKernelEvent, IO_NO_INCREMENT, FALSE);
}
// 释放事件对象
ObDereferenceObject(pKernelEvent);
pKernelEvent = NULL;
DbgPrint("Leave ThreadProc\n");
}