Windows系统内核提供了一系列的事件通知(Notify)机制以及回调(Callback)机制。
事件通知机制:用于监控系统内某一事件的操作。
回调机制:用来反映系统内某一个部件的状态,也可以被用来实现多个内核模块之间的通信。
本文主要介绍一下事件通知机制!
常见的事件通知有:
创建进程通知(CreateProcessNotify)
创建线程通知(CreateThreadNotify)
加载模块通知(LoadImageNotify)
注册表操作通知
注意:一旦事件通知被成功注册后,在不需要时(如驱动卸载),一定要进行移除操作,否则系统会崩溃。
当一个进程被创建以及一个进程结束时,系统会有一个通知的时机,可以通过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; }