我是靠谱客的博主 俭朴金针菇,最近开发中收集的这篇文章主要介绍Windows的事件通知机制事件通知机制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Windows系统内核提供了一系列的事件通知(Notify)机制以及回调(Callback)机制。
事件通知机制:用于监控系统内某一事件的操作。
回调机制:用来反映系统内某一个部件的状态,也可以被用来实现多个内核模块之间的通信。

本文主要介绍一下事件通知机制!

事件通知机制

常见的事件通知有:

  1. 创建进程通知(CreateProcessNotify)

  2. 创建线程通知(CreateThreadNotify)

  3. 加载模块通知(LoadImageNotify)

  4. 注册表操作通知

注意:一旦事件通知被成功注册后,在不需要时(如驱动卸载),一定要进行移除操作,否则系统会崩溃。

创建进程通知

当一个进程被创建以及一个进程结束时,系统会有一个通知的时机,可以通过PsSetCreateProcessNotifyRoutine函数注册一个创建进程通知。

NTSTATUS
NTAPI
PsSetCreateProcessNotifyRoutine(
_In_ PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,// 通知例程指针
_In_ BOOLEAN Remove); // 为FALSE表示注册操作,为TRUE表示移除操作
typedef
VOID
(NTAPI *PCREATE_PROCESS_NOTIFY_ROUTINE)(
_In_ HANDLE ParentId,	// 父进程ID
_In_ HANDLE ProcessId,	// 引发该通知的进程ID
_In_ BOOLEAN Create);
// 为TRUE表示创建进程通知,为FALSE表示进程结束通知

通知例程没有返回值,不能通过返回值来使系统改变进程创建或结束的行为。

另外,通知的过程是以阻塞方式进行的,如果前面一个通知例程没有返回,下一个通知例程也不会被调用。

从Vista系统开始,系统提供了PsSetCreateProcessNotifyRoutineEx来注册进程通知,它和PsSetCreateProcessNotifyRoutine的不同在于,它可以控制进程创建的结果(通过CreateInfo->CreationStatus)

