В операционной системе Windows разработчиками было предусмотрено довольно много механизмов получения каких-либо уведомлений и событий. В некоторых случаях разработчик может влиять на возвращаемый результат, в некоторых – нет. В основном считается, что программист не может влиять на возвращаемый результат функций уведомления (notifications) и может влиять на возвращаемый результат функций обратного вызова (callbacks). Однако это совсем не означает, что функции уведомления вызываются асинхронно, поэтому необходимо обязательно возвращать управление из тех и из других.
Предоставленные функции связаны с различными объектами операционной системы, такими как процессы, потоки, файловые образы, объекты системного реестра и так далее.
PsSetLoadImageNotifyRoutine
PsSetLoadImageNotifyRoutine регистрирует функцию уведомления, которая вызывается в момент загрузки образа или отображения образа в память.
NTSTATUS PsSetLoadImageNotifyRoutine( IN PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine );
Функция уведомления должна быть определена следующим образом:
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE) ( IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo );
После регистрации данной функции она будет вызываться операционной системой после отображения в память исполняемого образа в пространстве ядра или в пользовательском пространстве, до начала исполнения образа. Данная функция вызывается, в том числе и в момент загрузки DLL в пользовательском пространстве.
В момент загрузки основного исполняемого образа в память нового процесса данная функция уведомления вызывается в контексте создаваемого процесса.
Пример регистрации функции уведомления:
NTSTATUS SetLoadImageNotifyRoutine(IN PLOAD_IMAGE_NOTIFY_ROUTINE Routine) { PAGED_CODE(); if ( !Routine ) return STATUS_INVALID_PARAMETER; return PsSetLoadImageNotifyRoutine( Routine ); } VOID LoadImageNotifyRoutine(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo) { PAGED_CODE(); KdPrint( ( "LoadImageNotifyRoutine called with FullImageName = %wZ, ProcessId = 0x%08X\n", FullImageName, ProcessId ) ); }
Из переменной ImageInfo можно получить дополнительную информацию. Причем для систем Windows Vista и выше данная структура содержит больше информации, чем для предыдущих версий ОС (см. WDK Help).
Операционная система содержит список (массив) функций уведомления. Например, для Windows XP/2003 – это глобальный массив PspLoadImageNotifyRoutine. Вызов каждой зарегистрированной процедуры происходит последовательно в функции PsCallImageNotifyRoutines.
NTSTATUS MmLoadSystemImage ( IN PUNICODE_STRING ImageFileName, IN PUNICODE_STRING NamePrefix OPTIONAL, IN PUNICODE_STRING LoadedBaseName OPTIONAL, IN ULONG LoadFlags, OUT PVOID *ImageHandle, OUT PVOID *ImageBaseAddress ) { … if (PsImageNotifyEnabled) { IMAGE_INFO ImageInfo; ImageInfo.Properties = 0; ImageInfo.ImageAddressingMode = IMAGE_ADDRESSING_MODE_32BIT; ImageInfo.SystemModeImage = TRUE; ImageInfo.ImageSize = DataTableEntry->SizeOfImage; ImageInfo.ImageBase = *ImageBaseAddress; ImageInfo.ImageSelector = 0; ImageInfo.ImageSectionNumber = 0; PsCallImageNotifyRoutines(ImageFileName, (HANDLE)NULL, &ImageInfo); } … } PsCallImageNotifyRoutines( IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo ) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PLOAD_IMAGE_NOTIFY_ROUTINE Rtn; PAGED_CODE(); if (PsImageNotifyEnabled) { for (i=0; i < PSP_MAX_LOAD_IMAGE_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspLoadImageNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PLOAD_IMAGE_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (FullImageName, ProcessId, ImageInfo); ExDereferenceCallBackBlock (&PspLoadImageNotifyRoutine[i], CallBack); } } } }
Удалить функцию уведомления можно с помощью вызова PsRemoveLoadImageNotifyRoutine, передав указатель на зарегистрированный обработчик.
PsSetCreateProcessNotifyRoutine и PsSetCreateProcessNotifyRoutineEx
PsSetCreateProcessNotifyRoutine и PsSetCreateProcessNotifyRoutineEx позволяют зарегистрировать функцию уведомления на создание и завершение процессов в системе. Последняя из перечисленных функций присутствует только начиная с Windows Vista SP1 и предоставляет больше информации и более удобный интерфейс для использования.
NTSTATUS PsSetCreateProcessNotifyRoutine( IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, IN BOOLEAN Remove ); NTSTATUS PsSetCreateProcessNotifyRoutineEx( IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, IN BOOLEAN Remove );
В отличие от PsSetLoadImageNotifyRoutine для удаления обработчика не нужно вызывать другую функцию, достаточно вызвать ту же самую с параметром Remove равным TRUE.
Обработчики должны быть определены следующим образом:
VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE) ( IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create ); VOID CreateProcessNotifyEx( __inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo );
Структура с дополнительной информацией:
typedef struct _PS_CREATE_NOTIFY_INFO { __in SIZE_T Size; union { __in ULONG Flags; struct { __in ULONG FileOpenNameAvailable : 1; __in ULONG Reserved : 31; }; }; __in HANDLE ParentProcessId; __in CLIENT_ID CreatingThreadId; __inout struct _FILE_OBJECT *FileObject; __in PCUNICODE_STRING ImageFileName; __in_opt PCUNICODE_STRING CommandLine; __inout NTSTATUS CreationStatus; } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
После регистрации функции уведомления операционная система вызывает зарегистрированный обработчик в двух случаях: когда процесс создается и когда процесс завершается. В случае создания процесса функция уведомления вызывается после того как создан начальный поток, но исполнение его еще не началось. В случае завершения процесса операционная система вызывает функцию уведомления, перед тем как последний поток в процессе завершится. В обработчике, зарегистрированном с помощью PsSetCreateProcessNotifyRoutineEx, можно влиять на результат создания процесса. Для этого необходимо использовать член CreationStatus структуры PS_CREATE_NOTIFY_INFO.
В момент создания нового процесса функция уведомления вызывается в контексте потока, который инициировал создание этого процесса. В момент уничтожения процесса функция уведомления вызывается в контексте последнего потока завершаемого процесса.
Пример регистрации функции уведомления:
typedef NTSTATUS (NTAPI* PSSETCREATEPROCESSNOTIFYROUTINEEX_PROC)( IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, IN BOOLEAN Remove ); NTSTATUS SetCreateProcessNotifyRoutine(VOID) { NTSTATUS status; UNICODE_STRING szCreateProcessEx = {0}; PSSETCREATEPROCESSNOTIFYROUTINEEX_PROC pCreateProcessEx = NULL; PAGED_CODE(); RtlInitUnicodeString( &szCreateProcessEx, L"PsSetCreateProcessNotifyRoutineEx" ); pCreateProcessEx = (PSSETCREATEPROCESSNOTIFYROUTINEEX_PROC )MmGetSystemRoutineAddress( &szCreateProcessEx ); if ( pCreateProcessEx ) { status = pCreateProcessEx( CreateProcessNotifyRoutineEx, FALSE ); } else { status = PsSetCreateProcessNotifyRoutine( CreateProcessNotifyRoutine, FALSE ); } return status; } VOID CreateProcessNotifyRoutine(IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create) { PAGED_CODE(); KdPrint( ( "CreateProcessNotifyRoutine called with ParentId = 0x%08X, ProcessId = 0x%08X, Create = %d\n", ParentId, ProcessId, Create ) ); } VOID CreateProcessNotifyRoutineEx(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo) { PAGED_CODE(); KdPrint( ( "CreateProcessNotifyRoutineEx called with Process = 0x%08X, ProcessId = 0x%08X\n", Process, ProcessId ) ); }
Как и в случае с PsLoadImageNotifyRoutine операционная система содержит список (массив) функций уведомления в глобальной переменной PspCreateProcessNotifyRoutine (Windows XP/2003). Вызов каждой зарегистрированной функции происходит из системных функций PspCreateThread и PspExitProcess.
VOID PspExitProcess( IN BOOLEAN LastThreadExit, IN PEPROCESS Process ) { … if (LastThreadExit) { … if (PspCreateProcessNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_PROCESS_NOTIFY_ROUTINE Rtn; for (i = 0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Process->InheritedFromUniqueProcessId, Process->UniqueProcessId, FALSE); ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i], CallBack); } } } } … }
PsSetCreateThreadNotifyRoutine
PsSetCreateThreadNotifyRoutine регистрирует функцию уведомления, которая вызывается в момент создания и уничтожения потока.
NTSTATUS PsSetCreateThreadNotifyRoutine( IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine );
Функция уведомления должна быть определена следующим образом:
VOID (*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );
Для удаления обработчика необходимо использовать функцию PsRemoveCreateThreadNotifyRoutine.
В момент создания нового потока функция уведомления вызывается в контексте потока, который инициировал его создание. В момент уничтожения потока функция уведомления вызывается в контексте завершаемого потока.
Пример регистрации функции уведомления:
NTSTATUS SetCreateThreadNotifyRoutine(IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine) { PAGED_CODE(); if ( !NotifyRoutine ) return STATUS_INVALID_PARAMETER; return PsSetCreateThreadNotifyRoutine( NotifyRoutine ); } VOID CreateThreadNotifyRoutine(IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create) { PAGED_CODE(); KdPrint( ( "CreateThreadNotifyRoutine called with ProcessId = 0x%08X, ThreadId = 0x%08X, Create = %d\n", ProcessId, ThreadId, Create ) ); }
Как и во всех вышеперечисленных функциях, операционная система хранит список (массив) функций уведомления в глобальной переменной PspCreateThreadNotifyRoutine (Windows XP/2003). Вызов каждой зарегистрированной функции происходит из функций PspCreateThread и PspExitThread.
NTSTATUS PspCreateThread( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, IN PEPROCESS ProcessPointer, OUT PCLIENT_ID ClientId OPTIONAL, IN PCONTEXT ThreadContext OPTIONAL, IN PINITIAL_TEB InitialTeb OPTIONAL, IN BOOLEAN CreateSuspended, IN PKSTART_ROUTINE StartRoutine OPTIONAL, IN PVOID StartContext ) { … if (PspCreateThreadNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_THREAD_NOTIFY_ROUTINE Rtn; for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Thread->Cid.UniqueProcess, Thread->Cid.UniqueThread, TRUE); ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i], CallBack); } } } … }
На этом первая часть цикла о функциях уведомления и функциях обратного вызова закончена. Исходные тексты к статье можно скачать здесь. После каждой новой главы я буду их обновлять.