Процессы Windows: устройство, жизненный цикл, память и телеметрия для исследователя

Процесс в Windows — это не просто запущенный исполняемый файл, а сложный объект ядра с собственным виртуальным адресным пространством, таблицей дескрипторов, счётчиком ссылок и набором пользовательских структур, через которые runtime взаимодействует с подсистемами ОС. Для исследователя защиты понимание этих механизмов критично: любая аномалия в жизненном цикле процесса, нестандартное распределение памяти или подозрительная модификация PEB — это потенциальный индикатор компрометации.

Процесс как объект ядра​


Каждый процесс в Windows представлен объектом, которым управляет Object Manager. Ядро хранит внутреннюю структуру EPROCESS, содержащую метаданные процесса: идентификатор, указатель на адресное пространство, токен безопасности, список потоков, квоты и информацию о родительском процессе.

Object Manager отслеживает количество ссылок на объект. При создании процесса счётчик ссылок устанавливается в единицу. Объект существует до тех пор, пока счётчик не упадёт до нуля — после этого память освобождается. Ссылки на объект процесса могут быть двух типов:

  • Через дескриптор (handle). Каждый вызов функции, открывающей дескриптор (например, OpenProcess), увеличивает и счётчик ссылок, и счётчик открытых дескрипторов на единицу. Закрытие дескриптора через ZwClose уменьшает оба счётчика.
  • Через указатель в режиме ядра. Подпрограммы вроде ObReferenceObjectByHandle получают дескриптор и возвращают указатель на объект, увеличивая счётчик ссылок. После завершения работы с указателем драйвер обязан вызвать ObDereferenceObject.

Если объект освобождён преждевременно из-за ошибки в подсчёте ссылок, система может упасть. Если счётчик ошибочно завышен, объект никогда не будет освобождён — это утечка ресурсов.

Временные и постоянные объекты​


По умолчанию объекты процессов являются временными: они существуют, пока на них есть ссылки. Временный объект доступен по имени только при ненулевом количестве открытых дескрипторов. Когда дескрипторы закрываются, имя удаляется из пространства имён Object Manager, хотя сам объект может оставаться в памяти, пока счётчик ссылок больше нуля.

Постоянные объекты создаются с атрибутом OBJ_PERMANENT в структуре OBJECT_ATTRIBUTES. Object Manager сам удерживает ссылку на такой объект, поэтому его счётчик никогда не падает до нуля. Для процессов это нехарактерно, но механизм важен для понимания общей модели объектов Windows. Чтобы сделать постоянный объект временным, используется ZwMakeTemporaryObject — после этого объект будет автоматически удалён, когда на него не останется ссылок.

Этапы создания процесса​


Создание процесса проходит через несколько стадий, каждая из которых оставляет наблюдаемые артефакты:

  1. Вызов CreateProcess. Пользовательский API формирует запрос и передаёт его в подсистему Win32 (csrss.exe), которая создаёт структуру процесса в ядре.
  2. Создание объекта процесса. Ядро выделяет EPROCESS, инициализирует адресное пространство, создаёт первичный токен безопасности и устанавливает начальный счётчик ссылок.
  3. Загрузка образа. Менеджер памяти отображает исполняемый файл в адресное пространство. На этом этапе формируется секция image memory.
  4. Создание PEB и TEB. Ядро выделяет и инициализирует Process Environment Block и Thread Environment Block для первичного потока.
  5. Загрузка DLL. Загрузчик обрабатывает таблицу импорта, отображает необходимые библиотеки и заполняет списки модулей в PEB.
  6. Передача управления. Точка входа исполняемого файла получает управление, процесс переходит в состояние выполнения.

Каждый из этих этапов фиксируется подсистемами телеметрии: ETW-провайдеры, callback-механизмы ядра (PsSetCreateProcessNotifyRoutine) и аудит безопасности Windows генерируют события, которые доступны для мониторинга.

Виртуальное адресное пространство и типы памяти​


Каждый процесс обладает изолированным виртуальным адресным пространством. Windows разделяет память процесса на три основных типа:

Приватная память (Private Memory)​


Предназначена исключительно для одного процесса и не может быть разделена с другими. Используется для хранения данных, специфичных для процесса: куча (heap), стеки потоков, выделения через VirtualAlloc. Приватная память не отображается на файл и существует только в pagefile или физической RAM.

Для исследователя приватная память — ключевой индикатор: аномально большие выделения, регионы с правами RWX или приватная память в регионах, где ожидается image memory, указывают на инъекцию кода или распаковку payload.

Отображённая память (Mapped Memory)​


Может быть разделена между двумя или несколькими процессами. Используется для межпроцессного обмена данными, общих библиотек и memory-mapped файлов. Отображённая память видима другим процессам, но защищена от несанкционированных изменений атрибутами доступа.

Память образа (Image Memory)​


Содержит код и данные исполняемого файла: секции .text, .data, .rdata, ресурсы. Память образа связана с файлами DLL, загруженными в адресное пространство процесса. Каждая загруженная библиотека занимает регион image memory с соответствующими правами доступа (обычно RX для кода, RW для данных).

Структуры PEB и TEB​


Process Environment Block (PEB) — пользовательская структура, содержащая информацию о процессе, доступную без перехода в режим ядра. TEB (Thread Environment Block) — аналогичная структура для каждого потока.

PEB​


Ключевые поля PEB, значимые для исследователя:

