内核事件 KEVENT 实现驱动与应用层通讯

前段时间一直在学习内核监控进程创建的知识,虽然成功监视,但一直在内核输出到 DebugView 中,不能通知我们的应用程序来显示指定内容,无论如何也不方便,所以赶在周末参考了 Windows 内核安全与驱动开发 中第五章 “应用与内核通讯” 制作了以下程序。程序主要使用了内核事件 KEVENT 实现同步,更多请参考 Windows 内核安全与驱动开发,程序运行后的效果如下:

2016-08-21_203643

可以看到,程序可以成功运行在 Win10x64 环境下,下面我们分别仔细的讲解一下程序的细节。程序的主要工作流程如下图:

2016-08-21_211926

先来看一下 DriverEntry 入口函数。函数中先创建了进程创建后的回调监听函数,我们在这个函数里面实现对进程的监控。随后初始化了链表锁、链表头(用于存放已经创建的进程数据)及事件句柄。最后入口函数设置了各个通知函数:

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    NTSTATUS status = STATUS_SUCCESS;

    KdPrint(("pRegistryPath = %wZ", pRegistryPath));

    // 创建进程监视回调
    status = PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx, FALSE);
    if (!NT_SUCCESS(status))
    {
        KdPrint(("Failed to call PsSetCreateProcessNotifyRoutineEx, error code = 0x%08X", status));
    }

    // 初始化事件、锁、链表头
    KeInitializeEvent(&g_Event, SynchronizationEvent, TRUE);
    KeInitializeSpinLock(&g_Lock);
    InitializeListHead(&g_ListHead);

    // 创建设备和符号链接
    CreateDevice(pDriverObject);

    // 指派各分发函数
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateCompleteRoutine;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseCompleteRoutine;
    pDriverObject->MajorFunction[IRP_MJ_READ] = ReadCompleteRoutine;
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = WriteCompleteRoutine;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlCompleteRoutine;

    pDriverObject->DriverUnload = DriverUnLoad;

    return status;
}

接下来我们看一下创建进程会调用的回调函数中,我们设定若发现新进程创建,则将进程的信息作为链表一个节点插入到链表中,并设置全局的 g_Event 为有信号状态。这里一定要注意 KeSetEvent 函数的使用,如果第三个参数设置为 TRUE 的话,100% 会蓝屏,代码为 0x0000004A。

VOID CreateProcessNotifyEx(
    _Inout_  PEPROCESS              Process,
    _In_     HANDLE                 ProcessId,
    _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
)
{
    // 如果 CreateInfo 结构不为 NULL 则证明是创建进程
    if (NULL != CreateInfo)
    {
        // 创建一个链表节点
        PPROCESSNODE pNode = InitListNode();
        if (pNode != NULL)
        {
            // 给节点的 pProcessInfo 分配内存
            // 该 ProcessInfo 结构体与应用层使用的是同样的结构体
            // 应用层传入相同大小的内存提供内核写入相应数据
            pNode->pProcessInfo = ExAllocatePoolWithTag(NonPagedPool, sizeof(PROCESSINFO), MEM_TAG);

            // 给各节点赋值
            pNode->pProcessInfo->hParentId = CreateInfo->ParentProcessId;
            pNode->pProcessInfo->hProcessId = ProcessId;
            RtlFillMemory(pNode->pProcessInfo->wsProcessPath, MAX_STRING_LENGTH, 0x0);
            RtlFillMemory(pNode->pProcessInfo->wsProcessCommandLine, MAX_STRING_LENGTH, 0x0);
            wcsncpy(pNode->pProcessInfo->wsProcessPath, CreateInfo->ImageFileName->Buffer, CreateInfo->ImageFileName->Length / sizeof(WCHAR));
            wcsncpy(pNode->pProcessInfo->wsProcessCommandLine, CreateInfo->CommandLine->Buffer, CreateInfo->CommandLine->Length / sizeof(WCHAR));

            // 插入链表,设置事件
            ExInterlockedInsertTailList(&g_ListHead, (PLIST_ENTRY)pNode, &g_Lock);

            // 这里第三个参数一定要注意,如果为 TRUE 则表示 KeSetEvent 后面一定会有一个 KeWaitForSigleObject
            // 而如果 KeWaitForSigleObject 不在 KeSetEvent 调用之后,则设置为 FLASE,否则会导致 0x0000004A 蓝屏
            KeSetEvent(&g_Event, 0, FALSE);
        }
    }
}

此时若有新进程创建全局的 g_Event 会被设置为有信号状态,接下来就到我们处理应用层使用 DeviceIoControl 在驱动中的响应功能了。我们设置了一个无限循环,一直从链表中取数据,若取出的数据为 NULL,则等待 g_Event,当 g_Event 为有信号状态时,证明有新进程创建了,那么 KeWaitForSigleObject 立即返回执行下一次循环,这次循环就可以取到链表中的节点信息了,取到以后直接拷贝给应用层提供的 Buffer 中。应用层接收并打印内容。代码如下:

