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;
}

相关文章

Windows2012R2备用域控搭建 前置操作 域控主域控的主dns:自...
主域控角色迁移和夺取(转载) 转载自:http://yupeizhi.blo...
Windows2012R2 NTP时间同步 Windows2012R2里没有了internet时...
Windows注册表操作基础代码 Windows下对注册表进行操作使用的...
黑客常用WinAPI函数整理之前的博客写了很多关于Windows编程的...
一个简单的Windows Socket可复用框架说起网络编程,无非是建...