EnglishFrenchGermanItalianPortugueseSpanish

Функции уведомления и функции обратного вызова в Windows (ч.1, Ps*)

В операционной системе 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);
            }
        }
    }
 
…
 
}

На этом первая часть цикла о функциях уведомления и функциях обратного вызова закончена. Исходные тексты к статье можно скачать здесь. После каждой новой главы я буду их обновлять.

Читайте также:

Share

Leave a Reply

  

  

  

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">