NTSTATUS DeviceControlCompleteRoutine(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    NTSTATUS            status = STATUS_SUCCESS;
    PIO_STACK_LOCATION  pIrpsp = IoGetCurrentIrpStackLocation(pIrp);
    ULONG               uLength = 0;

    PVOID pBuffer           = pIrp->AssociatedIrp.SystemBuffer;
    ULONG ulInputlength     = pIrpsp->Parameters.DeviceIoControl.InputBufferLength;
    ULONG ulOutputlength    = pIrpsp->Parameters.DeviceIoControl.OutputBufferLength;

    do 
    {
        switch (pIrpsp->Parameters.DeviceIoControl.IoControlCode)
        {
        case CWK_DVC_SEND_STR:          // 接收到发送数据请求
            {
                ASSERT(pBuffer != NULL);
                ASSERT(ulInputlength > 0);
                ASSERT(ulOutputlength == 0);

                // KdPrint(("pBuffer = %s", pBuffer));
            }
            break;
        case CWK_DVC_RECV_STR:          // 接收到获取数据请求
            {
                ASSERT(pBuffer != NULL);
                ASSERT(ulInputlength == 0);

                // 如果给出的 Buffer 大小小于 PROCESSINFO 结构体大小,则判断非法
                if (ulOutputlength < sizeof(PROCESSINFO))
                {
                    status = STATUS_INVALID_BUFFER_SIZE;
                    break;
                }

                // 创建一个循环,不断从链表中拿是否有节点
                while (TRUE)
                {
                    PPROCESSNODE pNode = (PPROCESSNODE)ExInterlockedRemoveHeadList(&g_ListHead, &g_Lock);

                    // 如果拿到了节点,则传给应用层,直接想 pBuffer 里面赋值,应用层 DeviceIoControl 就能收到数据
                    if (NULL != pNode)
                    {
                        PPROCESSINFO pProcessInfo = (PPROCESSINFO)pBuffer;
                        if (NULL != pNode->pProcessInfo)
                        {
                            // 给应用层 Buffer 赋值
                            pProcessInfo->hParentId = pNode->pProcessInfo->hParentId;
                            pProcessInfo->hProcessId = pNode->pProcessInfo->hProcessId;
                            wcsncpy(pProcessInfo->wsProcessPath, pNode->pProcessInfo->wsProcessPath, MAX_STRING_LENGTH);
                            wcsncpy(pProcessInfo->wsProcessCommandLine, pNode->pProcessInfo->wsProcessCommandLine, MAX_STRING_LENGTH);
                            uLength = sizeof(PROCESSINFO);

                            // 释放内存
                            ExFreePoolWithTag(pNode->pProcessInfo, MEM_TAG);
                        }

                        // 释放内存
                        ExFreePoolWithTag(pNode, MEM_TAG);
                        break;
                    }
                    else
                    {
                        // 如果没有取到节点,则等待一个事件通知,该事件在 CreateProcessNotifyEx 函数中会被设置
                        // 当产生一个新的进程时会向链表插入一个节点,同时该事件被设置为有信号状态
                        // 随后 KeWaitForSingleObject 返回继续执行循环,继续执行时就可以取到新的节点数据了
                        KeWaitForSingleObject(&g_Event, Executive, KernelMode, 0, 0);
                    }
                }
            }
            break;
        default:
        {
            status = STATUS_INVALID_PARAMETER;
        }
        break;
        }
    } while (FALSE);


    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = uLength;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);

    return status;
}

上面是驱动层的实现,我们来看一下应用层的示例代码。通过 CreateFile 打开设备,并调用 DeviceIoControl 函数向驱动发送一个接收数据的请求。此时如果驱动链表中没有数据,那么会停在 KeWaitForSingleObject 函数,同时应用层也阻塞在 DeviceIoControl 函数上。一旦 g_Event 被设置为有信号状态,则 KeWaitForSingleObject 返回,拷贝数据给应用层提供的 Buffer,应用层接收数据打印。

// TestCommunication.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "NtStructDef.h"

#define SYMBOLIC_NAME _T("\\??\\Communication")

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE      hStdHandle;
    HANDLE      hDevice = NULL;
    ULONG       ulResult = 0;
    BOOL        bRet = FALSE;

    // 设置控制台窗口大小,方便查看
    hStdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    SMALL_RECT rc = { 0, 0, 120 - 1, 25 - 1 };  
    SetConsoleWindowInfo(hStdHandle, TRUE, &rc);

    // 打开驱动设备
    hDevice = CreateFile(SYMBOLIC_NAME, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("Failed to Open device.\r\n");
        return -1;
    }

    // Receive message from driver
    PROCESSINFO stProcessInfo;
    while (TRUE)
    {
        memset(&stProcessInfo, 0, sizeof(PROCESSINFO));
        bRet = DeviceIoControl(hDevice, CWK_DVC_RECV_STR, NULL, 0, &stProcessInfo, sizeof(stProcessInfo), &ulResult, 0);
        if (bRet)
        {
            // 打印数据,wsProcessCommandLine 也是一个参数,如果需要可以自己放开,格式化字符串中增加一个 %ws
            printf("PPID = %ld, PID = %ld, %ws\r\n",
                stProcessInfo.hParentId,
                stProcessInfo.hProcessId,
                stProcessInfo.wsProcessPath/*,
                stProcessInfo.wsProcessCommandLine*/);
        }
    }

    CloseHandle(hDevice);
    system("PAUSE");

    return 0;
}

