下你所需,载你所想!
汇集开发技术源码资料

系统驱动创建回调与删除方法及思路

:2.304KB :1 :2019-11-14 19:33:12

部分简介

在内核模式下我们同样可以使用API HOOK来实现,但是还有一些简单的做法,比如我们今天要介绍的PsSetCreateProcessNotifyRoutine函数。

PsSetCreateProcessNotifyRoutine通过向系统注册一个回调例程便可以轻松获得关于进程创建、终止的信息,虽然这种方法很多大牛都不屑使用,但对于我们新手学习内核编程入门却还是很有帮助的。

与它类似的函数还有PsSetCreateThreadNotifyRoutine、PsSetLoadImageNotifyRoutine,它们的用法大同小异,因此我们仅以PsSetCreateProcessNotifyRoutine为例来学习。

第一部分:关于PsSetCreateProcessNotifyRoutine

在DDK的帮助文档里是这样介绍这个函数的:PsSetCreateProcessNotifyRoutine adds a driver-supplied callback routine to, or removes it from, a list of routines to be called whenever a process is created or deleted.

从上面可以看出,这个函数不仅可以向系统中注册一个回调例程,该例程在有进程被创建或结束的时候会被调用,而且这个函数还可以从系统中删除我们注册的回调例程(这个很重要,真的!)。

我们先来看一下这个函数的原型声明,如下所示:

NTSTATUS

PsSetCreateProcessNotifyRoutine(

IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,

IN BOOLEAN Remove

);

可以看出它的参数很简单,第一个参数指定了要添加/删除的回调例程;第二个参数表明了是要添加还是删除这个例程。

跟它不太一样的是,PsSetCreateThreadNotifyRoutine和PsSetLoadImageNotifyRoutine函数都只有一个参数,它们的删除操作另有函数,比如PsRemoveCreateThreadNotifyRoutine。

下面我们来看一下进程回调例程的定义:

VOID

(*PCREATE_PROCESS_NOTIFY_ROUTINE) (

IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create

);

它有三个参数,第一个参数指明了父进程的ID,第二个参数是进程本身的ID,最后一个参数指明了该进程是刚刚执行了创建还是结束操作。

有人可能会问,进程ID怎么会是HANDLE类型呢?别问我,DDK的帮助文档里就是这么写的。其实HANDLE与DWORD一样在本质上都是个32位的整数,因此这样定义是没什么问题的。

当我们注册了一个回调例程以后,如果发生了进程创建、结束的事件,系统就会调用该回调例程,这时侯我们就可以通过参数二和三分别得到进程的PID和创建/结束信息。我们可以在回调例程中简单地将这些信息打印出来,或者通知应用层的程序。这就涉及到了驱动程序与应用层程序的通信问题,下面我们进行介绍。

第二部分:关于驱动程序与应用层程序的通信

在多数情况下我们可以通过调用ReadFile/WriteFile或DeviceIoControl函数从驱动程序中读写信息,关于它们的详细使用方法这里就不废话了,简单说一下就行。

在调用上述三个Win32 API时,分别会产生三个与之对应的IRP,即:IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_DEVICE_CONTROL,我们只要在驱动程序中进行相应的响应处理即可。

在多数情况下我们都是使用DeviceIoControl函数,它需要定义一个IOCTL,它需要在驱动程序和应用层程序之间共享,因此较好的做法是使用一个专门的头文件来进行定义。在定义IOCTL的时候需要注意使用哪种I/O方式,常见的做法是使用缓冲区I/O,这时候需要将我们创建的设备对象的Flags设置为DO_BUFFERED_IO,如下所示:

deviceObject->Flags |= DO_BUFFERED_IO;

因为在我使用的这个EasySys生成的驱动框架中没有明确将设备对象的读写方式设置为缓冲区I/O或直接I/O,这样它就会使用默认的其他I/O方式,而起初我没有考虑到这一点,造成了蓝屏的后果~~~

另外,我们的驱动程序和应用层程序之间必须实现同步,这可以通过事件对象来解决,否则我们要么只能在ring3建立一个线程不停地尝试读取数据,要么让驱动先把信息保存起来,我们隔一段时间去读取一次,这样都不是好的做法。

事件对象既可以在应用层创建,也可以在驱动中创建,全在于我们的喜好。

第三部分:代码分析

下面我们来分析代码,首先我们需要定义一些变量,还将一些重要的变量添加为驱动程序的DEVICE_EXTENSION子域(在驱动中要尽量避免使用全局变量),如下所示:

typedef struct _DEVICE_EXTENSION

{

HANDLE hProcessHandle; // 事件对象句柄

PKEVENT ProcessEvent; // 用户和内核通信的事件对象指针

HANDLE hParentId; // 在回调函数中保存进程信息

HANDLE hProcessId;

BOOLEAN bCreate;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

我们现在要做的事情是,在DriverEntry例程中创建一个事件对象并将其句柄保存起来,然后设置回调例程。对了,不要忘记设置I/O方式。因为我们定义的IOCTL是使用的缓冲区I/O,因此这里也要设置成缓冲区I/O方式,相关代码如下所示:

deviceObject->Flags |= DO_BUFFERED_IO;

// 创建符号链接与分发IRP的代码是自动生成的,不用考虑

// 保存设备对象指针

// 这里我们不得不使用全局变量g_pDeviceObject,我们在回调例程中需要用它

// 来得到DEVICE_EXTENSION以获得相关信息

g_pDeviceObject = deviceObject;

// 创建事件对象与应用层通信

RtlInitUnicodeString(&ProcessEventString, EVENT_NAME);

deviceExtension->ProcessEvent = IoCreateNotificationEvent(&ProcessEventString, &deviceExtension->hProcessHandle);

KeClearEvent(deviceExtension->ProcessEvent); // 设置为非受信状态

// 设置回调例程

系统驱动创建回调与删除方法及思路

热门推荐

相关文章