NTSTATUS
NTAPI
PsSetCreateProcessNotifyRoutineEx(
IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
IN BOOLEAN Remove);
typedef
VOID
(NTAPI *PCREATE_PROCESS_NOTIFY_ROUTINE_EX)(
_Inout_ PEPROCESS Process,	// 引发该通知的进程EPROCESS
_In_ HANDLE ProcessId,
// 进程ID
_Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);	// 创建进程通知时表示与进程创建相关的信息,进程结束通知时为NULL
typedef struct _PS_CREATE_NOTIFY_INFO {
SIZE_T Size;
union
{
_In_ ULONG Flags;
struct
{
ULONG FileOpenNameAvailable:1;
ULONG Reserved:31;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
HANDLE ParentProcessId;
// 父进程ID
CLIENT_ID CreatingThreadId;	// 父进程信息
struct _FILE_OBJECT *FileObject;	// 进程可执行文件的文件对象指针
PCUNICODE_STRING ImageFileName;	// 进程名称
PCUNICODE_STRING CommandLine;	// 命令行
NTSTATUS CreationStatus;	// 进程的创建结果,修改该值为某一错误码可以阻止进程创建,常用的错误码是0xC0000022(STATUS_ACCESS_DENIED),表示拒绝操作。
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
typedef struct _CLIENT_ID
{
HANDLE UniqueProcess;	// 父进程ID
HANDLE UniqueThread;	// 父进程创建子进程的线程ID
} CLIENT_ID, *PCLIENT_ID;

有一个问题,通知例程是被哪一个线程调用的呢?

答案是:

对于创建来说,通知例程运行在创建该进程的线程上下文中。例如,进程A调用CreateProcess函数创建子进程B,那么通知例程就运行在进程A中线程的上下文中。

对于结束来说,通知例程运行在该进程最后一个退出的线程的上下文中。

创建线程通知

当一个线程被创建或结束时,系统也会有一个通知。可以通过PsSetCreateThreadNotifyRoutine函数注册一个创建线程通知。

与创建进程通知不同的是,创建线程通知的注册和移除是通过两个函数完成的。

// 注册
NTSTATUS
NTAPI
PsSetCreateThreadNotifyRoutine(
_In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
// 移除
NTSTATUS
NTAPI
PsRemoveCreateThreadNotifyRoutine(
IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
typedef VOID
(NTAPI *PCREATE_THREAD_NOTIFY_ROUTINE)(
_In_ HANDLE ProcessId,	// 线程所属进程的ID
_In_ HANDLE ThreadId,	// 引发该通知的线程ID
_In_ BOOLEAN Create);	// TRUE表示创建线程,FALSE表示线程结束

创建线程时,通知例程在创建线程的上下文中。例如,ID为1000的线程创建ID为2000的线程,那么通知例程在ID为1000的线程上下文中。

线程结束时,通知例程在即将结束线程的上下文中。

加载模块通知

当一个模块被加载时,会触发一个加载模块的通知。当通知发生时,模块已经被映射到内存中,但是模块内的代码还没有开始执行。

// 注册
NTSTATUS
NTAPI
PsSetLoadImageNotifyRoutine(
_In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
// 移除
NTSTATUS
NTAPI
PsRemoveLoadImageNotifyRoutine(
_In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
typedef VOID
(NTAPI *PLOAD_IMAGE_NOTIFY_ROUTINE)(
_In_ PUNICODE_STRING FullImageName,	// 模块路径,有可能为NULL
_In_ HANDLE ProcessId,
// 加载该模块的进程ID
_In_ PIMAGE_INFO ImageInfo);
// 模块加载的详细信息
typedef struct _IMAGE_INFO {
union
{
ULONG Properties;
struct
{
ULONG ImageAddressingMode:8;	// 值为IMAGE_ADDRESSING_MODE_32BIT
ULONG SystemModeImage:1;
// 1表示内核模块,0表示用户模块
ULONG ImageMappedToAllPids:1;	// 值为0
ULONG ExtendedInfoPresent:1;	// 值为1时表示当前结构体为扩展结构体IMAGE_INFO_EX的一部分
ULONG Reserved:21;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
PVOID ImageBase;
// 模块加载的基地址
ULONG ImageSelector;	// 值为0
SIZE_T ImageSize;
// 模块大小
ULONG ImageSectionNumber;	// 值为0
} IMAGE_INFO, *PIMAGE_INFO;
// 可以通过CONTAINING_RECORD来获得IMAGE_INFO_EX的地址
typedef struct _IMAGE_INFO_EX {
SIZE_T Size;
IMAGE_INFO ImageInfo;
struct _FILE_OBJECT *FileObject;	// 加载模块对应的文件对象指针
} IMAGE_INFO_EX, *PIMAGE_INFO_EX;

注册表操作通知

也称为注册表操作回调或注册表回调。但这个回调和下面的回调不一样。

// 注册
NTSTATUS
NTAPI
CmRegisterCallback(
IN PEX_CALLBACK_FUNCTION Function,	// 回调例程
IN PVOID Context,	// 用户自定义的数据结构,用于数据传递,不需要可设为NULL
IN OUT PLARGE_INTEGER Cookie);	// 注册成功时,会保存注册的信息,最后移除时需要提供它
// 移除
NTSTATUS
NTAPI
CmUnRegisterCallback(IN LARGE_INTEGER Cookie)
NTSTATUS
RegistryCallback(
IN PVOID CallbackContext,
// 就是CmRegisterCallback的第二参数
IN PVOID Argument1 OPTIONAL,
// 注册表操作的类型,是个枚举
IN PVOID Argument2 OPTIONAL);
typedef enum _REG_NOTIFY_CLASS {
RegNtDeleteKey,
RegNtPreDeleteKey = RegNtDeleteKey,
RegNtSetValueKey,
RegNtPreSetValueKey = RegNtSetValueKey,
RegNtDeleteValueKey,
RegNtPreDeleteValueKey = RegNtDeleteValueKey,
RegNtSetInformationKey,
RegNtPreSetInformationKey = RegNtSetInformationKey,
RegNtRenameKey,
RegNtPreRenameKey = RegNtRenameKey,
RegNtEnumerateKey,
RegNtPreEnumerateKey = RegNtEnumerateKey,
RegNtEnumerateValueKey,
RegNtPreEnumerateValueKey = RegNtEnumerateValueKey,
RegNtQueryKey,
RegNtPreQueryKey = RegNtQueryKey,
RegNtQueryValueKey,
RegNtPreQueryValueKey = RegNtQueryValueKey,
RegNtQueryMultipleValueKey,
RegNtPreQueryMultipleValueKey = RegNtQueryMultipleValueKey,
RegNtPreCreateKey,
RegNtPostCreateKey,
RegNtPreOpenKey,
RegNtPostOpenKey,
RegNtKeyHandleClose,
RegNtPreKeyHandleClose = RegNtKeyHandleClose,
RegNtPostDeleteKey,
RegNtPostSetValueKey,
RegNtPostDeleteValueKey,
RegNtPostSetInformationKey,
RegNtPostRenameKey,
RegNtPostEnumerateKey,
RegNtPostEnumerateValueKey,
RegNtPostQueryKey,
RegNtPostQueryValueKey,
RegNtPostQueryMultipleValueKey,
RegNtPostKeyHandleClose,
RegNtPreCreateKeyEx,
RegNtPostCreateKeyEx,
RegNtPreOpenKeyEx,
RegNtPostOpenKeyEx,
RegNtPreFlushKey,
RegNtPostFlushKey,
RegNtPreLoadKey,
RegNtPostLoadKey,
RegNtPreUnLoadKey,
RegNtPostUnLoadKey,
RegNtPreQueryKeySecurity,
RegNtPostQueryKeySecurity,
RegNtPreSetKeySecurity,
RegNtPostSetKeySecurity,
RegNtCallbackObjectContextCleanup,
RegNtPreRestoreKey,
RegNtPostRestoreKey,
RegNtPreSaveKey,
RegNtPostSaveKey,
RegNtPreReplaceKey,
RegNtPostReplaceKey,
MaxRegNtNotifyClass
} REG_NOTIFY_CLASS, *PREG_NOTIFY_CLASS;

注册表具体的每一个操作,被分解成前操作(pre)和后操作(post)。

不同的枚举值代表不同的注册表操作,不同的操作也对应不同的操作信息,这个信息存在Argument2中。

例如:当Argument1为RegNtPreCreateKey时,Argument2作为指针指向REG_PRE_CREATE_KEY+INFORMATION类型的结构体,结构体内包含相应的操作信息或结果。

回调例程若返回STATUS_SUCCESS表示成功,配置管理器会继续处理这个操作。

若返回一个错误码,如STATUS_ACCESS_DENIED,配置管理器会停止处理这个注册表请求,并把错误码作为最终的操作结果返回给调用线程。

测试代码:

typedef
NTSTATUS(_stdcall *LPFN_PSSETCREATEPROCESSNOTIFYROUTINEEX)(
IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX
NotifyRoutine,
IN BOOLEAN
Remove
);
LPFN_PSSETCREATEPROCESSNOTIFYROUTINEEX __PsSetCreateProcessNotifyRoutineEx = NULL;
BOOLEAN bUseEx = FALSE;
BOOLEAN bSuccess = FALSE;
LARGE_INTEGER gCookie = { 0 };
VOID
CreateThreadNotify(
HANDLE ProcessId,
HANDLE ThreadId,
BOOLEAN Create)
{
if (Create)
{
DbgPrint("创建线程,线程ID: %08X 进程ID: %08X", ThreadId, ProcessId);
}
else
{
DbgPrint("终止线程,线程ID: %08X 进程ID: %08X", ThreadId, ProcessId);
}
}
VOID
LoadImageNotify(
PUNICODE_STRING FullImageName,
HANDLE ProcessId,
PIMAGE_INFO ImageInfo)
{
if (FullImageName != NULL)
{
DbgPrint("加载模块:%ws", FullImageName->Buffer);
}
if (ImageInfo != NULL)
{
DbgPrint("模块基地址:%08X", ImageInfo->ImageBase);
}
}
VOID CreateProcessNotify(
IN HANDLE
ParentId,
IN HANDLE
ProcessId,
IN BOOLEAN
Create)
{
if (Create)
{
DbgPrint("创建进程,进程ID: %08X 父进程ID: %08X", ProcessId, ParentId);
}
else
{
DbgPrint("终止进程,进程ID: %08X 父进程ID: %08X", ProcessId, ParentId);
}
}
VOID
CreateProcessNotifyEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo)
{
if (CreateInfo != NULL)
{
DbgPrint("创建进程Ex,进程ID: %08X 父进程ID: %08X", ProcessId, CreateInfo->ParentProcessId);
}
else
{
DbgPrint("终止进程Ex,进程ID: %08X 父进程ID: %08X", ProcessId, CreateInfo->ParentProcessId);
}
}
void DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
if (!bSuccess)
{
return;
}
if (bUseEx && __PsSetCreateProcessNotifyRoutineEx)
{
__PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx, TRUE);
}
else
{
PsSetCreateProcessNotifyRoutine(CreateProcessNotify, TRUE);
}
PsRemoveCreateThreadNotifyRoutine(CreateThreadNotify);
PsRemoveLoadImageNotifyRoutine(LoadImageNotify);
}
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
//解引用
UNREFERENCED_PARAMETER(RegisterPath);
NTSTATUS Status = STATUS_SUCCESS;
do
{
UNICODE_STRING FunctionName = { 0 };
RtlInitUnicodeString(&FunctionName, L"PsSetCreateProcessNotifyRoutineEx");
__PsSetCreateProcessNotifyRoutineEx = (LPFN_PSSETCREATEPROCESSNOTIFYROUTINEEX)MmGetSystemRoutineAddress(&FunctionName);
if (__PsSetCreateProcessNotifyRoutineEx == NULL)
{
break;
}
if (__PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx, FALSE) != STATUS_SUCCESS)
{
break;
}
bSuccess = TRUE;
bUseEx = TRUE;
Status = STATUS_SUCCESS;
} while (FALSE);
do
{
if (bUseEx == TRUE)
{
break;
}
if (PsSetCreateProcessNotifyRoutine(CreateProcessNotify, FALSE) != STATUS_SUCCESS)
{
break;
}
bSuccess = TRUE;
Status = STATUS_SUCCESS;
} while (FALSE);
if (PsSetCreateThreadNotifyRoutine(CreateThreadNotify) != STATUS_SUCCESS)
{
DbgPrint("PsSetCreateThreadNotifyRoutine() Failed");
}
if (PsSetLoadImageNotifyRoutine(LoadImageNotify) != STATUS_SUCCESS)
{
DbgPrint("PsSetLoadImageNotifyRoutine() Failed");
}
//设置驱动卸载例程
DriverObject->DriverUnload = DriverUnload;
return Status;
}

最后

以上就是俭朴金针菇为你收集整理的Windows的事件通知机制事件通知机制的全部内容,希望文章能够帮你解决Windows的事件通知机制事件通知机制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部