: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); // 设置为非受信状态
// 设置回调例程
02-17会员管理插件源码
02-16动态创建菜单与响应事件源码,菜单编辑器
02-09画板快捷启动3.2源码修改版
02-09王者荣耀战力小程序源码分享
02-09简单的指定颜色抠图源码
02-05WinLicense授权SDK源码
02-05文本逐字分割源码及优化历程
02-05易语言调用cmd命令并编辑框显示执行结果
09-07C++的string的实现源码分析
10-15易语言word循环插入文字 图片工具源码