这里请注意应用层和驱动层使用了相同的 PROCESSINFO 结构体,该结构体包含了创建进程的各种信息,我们使用一个公共头文件保存他,所有代码请参考 github:https://github.com/nmgwddj/Learn-Windows-Drivers,其中 Communication 是驱动层的实现,TestCommunication 是应用层的实现。

4 评论

  1. Hi! I download you samples drivers from github. But can’t compiler it (Windows 7 SP1 x64, VS2015SP3, latest SDK and WDK):

    1>—— Build started: Project: Communication, Configuration: Debug x64 ——
    1> Building ‘Communication’ with toolset ‘WindowsKernelModeDriver10.0’ and the ‘Desktop’ target platform.
    1> Stamping x64\Debug\Communication.inf [Version] section with DriverVer=05/06/2017,16.7.33.358
    1> DriverEntry.c
    1>DriverEntry.c(153): error C2065: ‘g_ListHead’: undeclared identifier
    1>DriverEntry.c(153): warning C4133: ‘function’: incompatible types – from ‘int *’ to ‘PLIST_ENTRY’
    1>DriverEntry.c(153): error C2065: ‘g_Lock’: undeclared identifier
    1>DriverEntry.c(153): warning C4133: ‘function’: incompatible types – from ‘int *’ to ‘PKSPIN_LOCK’
    1>DriverEntry.c(162): error C2039: ‘hParentId’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(164): error C2039: ‘wsProcessPath’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(164): warning C4047: ‘function’: ‘wchar_t *’ differs in levels of indirection from ‘int’
    1>DriverEntry.c(164): warning C4024: ‘wcsncpy’: different types for formal and actual parameter 1
    1>DriverEntry.c(164): error C2198: ‘wcsncpy’: too few arguments for call
    1>DriverEntry.c(165): error C2039: ‘wsProcessCommandLine’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(165): warning C4047: ‘function’: ‘wchar_t *’ differs in levels of indirection from ‘int’
    1>DriverEntry.c(165): warning C4024: ‘wcsncpy’: different types for formal and actual parameter 1
    1>DriverEntry.c(165): error C2198: ‘wcsncpy’: too few arguments for call
    1>DriverEntry.c(181): error C2065: ‘g_Event’: undeclared identifier
    1>DriverEntry.c(221): error C2039: ‘hParentId’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(223): error C2039: ‘wsProcessPath’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(223): error C2168: ‘memset’: too few actual parameters for intrinsic function
    1>DriverEntry.c(224): error C2039: ‘wsProcessCommandLine’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(224): error C2168: ‘memset’: too few actual parameters for intrinsic function
    1>DriverEntry.c(225): error C2039: ‘wsProcessPath’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(225): warning C4047: ‘function’: ‘const wchar_t *’ differs in levels of indirection from ‘std::size_t’
    1>DriverEntry.c(225): warning C4024: ‘wcsncpy’: different types for formal and actual parameter 2
    1>DriverEntry.c(225): error C2198: ‘wcsncpy’: too few arguments for call
    1>DriverEntry.c(226): error C2039: ‘wsProcessCommandLine’: is not a member of ‘_PROCESSINFO’
    1> E:\Drivers\Learn-Windows-Drivers\Common\NtStructDef.h(130): note: see declaration of ‘_PROCESSINFO’
    1>DriverEntry.c(226): warning C4047: ‘function’: ‘const wchar_t *’ differs in levels of indirection from ‘std::size_t’
    1>DriverEntry.c(226): warning C4024: ‘wcsncpy’: different types for formal and actual parameter 2
    1>DriverEntry.c(226): error C2198: ‘wcsncpy’: too few arguments for call
    1>DriverEntry.c(229): error C2065: ‘g_ListHead’: undeclared identifier
    1>DriverEntry.c(229): warning C4133: ‘function’: incompatible types – from ‘int *’ to ‘PLIST_ENTRY’
    1>DriverEntry.c(229): error C2065: ‘g_Lock’: undeclared identifier
    1>DriverEntry.c(229): warning C4133: ‘function’: incompatible types – from ‘int *’ to ‘PKSPIN_LOCK’
    1>DriverEntry.c(233): error C2065: ‘g_Event’: undeclared identifier
    1>DriverEntry.c(233): warning C4133: ‘function’: incompatible types – from ‘int *’ to ‘PRKEVENT’

    1. I’m sorry, I have modified the code for this project and bring this error.
      Please try it:
      1. Open cmd.exe in project directory.
      2. Type “git reset –hard d085b2b1b634ec9f33c1a0a6c1039101b6dcc68f”.
      3. Recompile the project.

  2. Спасибо огромное! Очень вам признателен! Доработаете ли вы новый вариант вашего набора драйверов? Чтобы его можно было скомпилировать.

  3. Sorry! 🙂 Thank you very much! Very much to you it is grateful! Will you modify the new version of your driver set? So that it can be compiled.

发表评论