C:
typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    PPEB_LDR_DATA Ldr;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    // ... остальные поля
    ULONG SessionId;
} PEB, *PPEB;

  • BeingDebugged — флаг, устанавливаемый при подключении отладчика. Антиотладочные техники часто проверяют это поле напрямую.
  • Ldr — указатель на PEB_LDR_DATA, содержащую списки загруженных модулей.
  • ProcessParameters — указатель на RTL_USER_PROCESS_PARAMETERS с путём к образу и командной строкой.

PEB_LDR_DATA и списки модулей​


C:
typedef struct _PEB_LDR_DATA {
    BYTE Reserved1[8];
    PVOID Reserved2[3];
    LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

InMemoryOrderModuleList — двусвязный список загруженных модулей в порядке их отображения в память. Этот список используется загрузчиком и доступен пользовательскому коду. Техники сокрытия DLL (PEB unlinking, module stomping) часто манипулируют именно этим списком, удаляя из него записи.

RTL_USER_PROCESS_PARAMETERS​


C:
typedef struct _RTL_USER_PROCESS_PARAMETERS {
    BYTE Reserved1[116];
    PVOID Reserved2[10];
    UNICODE_STRING ImagePathName;
    UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

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

TEB​


C:
typedef struct _TEB {
    PVOID Reserved1[12];
    PPEB ProcessEnvironmentBlock;
    // ... остальные поля
    PVOID TlsSlots[64];
    PVOID TlsExpansionSlots;
} TEB, *PTEB;

TEБ содержит указатель на PEB процесса и слоты Thread Local Storage. Доступ к TEB осуществляется через сегментный регистр: на x86-64 используется GS, на 32-битном x86 — FS. Указатель на PEB расположен по смещению 0x60 от начала TEB в 64-битном режиме (GS:[0x60]), что соответствует 12 указателям Reserved1 по 8 байт каждый. В 32-битном режиме это смещение составляет 0x30 (FS:[0x30]), поскольку указатели занимают 4 байта. Это делает TEB быстрой мишенью для чтения без вызова API.

Завершение процесса​


Процесс завершается через ExitProcess (нормальное завершение изнутри) или TerminateProcess (принудительное завершение извне). При завершении:

  1. Ядро уведомляет все зарегистрированные callback-функции (PsSetCreateProcessNotifyRoutine с флагом создания, установленным в FALSE).
  2. Закрываются все дескрипторы, принадлежащие процессу.
  3. Освобождаются объекты потоков.
  4. Адресное пространство уничтожается.
  5. Счётчик ссылок на объект процесса уменьшается. Когда он достигает нуля, EPROCESS освобождается.

Объект процесса может существовать некоторое время после завершения, если на него остаются открытые дескрипторы из других процессов. Это нормальное поведение, но аномально долгое удержание дескриптора на завершённый процесс может быть признаком мониторинга или инъекции.

Точки наблюдения для исследователя​


Для детекта аномалий в жизненном цикле процессов доступны следующие механизмы:

МеханизмУровеньЧто фиксирует
PsSetCreateProcessNotifyRoutineЯдроСоздание и завершение процессов
PsSetLoadImageNotifyRoutineЯдроЗагрузка образов (DLL, драйверы)
ETW-провайдер Microsoft-Windows-Kernel-ProcessПользовательскийСоздание, завершение, изменение атрибутов
Object Manager callbacks (ObRegisterCallbacks)ЯдроОткрытие дескрипторов процессов и потоков
Audit Policy (Security Log)ПользовательскийСобытия 4688, 4689

OpenProcess и OpenThread​


Функции OpenProcess и OpenThread открывают дескриптор на существующий объект по его идентификатору. Запрошенный набор прав доступа (PROCESS_VM_READ, PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION и др.) определяет, какие операции доступны через полученный дескриптор.

Для защитных решений мониторинг вызовов OpenProcess с правами, достаточными для инъекции (PROCESS_VM_WRITE | PROCESS_CREATE_THREAD), является стандартной техникой обнаружения process hollowing и classic injection.

Типичные аномалии и их интерпретация​


  • Процесс без родительского процесса или с неожиданным родителем — возможен запуск через WMI, scheduled task или эксплойт.
  • Несоответствие ImagePathName в PEB и реального пути образа на диске — подмена параметров, process doppelgänging.
  • Модуль в памяти, отсутствующий в InMemoryOrderModuleList — DLL была загружена с последующим удалением из списка (PEB unlinking).
  • Приватная память с правами RWX — характерно для распакованного shellcode или JIT-инъекции.
  • BeingDebugged = 0 при подключённом отладчике — антиотладочный патч или использование нестандартного отладчика.
  • Аномально высокий счётчик дескрипторов на завершённый процесс — удержание объекта для чтения памяти или манипуляции.

Практический чек-лист при анализе процесса​


  1. Получить PID и дескриптор через OpenProcess с PROCESS_QUERY_INFORMATION | PROCESS_VM_READ.
  2. Прочитать PEB целевого процесса, извлечь ImagePathName и CommandLine.
  3. Пройти по InMemoryOrderModuleList, сверить список модулей с ожидаемым для данного образа.
  4. Перечислить регионы памяти через VirtualQueryEx, классифицировать по типу (private/mapped/image) и правам доступа.
  5. Проверить наличие регионов RWX или private memory в диапазонах, где ожидается image.
  6. Сверить данные PEB с информацией из EPROCESS (доступно только из режима ядра или через драйвер).
  7. Проверить события ETW и Security Log на соответствие фактического поведения процесса заявленному.

Источники​


 
Назад
Верх Низ