Рождение процесса. Часть 2

MonsterV2

Премиум
Premium
Регистрация
09.03.2025
Сообщения
59
Реакции
71
Гарант сделки
1
Депозит
13.2864 Ł
Автор: Achilles
Переведено и дополнено: MonsterV2

Перед прочтением рекомендуется ознакомиться с первой частью: Ссылка

Этапы CreateProcess*


Создание процесса Windows (специфического для подсистемы) происходит в несколько этапов, выполняемых в разных разделах операционной системы: клиентская библиотека Kernel32.dll/Kernelbase.dll, ядро Windows (NtOsKrnl.exe), процесс подсистемы Windows (Сsrss.exe) и PE загрузчик пользовательского режима Ntdll.dll.
Выполяемые в каждом разделе операции описаны на диаграме снизу:

2.png


Основные этапы создания процесса

Этап 1: Конвертация и Валидация пользовательских параметров

CreateProcessInternalW выполняем следующие шаги:
  1. Приоритет для нового процесса указывается в отдельных битах параметра CreationFlags функций CreateProcess*:
C++:
#define IDLE_PRIORITY_CLASS               0x00000040
#define BELOW_NORMAL_PRIORITY_CLASS       0x00004000
#define NORMAL_PRIORITY_CLASS             0x00000020
#define ABOVE_NORMAL_PRIORITY_CLASS       0x00008000
#define HIGH_PRIORITY_CLASS               0x00000080
#define REALTIME_PRIORITY_CLASS           0x00000100

Если ни один из этих битов не указан, то используется NORMAL_PRIORITY_CLASS.

Создание процесса не завершится ошибкой, если вызывающий определяет приоритет REALTIME_PRIORITY_CLASS без привилегии SE_INC_BASE_PRIORITY_NAME — если проверка kernelbase.BasepIsRealtimeAllowed вернёт false, то процессу просто будет присвоен приоритет HIGH_PRIORITY_CLASS
  1. Если определён debug флаг, то Kernel32 инициализирует подключение через ntdll.DbgUiConnectToDbg и получает дескриптор объекта отладки из текущего TEB через ntdll.DbgUiGetThreadDebugObject.
  2. Поддерживает список атрибутов, указанных пользователем. Список атрибутов, переданный в CreateProcess*, позволяет передавать обратно вызывающей стороне информацию, выходящую за рамки простого кода состояния, например, адрес TEB начального потока и т. д.
  3. Если процесс является частью объекта Job, но указан флан CREATE_SEPARATE_WOW_VDM, который запрашивают отдельную виртуальную машину DOS (VDM), то этот флаг игнорируется.
  4. CreateProcessInternalW проверяет, следует ли создавать процесс как современный (с атрибутами: PROC_THREAD_ATTRIBUTE_PACKAGE_FULL_NAME, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS). Если это так, выполняется вызов внутренней функции kernel32.BasepAppXExtension для сбора дополнительной контекстной информации о параметрах современного приложения, описанных внутренней структурой APPX_PROCESS_CONTEXT. Эта структура содержит информацию, такую как: имя пакета (внутреннее название — package moniker), связанные с приложением возможности, текущая папка процесса и должно ли приложение иметь полное доверие. Возможность создания современных приложений с полным доверием не является публично доступной и зарезервирована для приложений, которые имеют современный вид и поведение, но выполняют операции на уровне системы. Каноническим примером является приложение «Параметры» в Windows 10–11 (SystemSettings.exe)
  5. Если процесс должен быть создан как современный, возможности безопасности (PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES) записываются для первоначального создания токена внутренней функцией kernelbase.BasepCreateLowBox. Термин LowBox относится к песочнице (AppContainer), в которой должен выполняться процесс. Обратите внимание, что хотя создание современных процессов через CreateProcess* не поддерживается (вместо этого следует использовать описанный в первой части интерфейс COM IApplicationActivationManager), Windows SDK и MSDN документируют возможность создавать устаревшие десктоп приложения AppContainer путём передачи этого атрибута.
  6. Если создаётся современный процесс, то устанавливается флаг, указывающий ядру пропустить встроенное обнаружение манифеста. В современном процессе он не нужен, так как уже встроен.
  7. Если указан флаг DEBUG_PROCESS, то Debugger значение в разделе реестра Image File Execution Options для исполняемого файла помечается для дальнейшего скипа. В противном случае отладчик никогда не сможет создать свой отлаживаемый процесс, потому что операция создания войдёт в бесконечный цикл.
  8. Если в структуре STARTUPINFO(EX) не указан объект рабочего стола lpDesktop, процесс связывается с текущим рабочим столом вызывающего объекта.
Функция виртуального рабочего стола Windows 10+ не создаёт других объектов рабочего стола (в смысле объектов ядра). Рабочий стол по-прежнему один, но окна отображаются и скрываются по мере необходимости. Это контрастирует с инструментом SysInternals desktops.exe, который на самом деле создаёт до четырёх объектов рабочего стола. Разницу можно почувствовать при попытке переместить окно с одного рабочего стола на другой. В случае desktops.exe это невозможно сделать, поскольку такая операция не поддерживается в Windows. С другой стороны, виртуальный рабочий стол Windows 10+ позволяет это, поскольку никакого реального «перемещения» не происходит.

  1. Аргументы приложения и командной строки, переданные в API, анализируются и при необходимости преобразуются во внутреннее имя NT. Например, С:\temp\a.exe превращается во что-то вроде \Device\HardDiskVolume1\temp\a.exe.
  2. Бо́льшая часть обработанной информации преобразуется в одну большую структуру RTL_USER_PROCESS_PARAMETERS функцией kernelbase.BasepCreateProcessParameters.
После завершения всех предыдущих шагов происходит вызов NtCreateUserProcess, который передаёт управление ядру.

C++:
/**
 * Creates a new process and primary thread.
 *
 * @param ProcessHandle A pointer to a handle that receives the process object handle.
 * @param ThreadHandle A pointer to a handle that receives the thread object handle.
 * @param ProcessDesiredAccess The access rights desired for the process object.
 * @param ThreadDesiredAccess The access rights desired for the thread object.
 * @param ProcessObjectAttributes Optional. A pointer to an OBJECT_ATTRIBUTES structure that specifies the attributes of the new process.
 * @param ThreadObjectAttributes Optional. A pointer to an OBJECT_ATTRIBUTES structure that specifies the attributes of the new thread.
 * @param ProcessFlags Flags that control the creation of the process. These flags are defined as PROCESS_CREATE_FLAGS_*.
 * @param ThreadFlags Flags that control the creation of the thread. These flags are defined as THREAD_CREATE_FLAGS_*.
 * @param ProcessParameters Optional. A pointer to a RTL_USER_PROCESS_PARAMETERS structure that specifies the parameters for the new process.
 * @param CreateInfo A pointer to a PS_CREATE_INFO structure that specifies additional information for the process creation.
 * @param AttributeList Optional. A pointer to a list of attributes for the process and thread.
 * @return NTSTATUS Successful or errant status.
 */
NTSYSCALLAPI
NTSTATUS
NTAPI
NtCreateUserProcess(
    _Out_ PHANDLE ProcessHandle,
    _Out_ PHANDLE ThreadHandle,
    _In_ ACCESS_MASK ProcessDesiredAccess,
    _In_ ACCESS_MASK ThreadDesiredAccess,
    _In_opt_ PCOBJECT_ATTRIBUTES ProcessObjectAttributes,
    _In_opt_ PCOBJECT_ATTRIBUTES ThreadObjectAttributes,
    _In_ ULONG ProcessFlags, // PROCESS_CREATE_FLAGS_*
    _In_ ULONG ThreadFlags, // THREAD_CREATE_FLAGS_*
    _In_opt_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
    _Inout_ PPS_CREATE_INFO CreateInfo,
    _In_opt_ PPS_ATTRIBUTE_LIST AttributeList
  );
// https://ntdoc.m417z.com/ntcreateuserprocess

Этап 2: Открытие исполняемого файла


Итак, мы в ядре:
  1. NtCreateUserProcess сначала проверяет аргументы и инициализирует внутреннюю структуру для хранения всей информации о создании для проверки и обеспечения безопасности.
  2. Если процесс создаётся как защищённый, NtCreateUserProcess проверит политику подписи через вызов nt.SeQuerySigningPolicy.
  3. Если создаваемый процесс является современным (UWP), проводится проверка лицензии, чтобы убедиться, что он лицензирован и разрешён к запуску.
  4. Если процесс является Trustlet'ом (см. 1 часть), объект секции должен быть создан со специальным флагом, который позволяет безопасному ядру использовать его.
  5. NtCreateUserProcess просматривает реестр в разделе HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options, чтобы узнать, существует ли там подраздел с именем файла и расширением исполняемого образа. Если он есть, PspAllocateProcess ищет значение с именем Debugger для этого ключа.
  6. NtCreateUserProcess попытается открыть переданное в аргументах исполняемое изображение и создать для него объект секции с атрибутом SEC_IMAGE через вызов nt.MmCreateSpecialImageSection (но ещё не мапит её в память). Однако, факт успешного создания объекта секции не означает, что файл является допустимым образом Windows. Это может быть DLL или исполняемый файл POSIX. Если файл является исполняемым файлом POSIX, вызов завершается ошибкой, поскольку POSIX больше не поддерживается. DLL файлы также считаются недопустимым изображением.
  7. Если указанный файл не является Windows EXE, kernelbase.CreateProcessInternalW попытается найти вспомогательный файл для его запуска.
7.1. Если это x86 32-разрядная Windows, а образ является приложением MS-DOS с расширением .exe, .com или .pif, в подсистему Windows отправляется сообщение для проверки того, был ли уже создан процесс эмуляции MS-DOS (Ntvdm.exe, указанный в значении реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WOW\cmdline) для этого сеанса. Если процесс эмуляции был создан, он используется для запуска приложения MS-DOS. (Подсистема Windows отправляет сообщение процессу виртуальной машины DOS [VDM] для запуска нового образа.) Затем kernelbase.CreateProcessInternalW возвращается. Если процесс эмуляции не был создан, образ для запуска изменяется на Ntvdm.exe, и kernelbase.CreateProcessInternalW перезапускается с первого этапа.
7.2. Если файл для запуска имеет расширение .bat или .cmd, образ для запуска становится Cmd.exe, командной строкой Windows, и kernelbase.CreateProcessInternalW перезапускается с первого этапа. (Имя пакетного файла передаётся в качестве второго параметра Cmd.exe после переключателя /c.)
7.3. Для системы x86 Windows, если образ является исполняемым файлом Win16 (Windows 3.1), kernelbase.CreateProcessInternalW должен решить, нужно ли создавать новый процесс VDM для его запуска или следует использовать общий процесс VDM по умолчанию для всего сеанса (который, возможно, ещё не создан). Флаги CreateProcess* CREATE_SEPARATE_WOW_VDM и CREATE_SHARED_WOW_VDM управляют этим решением. Если эти флаги не указаны, значение реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WOW\DefaultSeparateVDM определяет поведение по умолчанию. Если приложение должно быть запущено в отдельном VDM, образ для запуска изменяется на Ntvdm.exe, за которым следуют некоторые параметры конфигурации и 16-разрядное имя процесса, и kernelbase.CreateProcessInternalW перезапускается на этапе 1. В противном случае подсистема Windows отправляет сообщение, чтобы проверить, существует ли общий процесс VDM и может ли он быть использован. Если процесс VDM запущен на другом рабочем столе или не запущен в том же контексте безопасности, что и вызывающая сторона, его нельзя использовать, и необходимо создать новый процесс VDM. Если общий процесс VDM может быть использован, подсистема Windows отправляет ему сообщение для запуска нового образа и kernelbase.CreateProcessInternalW завершится. Если процесс VDM ещё не был создан (или если он существует, но не может быть использован), образ для запуска изменяется на образ поддержки VDM и kernelbase.CreateProcessInternalW перезапускается на этапе 1.
7.4. Раньше в Windows были подсистемы OS/2 и Posix, где была реализована поддержка соответствующих исполняемых изображений, но сейчас они полностью выпилены, поэтому попытка создать из них процесс завершится ошибкой.

1.png


Прилагаю ниже таблицу из книги Windows Internals 5th Edition. Обратите также внимание, что Windows окончательно выпилила подсистемы Posix и OS/2 где-то в Windows 8 и Windows Server 2012, так что на современных Windows у вас не запустятся OS/2 или Posix файлы.
Если исполняемое изображение...​
Возвращаемое состоение из перечисления PS_CREATE_STATE
Изображение, которое будет запущено...​
...И произойдёт это​
POSIX (IMAGE_SUBSYSTEM_POSIX_CUI)​
PsCreateSuccess​
Posix.exe​
CreateProcessInternalW вернётся на первый этап​
OS/2 (IMAGE_SUBSYSTEM_OS2_CUI)​
PsCreateSuccess​
Os2.exe​
CreateProcessInternalW вернётся на первый этап​
MS-DOS (.exe, .com, .pif)​
PsCreateFailOnSectionCreate​
NtVdm.exe​
CreateProcessInternalW вернётся на первый этап​
Приложение Win16​
PsCreateFailOnSectionCreate​
NtVdm.exe​
CreateProcessInternalW вернётся на первый этап​
Приложение Win64, а система 32-битная (или PPC, MIPS или Alpha Binary)​
PsCreateFailMachineMismatch​
N/A​
CreateProcessInternalW завершится с ошибкой​
Невалидное или повреждённое​
PsCreateFailExeFormat​
N/A​
CreateProcessInternalW завершится с ошибкой​
Не может быть открыто​
PsCreateFailOnFileOpen​
N/A​
CreateProcessInternalW завершится с ошибкой​
Имеет в реестре в значении Debugger другое имя​
PsCreateFailExeName​
Указанное в значении Debugger изображение​
CreateProcessInternalW вернётся на первый этап​
Пакетный (.bat) или командный (.cmd) файл​
PsCreateFailOnSectionCreate​
Cmd.exe​
CreateProcessInternalW вернётся на первый этап​

Этап 3: Создание executive объекта процесса


На этом этапе NtCreateUserProcess открыл валидный исполняемый файл Windows и создал объект секции для его отображения в адресное пространство нового процесса. Затем он создаёт объект executive процесса Windows для запуска образа, вызывая внутреннюю системную функцию nt.PspAllocateProcess. Создание объекта executive процесса (которое выполняется создающим потоком) включает следующие подэтапы:

3A. Заполнение EPROCESS
3Б. Создание начального адресного пространства процесса
3В. Инициализация структуры KPROCESS
3Г. Завершение настройки адресного пространства процесса
3Д. Настройка PEB
3Е. Завершение настройки объекта executive процесса
3.png


Единственный процесс без родителя — System Idle Process. После его спавна родительский процесс всегда требуется при создании процессов для обеспечения контекста безопасности.

Этап 3А: Заполнение EPROCESS


Этот подэтап включает в себя следующие шаги:

  1. Наследуется привязка (affinity) родительского процесса, если оно не было явно задано во время создания процесса через список атрибутов.
  2. Выбирается идеальный узел NUMA, который был указан в списке атрибутов, если таковой имеется.
  3. Наследуется приоритет ввода-вывода (I/O) и страниц от родительского процесса. Если родительского процесса нет, используются приоритет страниц по умолчанию (5) и приоритет ввода-вывода IoPriorityNormal.
  4. Устанавливается exit статус процесса на STATUS_PENDING.
  5. Выбирается режим обработки hardware ошибок, выбранный списком атрибутов. В противном случае, если атрибут не указан, используется режим обработки родительского процесса. Если родительского процесса нет, используется режим обработки по умолчанию (0), который заключается в отображении всех ошибок в диалоговом окне.
  6. Сохраняется идентификатор родительского процесса в поле InheritedFromUniqueProcessId в новом объекте процесса.
  7. Проверяется ключ Image File Execution Options (IFEO-key), чтобы проверить, следует ли сопоставлять процесс с большими страницами (значение UseLargePages в IFEO-key); если процесс будет запущен под Wow64, то в этом случае большие страницы использоваться не будут. Также запрашивается ключ, чтобы проверить, указана ли NTDLL как DLL, которая должна быть сопоставлена с большими страницами в этом процессе.
  8. Проверяется ключ параметров производительности в IFEO (PerfOptions, если он существует), который может состоять из любого количества следующих возможных значений: IoPriority, PagePriority, CpuPriorityClass и WorkingSetLimitInKB.
  9. Если процесс будет запущен под Wow64, выделяется вспомогательную структура Wow64 (EWOW64PROCESS) и записывается в поле WoW64Process структуры EPROCESS.
  10. Если процесс должен быть создан внутри AppContainer (в большинстве случаев современные приложения), проверяется, что токен был создан с помощью LowBox.
  11. Идёт попытка получить все привилегии, необходимые для создания процесса. Выбор класса приоритета процесса в реальном времени, назначение токена новому процессу, сопоставление процесса с большими страницами и создание процесса в новом сеансе — всё это операции, требующие соответствующей привилегии.
  12. Создаётся primary токен доступа процесса (дубликат primary токена его родителя). Новые процессы наследуют профиль безопасности своих родителей. Если функция kernel32.CreateProcessAsUserA/W используется для указания другого токена доступа для нового процесса, то токен затем соответствующим образом изменяется. Это изменение может произойти только в том случае, если уровень целостности родительского токена доминирует над уровнем целостности токена доступа и если токен доступа является дочерним или родственным токену родительского токена. Обратите внимание, что привилегия SeAssignPrimaryToken у родителя позволит обойти эти проверки.
  13. Теперь проверяется идентификатор сеанса нового токена процесса, чтобы определить, является ли он кросс-сеансным. Если это так, родительский процесс временно подключается к целевому сеансу для правильной обработки квот и создания адресного пространства.
  14. Устанавливается блок квот нового процесса на адрес блока квот родительского процесса с увеличением его счётчика ссылок. Если процесс был создан с помощью kernel32.CreateProcessAsUserA/W, этот шаг не будет выполнен. Вместо этого создаётся квота по умолчанию или выбирается квота, соответствующая профилю пользователя.
  15. Минимальный и максимальный размеры рабочего набора процесса устанавливаются на значения nt.PspMinimumWorkingSet и nt.PspMaximumWorkingSet соответственно. Эти значения могут быть переопределены, если параметры производительности были указаны в ключе Image File Execution Options у значения PerfOptions , в этом случае максимальный рабочий набор берётся оттуда. Обратите внимание, что пределы рабочего набора по умолчанию являются мягкими ограничениями и по сути являются подсказками, в то время как максимальный рабочий набор PerfOptions является жёстким ограничением. То есть, рабочему набору не будет разрешено расти сверх этого числа.
  16. Инициализиeтся адресное пространство процесса (см. этап 3Б). Затем идёт отсоединение от целевого сеанса, если он был другим.
  17. Групповое affinity для процесса теперь выбирается, если наследование группового affinity не использовалось. Групповое affinity по умолчанию будет либо наследоваться от родителя, если распространение узла NUMA было установлено ранее (будет использоваться группа, владеющая узлом NUMA), либо назначаться циклически. Если система находится в режиме принудительной осведомленности о группах и группа 0 была выбрана алгоритмом выбора, вместо нее выбирается группа 1, пока она существует.
  18. Инициализируется часть KPROCESS объекта процесса (см. этап 3В).
  19. Теперь установлен токен для процесса.
  20. Класс приоритета процесса устанавливается на NORMAL_PRIORITY_CLASS, если родитель не использовал класс приоритета процесса IDLE_PRIORITY_CLASS или BELOW_NORMAL_PRIORITY_CLASS — в этом случае наследуется приоритет родителя.
  21. Инициализируется таблица дескрипторов процесса. Если для родительского процесса установлен флаг наследования дескрипторов, любые наследуемые дескрипторы копируются из таблицы дескрипторов родителя в новый процесс. Атрибут процесса также могут использоваться для указания только подмножества дескрипторов, что полезно, когда вы используете kernel32.CreateProcessAsUserA/W для ограничения того, какие объекты должны наследоваться дочерним процессом.
  22. Если параметры производительности были указаны через ключ PerfOptions, они теперь применяются. Ключ PerfOptions включает переопределения для рабочего предела набора, приоритета ввода-вывода (I/O), приоритета страниц и класса приоритета ЦП процесса.
  23. Вычисляются и устанавливаются окончательный класс приоритета процесса и квант по умолчанию для его потоков.
  24. Различные параметры митигации, представленные в ключе IFEO (как единое 64-битное значение с именем Mitigation), считываются и устанавливаются. Если процесс находится в AppContainer, добаляется флаг митигации TreatAsAppContainer.
  25. Теперь применяются все остальные флаги митигации.

Этап 3Б: Создание начального адресного пространства процесса


Начальное адресное пространство процесса состоит из следующих страниц:
• Каталог страниц (Page directory).
Их может быть больше одного для систем с таблицами страниц более двух уровней, например, систем x86 в режиме PAE или 64-разрядны систем
• Страница гиперпространства
• Страница битовой карты VAD
• Список рабочих наборов
Для создания этих страниц выполняются следующие шаги:
  1. Записи таблицы страниц создаются в соответствующих таблицах страниц для сопоставления начальных страниц.
  2. Количество страниц вычитается из переменной ядра nt.MmTotalCommittedPages и добавляется к nt.MmProcessCommit.
  3. Минимальный размер рабочего набора процесса по умолчанию для всей системы nt.PsMinimumWorkingSet вычитается из nt.MmResidentAvailablePages.
  4. Создаются страницы таблицы страниц для глобального системного пространства (то есть, кроме страниц, специфичных для процесса, которые мы только что описали, и за исключением памяти, специфичной для сеанса).

Этап 3В: Инициализация структуры KPROCESS


Следующий этап nt.PspAllocateProcess — инициализация структуры KPROCESS (поле Pcb у EPROCESS). Эту работу выполняет функция nt.KeInitializeProcess, которая делает следующее:
  1. Инициализируется двусвязный список, который соединяет все потоки, входящие в процесс (изначально пустой).
  2. Начальное значение (или значение сброса) кванта процесса по умолчанию захардкожено на 6, пока оно не будет инициализировано позже с помощью nt.PspComputeQuantumAndPriority.
  3. Базовый приоритет процесса устанавливается на основе того, что было вычислено на этапе 3А.
  4. Устанавливается affinity процессора по умолчанию для потоков в процессе, а также affinity группы. Affinity группы была рассчитана на этапе 3A или унаследована от родителя.
  5. Состояние подкачки процессов устанавливается как резидентное.
  6. Seed потока основан на идеальном процессоре, который ядро выбрало для этого процесса (который основан на идеальном процессоре ранее созданного процесса, эффективно рандомизируя его по принципу циклического перебора). Создание нового процесса обновит seed в nt.KeNodeBlock (начальный блок узлов NUMA), так что следующий новый процесс получит другой seed идеального процессора.
  7. Если процесс является безопасным процессом (Windows 10/Server 2016 и выше), то создаётся его безопасный идентификатор путём вызова nt.HvlCreateSecureProcess.

Этап 3Г: Завершение настройки адресного пространства процесса


Процедура nt.MmInitializeProcessAddressSpace выполняет бо́льшую часть работы по настройке адресного пространства. Она также поддерживает клонирование адресного пространства из другого процесса. Эта возможность была полезна когда-то для реализации системного вызова POSIX fork. Она также может быть использована в будущем для поддержки других fork в стиле Unix (именно так fork реализован для WSL в Windows 10 Redstone 1 и выше). Следующие шаги не описывают функциональность клонирования адресного пространства, а скорее фокусируются на обычной инициализации адресного пространства процесса.
  1. Диспетчер виртуальной памяти устанавливает значение последнего времени обрезки процесса на текущее время. Диспетчер рабочих наборов (который работает в контексте системного потока диспетчера балансировочных наборов) использует это значение для определения того, когда инициировать обрезку рабочих наборов.
  2. Диспетчер памяти инициализирует список рабочих наборов процесса. Теперь можно обрабатывать ошибки страниц.
  3. Секция, созданная при открытии файла образа, теперь отображается в адресное пространство нового процесса, а базовый адрес раздела процесса устанавливается на базовый адрес образа.
  4. Создается и инициализируется блок среды процесса (PEB) (см. этап 3Д).
  5. Ntdll.dll мапится в процесс. Если это процесс Wow64, то также мапится 32-битная версия Ntdll.dll.
  6. Новый сеанс, если запрошено, теперь создаётся для процесса. Этот специальный шаг в основном реализован для выгоды менеджера сеансов (Smss) при инициализации нового сеанса.
  7. Стандартные дескрипторы дублируются, а новые значения записываются в структуру параметров процесса.
  8. Теперь обрабатываются резервирования памяти, перечисленные в списке атрибутов. Кроме того, два флага позволяют выполнять массовое резервирование первых 1 или 16 МБ адресного пространства. Эти флаги используются внутренне для отображения, например, векторов реального режима и кода ПЗУ (который должен находиться в нижних диапазонах виртуального адресного пространства, где обычно могут располагаться куча или другие структуры процесса).
  9. Параметры пользовательского процесса записываются в процесс, копируются и фиксируются (то есть преобразуются из абсолютной формы в относительную, так что требуется один блок памяти).
  10. Информация об affinity записывается в PEB.
  11. Набор перенаправлений API MinWin отображается в процесс, а его указатель сохраняется в PEB.
  12. Теперь определяется и сохраняется уникальный идентификатор процесса. Ядро не различает уникальные идентификаторы и дескрипторы процесса и потока. Идентификаторы процессов и потоков (дескрипторы) хранятся в глобальной таблице дескрипторов (nt.PspCidTable), которая не связана ни с одним процессом.
  13. Если процесс безопасен (то есть он выполняется в IUM), объект безопасного процесса инициализируется и связывается с объектом процесса ядра.
4.png


Снапшот памяти процесса

Этап 3Д: Настройка PEB


NtCreateUserProcess вызывает nt.MmCreatePeb, который сначала отображает общесистемные таблицы поддержки национальных языков (NLS) в адресное пространство процесса.

Затем он вызывает nt.MiCreatePebOrTeb для выделения страницы для PEB, а затем инициализирует ряд полей, большинство из которых основаны на внутренних переменных, настроенных через реестр, таких как значения nt.MmHeap*, nt.MmCriticalSectionTimeout и nt.MmMinimumStackCommitInBytes. Некоторые из этих полей могут быть переопределены настройками в связанном исполняемом образе, такими как версия Windows в заголовке PE или affinity маска в каталоге конфигурации загрузки заголовка PE.

Если в заголовке изображения установлен флаг IMAGE_FILE_UP_SYSTEM_ONLY (указывающий на то, что изображение может работать только на однопроцессорной системе), для всех потоков в этом новом процессе выбирается один ЦП (nt.MmRotatingUniprocessorNumber). Процесс выбора выполняется путем простого циклического переключения доступных процессоров. Каждый раз при запуске этого типа изображения используется следующий процессор. Таким образом, эти типы изображений равномерно распределяются по процессорам.

Этап 3Е: Завершение настройки объекта executive процесса

Перед возвратом дескриптора нового поцесса необходимо выполнить несколько последних шагов настройки, которые выполняются функцией nt.PspInsertProcess и её вспомогательными функциями:
  1. Если включен общесистемный аудит процессов (из-за локальных настроек политики или настроек групповой политики контроллера домена), создание процесса записывается в журнал событий безопасности.
  2. Если родительский процесс содержался в Job'е, Job восстанавливается из набора уровней Job родителя, а затем привязывается к сеансу вновь созданного процесса. Наконец, новый процесс добавляется к Job объекту.
  3. Новый объект процесса вставляется в конец списка активных процессов Windows nt.PsActiveProcessHead. Теперь процесс доступен через такие функции, как EnumProcesses и OpenProcess.
  4. Дебаг порт родительского процесса копируется в новый дочерний процесс, если не установлен флаг NoDebugInherit, который можно запросить при создании процесса. Если был указан дебаг порт, он прикрепляется к новому процессу.
  5. Job объекты могут указывать ограничения на то, в какой группе или группах могут выполняться потоки в процессах, являющихся частью Job'ов. Поэтому nt.PspInsertProcess должен убедиться, что групповое affinity, связанное с процессом, не нарушит групповое affinity, связанное с Job. Интересный вторичный вопрос для рассмотрения заключается в том, предоставляют ли разрешения Job для изменения разрешений на affinity процесса, поскольку объект Job с меньшими привилегиями может помешать требованиям affinity более привилегированного процесса.
  6. Наконец, nt.PspInsertProcess создаёт дескриптор для нового процесса, вызывая ObOpenObjectByPointer, а затем возвращает этот дескриптор вызывающей стороне. Важно отметить, что колбеки, зарегистрированные через nt.PsSetCreateProcessNotifyRoutine(Ex/Ex2), не вызовутся до создания первого потока в процессе (см. этап 4).

Этап 4: Создание главного потока и инициализация его стека и контекста

Этот этап пропускается для процессов, создаваемых через NtCreateProcess(Ex).

На этом этапе executive объект процесса Windows полностью настроен. Однако у него всё ещё нет потока, поэтому он пока ничего не может делать. Теперь пришло время это исправить. Обычно процедура nt.PspCreateThread отвечает за все аспекты создания потока и вызывается NtCreateThread(Ex) при создании нового потока. Однако, поскольку начальный поток создаётся внутри ядра без ввода пользовательского режима, вместо этого используются две вспомогательные процедуры, на которые опирается nt.PspCreateThread: nt.PspAllocateThread и nt.PspInsertThread.

nt.PspAllocateThread обрабатывает фактическое создание и инициализацию самого executive объекта потока (ETHREAD), в то время как nt.PspInsertThread обрабатывает создание дескриптора потока и атрибутов безопасности, а также вызов nt.KeStartThread для превращения executive объекта в планируемый поток в системе. Однако поток пока ничего не делает. Он создаётся в приостановленном состоянии и не возобновляется, пока процесс не будет полностью инициализирован (описано на этапе 5).

nt.PspAllocateThread выполняет следующие шаги:

  1. Предотвращает создание потоков планирования пользовательского режима (UMS) в процессах Wow64, а также предотвращает создание потоков процессами пользовательского режима в системном процессе.
  2. Создаётся и инициализируется executive объект потока ETHREAD.
  3. Если для системы включена оценка энергии (всегда отключена для XBOX), то аллоцируется и инициализируется структура THREAD_ENERGY_VALUES, на которую указывает объект ETHREAD.
  4. Инициализируются различные списки, используемые LPC, управлением вводом-выводом (I/O) и executive процессом.
  5. Устанавливается время создания потока и создаётся его идентификатор потока (TID).
  6. Перед выполнением потока ему требуются стек и контекст для выполнения, поэтому они настраиваются. Размер стека для начального потока берется из образа; указать другой размер невозможно. Если это процесс Wow64, дополнительно будет инициализирован контекст WOW64_CONTEXT.
  7. Блок среды потока (TEB) аллоцируется для нового потока.
  8. Начальный адрес потока пользовательского режима сохраняется в ETHREAD в поле StartAddress. Это системная функция запуска потока в Ntdll.dll (ntdll.RtlUserThreadStart). Указанный пользователем начальный адрес Windows сохраняется в ETHREAD в другом месте (поле Win32StartAddress), чтобы инструменты отладки, такие как Process Explorer, могли отображать информацию.
  9. Вызывается nt.KeInitThread для настройки структуры KTHREAD. Начальный и текущий базовые приоритеты потока устанавливаются в соответствии с базовым приоритетом процесса, а его affinity и квант устанавливаются в соответствии с приоритетом процесса. Затем nt.KeInitThread выделяет стек ядра для потока и инициализирует аппаратно-зависимый контекст для потока, включая контекст, ловушку (trap) и фреймы исключений. Контекст потока настроен так, что поток будет запущен в режиме ядра в nt.KiThreadStartup. Наконец, nt.KeInitThread устанавливает состояние потока в Initialized и возвращается в nt.PspAllocateThread.
  10. Если это поток UMS , вызывается nt.PspUmsInitThread для инициализации состояния UMS.
Примечание с MSDN: Начиная с Windows 11, планирование в пользовательском режиме (UMS) не поддерживается. Все вызовы завершаются ошибкой ERROR_NOT_SUPPORTED.
23.png


После завершения этой работы NtCreateUserProcess вызывает nt.PspInsertThread для выполнения следующих шагов:
  1. Идеальный процессор потока инициализируется, если он был указан с помощью атрибута.
  2. Инициализируется групповое affinity потока, если оно было указано с помощью атрибута.
  3. Если процесс является частью объекта Job, выполняется проверка, чтобы убедиться, что групповое affinity потока не нарушает описанных ранее ограничений Job.
  4. Выполняются проверки, чтобы убедиться, что: процесс ещё не был завершен, поток ещё не был завершён или поток даже не смог начать работу. Если выполнится любое из этих условий, создание потока завершится ошибкой.
  5. Если поток является частью безопасного процесса (IUM), то создаётся и инициализируется защищённый объект потока через вызов функции nt.PspCreateSecureThread.
  6. Часть KTHREAD объекта потока инициализируется путём вызова nt.KeStartThread. Это включает в себя наследование настроек планировщика от процесса-владельца, установку идеального узла и процессора, обновление группового affinity, установку базовых и динамических приоритетов (копированием из процесса), установку кванта потока и вставку потока в список процессов ThreadListHead поле KPROCESS (отдельный от списка с таким же именем в EPROCESS).
  7. Если процесс находится в состоянии глубокой заморозки (то есть никакие потоки не могут выполняться, включая новые), то этот поток также замораживается.
  8. В системах, отличных от x86, если поток является первым в процессе, и процесс не является простаивающим (idle), тогда процесс вставляется в другой общесистемный список процессов nt.KiProcessListHead.
  9. Количество потоков в объекте процесса увеличивается, а приоритет ввода-вывода (I/O) и приоритет страниц процесса-владельца наследуются. Если это наибольшее количество потоков, которое когда-либо имел процесс, также обновляется верхний предел количества потоков. Если это был второй поток в процессе, основной токен замораживается (то есть его больше нельзя изменить).
  10. Поток вставляется в список потоков процесса и приостанавливается, если создающий процесс запросил это.
  11. Объект потока вставляется в таблицу дескрипторов процесса.
  12. Если это первый поток, созданный в процессе (то есть операция произошла как часть вызова CreateProcess*), функцией nt.PspCallProcessNotifyRoutines вызываются все зарегистрированные через nt.PsSetCreateProcessNotifyRoutine(Ex/Ex2) колбеки для создания процесса. Затем функцией nt.PspCallThreadNotifyRoutines вызываются все зарегистрированные через nt.PsSetCreateThreadNotifyRoutine(Ex) колбеки потока. Если какой-либо колбек накладывает запрет на создание, то оно завершится неудачей и вернёт соответствующий статус вызывающей стороне.
  13. Если был предоставлен список Job (с использованием атрибута) и это первый поток в процессе, то процесс назначается всем Job'ам в списке.
  14. Поток подготавливается к выполнению путём вызова nt.KeReadyThread. Он переходит в состояние отложенной (deferred) готовности.
1.png


Этап 5: Инициализация подсистемы Windows


После того, как NtCreateUserProcess возвращает STATUS_SUCCESS, необходимые executive объекты процесса и потока созданы, мы снова оказываемся в юзер-моде. Затем kernelbase.CreateProcessInternalW выполняет различные операции специфичные для подсистемы Windows, чтобы завершить инициализацию процесса.
  1. Проводятся различные проверки того, следует ли Windows разрешать запуск исполняемого файла. Эти проверки включают проверку версии образа в заголовке и проверку сертификации приложений Windows через групповую политику. В специализированных выпусках Windows Server 2012 R2, таких как Windows Storage Server 2012 R2, проводятся дополнительные проверки, чтобы увидеть, импортирует ли приложение какие-либо запрещённые API.
  2. Если это предписывают политики ограничения ПО, для нового процесса создаётся ограниченный токен. После этого запрашивается база данных совместимости приложений, чтобы узнать, существует ли запись в реестре или базе данных системных приложений для процесса. На этом этапе оболочки совместимости (compatibility shims) не будут применяться; информация будет сохранена в PEB после начала выполнения начального потока (этап 6).
  3. kernelbase.CreateProcessInternalW вызывает некоторые внутренние функции (для незащищённых процессов) для получения информации SxS, такой как файлы манифеста и пути перенаправления DLL, а также другую информацию, такую как съёмный ли носитель, на котором находится EXE, и флаги обнаружения установщика. Для современных (UWP) процессов он также возвращает информацию о версии и целевой платформе из манифеста пакета.
  4. Сообщение для подсистемы Windows формируется на основе собранной информации для отправки в Csrss. Сообщение включает в себя следующую информацию:

  • Имя пути и имя пути SxS
  • Дескрипторы процесса и потока
  • Дескриптор секции
  • Дескриптор токена доступа
  • Информация о медиа
  • Данные AppCompat и shim
  • Информация о современном процессе, если имеется
  • Адрес PEB
  • Различные флаги, например, защищённый ли это процесс или процесс под элевацией.
  • Флаг, указывающий, принадлежит ли процесс приложению Windows (чтобы Csrss мог определить, показывать ли курсор запуска)
  • Информация о языке пользовательского интерфейса
  • Редиректы DLL и .local флаги
  • Информация о файле манифеста
Получив это сообщение, подсистема Windows выполняет следующие действия:
  1. csrsrv.CsrCreateProcess дублирует дескриптор для процесса и потока. На этом этапе счетчик использования процесса и потока увеличивается с 1 (который был установлен во время создания) до 2
  2. Аллоцируется структура процесса Csrss (CSR_PROCESS).
  3. Порт исключений нового процесса устанавливается как общий порт функции для подсистемы Windows, чтобы подсистема Windows получала сообщение, когда в процессе возникает second-chance исключение.
  4. Если необходимо создать новую группу процессов с новым процессом, выступающим в качестве корня (флаг CREATE_NEW_PROCESS_GROUP в CreateProcess*), то он устанавливается и в CSR_PROCESS. Группа процессов полезна для отправки события управления набору процессов, совместно использующих консоль. См. документацию Windows SDK для CreateProcess и GenerateConsoleCtrlEvent для получения дополнительной информации.
  5. Аллоцируется и инициализируется структура потока Csrss (CSR_THREAD).
  6. csrsrv.CsrCreateThread вставляет поток в список потоков для процесса.
  7. Количество процессов в этом сеансе увеличивается.
  8. Shutdown level процесса устанавливается на 0x280, значение по умолчанию. См. SetProcessShutdownParameters в документации Windows SDK для получения дополнительной информации.
  9. Новая структура процесса Csrss вставлена в список процессов подсистемы Windows.

После того, как Csrss выполнил эти шаги, kernelbase.CreateProcessInternalW проверяет, был ли процесс запущен с повышенными правами (что означает, что он был запущен через shell32.ShellExecute(Ex)A/W и повышен службой AppInfo после того, как пользователю было показано диалоговое окно). Это включает проверку того, был ли процесс программой установки. Если это так, открывается токен процесса и включается флаг виртуализации, чтобы приложение было виртуализировано. Если приложение содержало elevation shim'ы или имело уровень элевации в своём манифесте, процесс уничтожается, и запрос на повышение прав отправляется службе AppInfo.

Обратите внимание, что большинство этих проверок не выполняются для защищённых процессов. Поскольку эти процессы разработаны для Windows Vista или более поздней версии, нет причин, по которым они должны требовать элевации, виртуализации или проверки и обработки совместимости приложений. Кроме того, возможности таких механизмов, как shim engine, использовать свои обычные методы перехвата и исправления памяти в защищённом процессе приведут к дыре в безопасности, если кто-то сможет придумать, как вставить произвольные shim, которые изменят поведение защищённого процесса. Кроме того, поскольку shim engine устанавливается родительским процессом, который может не иметь доступа к своему дочернему защищённому процессу, даже законный shim не может работать.

Этап 6: Запуск главного потока

На этом этапе среда процесса определена, ресурсы для потоков, которые будут использоваться, выделены, у процесса есть поток, и подсистема Windows знает о новом процессе. Если вызывающий не указал флаг CREATE_SUSPENDED, исходный поток теперь возобновляется, чтобы он мог начать работу и выполнить оставшуюся часть работы по инициализации процесса, которая происходит в контексте нового процесса (этап 7).

Этап 7: Инициализация процесса в его контексте. Загрузка PE модуля

Новый поток начинает жизнь, запуская процедуру запуска потока режима ядра nt.KiStartUserThread, которая понижает уровень IRQL потока с уровня отложенного вызова процедуры (DPC) до уровня APC, а затем вызывает системную начальную процедуру потока nt.PspUserThreadStartup. Указанный пользователем начальный адрес потока передается в эту процедуру в качестве параметра. nt.PspUserThreadStartup выполняет следующие действия:

  1. Устанавливает цепочку исключений на архитектуре x86.
  2. Понижает IRQL до PASSIVE_LEVEL (0, что является единственным IRQL, на котором разрешено выполняться пользовательскому коду).
  3. Отключает возможность замены основного токена процесса во время выполнения.
  4. Если поток был убит при запуске по какой-либо причине, он завершается, и никаких дальнейших действий не предпринимается.
  5. Он устанавливает идентификатор локали и идеальный процессор в TEB на основе информации, присутствующей в структурах данных режима ядра, а затем проверяет, действительно ли создание потока завершилось неудачей.
  6. Вызывает nt.DbgkCreateThread, который проверяет, были ли отправлены уведомления об изображении для нового процесса. Если они не были отправлены, и уведомления включены, сначала отправляется уведомление об изображении для процесса, а затем для загрузки изображения Ntdll.dll.
Это делается на этом этапе, а не при первом мапинге образов, поскольку идентификатор процесса (который требуется для колбека ядра) на тот момент ещё не выделен.
  1. После завершения этих проверок выполняется ещё одна проверка, чтобы определить, является ли процесс отлаживаемым. Если это так и если уведомления отладчика ещё не были отправлены, то сообщение о создании процесса отправляется через объект отладки (если он присутствует), чтобы событие отладки запуска процесса (CREATE_PROCESS_DEBUG_INFO) можно было отправить соответствующему процессу отладчика. За этим следует аналогичное событие отладки запуска потока и ещё одно событие отладки для загрузки образа Ntdll.dll. Затем nt.DbgkCreateThread ждёт ответа от отладчика (через функцию kernelbase.ContinueDebugEvent).
  2. Проверяет, включена ли в системе предварительная загрузка приложений; если да, вызывает предварительную загрузку для обработки файла инструкций предварительной загрузки и страниц предварительной загрузки, к которым обращались в течение первых 10 секунд последнего запуска процесса.
  3. Проверяет, был ли установлен системный cookie в структуре SharedUserData. Если нет, он генерирует его на основе хэша системной информации, такой как: количество обработанных прерываний, доставки DPC, ошибки страниц, время прерывания и случайное число. Этот системный cookie используется при внутреннем декодировании и кодировании указателей (ntdll.RtlEncodeSystemPointer и ntdll.RtlDecodeSystemPointer), например, в диспетчере кучи для защиты от определенных классов эксплуатации.
  4. Если процесс является безопасным (IUM), то выполняется вызов nt.HvlStartSecureThread, который передаёт управление безопасному ядру для запуска выполнения потока. Эта функция возвращается только при выходе из потока.
Далее ядро на лету подменяет контекст потока таким образом, чтоб он начался в ntdll.LdrInitializeThunk, сохраняя старый контекст в первый параметр этой фунции и базовый адрес изображения во второй. ntdll.LdrInitializeThunk вызывается для каждого создаваемого потока и если поток первый в процессе, то она в итоге дойдёт до функции ntdll.LdrpInitializeProcess, которая выполнит следующие действия:

  1. Инициализирует поле Ldr у PEB, записывая в неё переменную ntdll.PebLdr после предварительного заполнения.
  2. Вызовет функцию ntdll.LdrpInitializeNlsInfo, которая инициализирует NLS таблицы для процесса.
  3. Вносит новую запись для загружаемого .EXE модуля в таблицу исключений ntdll.LdrpInvertedFunctionTable функцией ntdll.RtlInsertInvertedFunctionTable. Эта таблица содержит информацию обо всех допустимых обработчиках исключений для каждого загруженного модуля.
При реализации ручного загрузчика (LoadPE) для поддержки исключений необходимо вносить эту запись вручную (или пытаться искать ntdll.RtlInsertInvertedFunctionTable в памяти).

  1. Инициализирует глобальную булевую переменную ntdll.UseWOW64: если значение в поле WowTebOffset у TEB больше нуля, то это значит, что процесс запущен в контексте WoW64. Эта переменная отсутствует в 32-битной Ntdll.dll.
  2. Инициализирует глобальную булевую переменную ntdll.UseCOR, истинное значение которой означает, что загружаемый .EXE модуль является .NET образом:

• если значение в поле WowTebOffset у TEB меньше или равно нулю (оно знаковое), но при этом загружаемый модуль является 32-битным (Поле Magic в IMAGE_OPTIONAL_HEADER32 имеет значение IMAGE_NT_OPTIONAL_HDR32_MAGIC), переменная устанавливается в true, а также вызывается вспомогательная процедура ntdll.LdrpCorFixupImage, которая преобразует формат загружаемого .EXE модуля PE32 в PE32+.

• если загружаемый .EXE модуль является 64-битным или же загрузка происходит в 32-битной Ntdll.dll, то значение переменной устанавливается, исходя из наличия в модуле IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR — она есть только у .NET образов.

  1. Инициализирует TLS И FLS: поля TlsBitmap, TlsExpansionBitmap, TlsExpansionBitmapBit у PEB, переменную ntdll.RtlpFlsContext и вызывает функцию ntdll.LdrpInitializeTls.
  2. Инициализирует менеджер кучи функцией ntdll.RtlInitializeHeapManager и создаёт ProcessHeap у PEB через функцию ntdll.LdrpInitializeProcessHeap.
  3. Аллоцирует для Ntdll новую запись в таблице модулей через ntdll.LdrpAllocateModuleEntry и вносит её в таблицу модулей PebLdr функцией ntdll.LdrpInsertDataTableEntry. Эта запись также сохраняется в глобальную переменную ntdll.LdrpNtDllDataTableEntry.
  4. Аллоцирует для загружаемого модуля новую запись в таблице модулей через ntdll.LdrpAllocateModuleEntry и вносит её в таблицу модулей PebLdr функцией ntdll.LdrpInsertDataTableEntry по аналогии с Ntdll. Эта запись также сохраняется в глобальную переменную ntdll.LdrpImageEntry. Все остальные модули будут подгружаться через ntdll.LdrLoadDll или её внутренние версии, которые сами будут вносить запись в таблицу модулей.
  5. Если .EXE модуль загружен не по тому ImageBase, который указан в IMAGE_OPTIONAL_HEADER, то вызывает функцию ntdll.LdrpProtectAndRelocateImage, которая на основе таблицы IMAGE_DIRECTORY_ENTRY_BASERELOC проведёт релокацию модуля.
  6. Если .EXE модуль является .NET изображением, то вызывает функцию ntdll.LdrpCorInitialize, которая загружает MsCoree.dll и сохраняет адрес функции mscoree._CorExeMain в глобальную переменную ntdll.LdrpCorExeMainRoutine, и после этого подменяет адрес точки входа в переданном контексте на mscoree._CorExeMain.
  7. Для изображений с подсистемами IMAGE_SUBSYSTEM_WINDOWS_GUI и IMAGE_SUBSYSTEM_WINDOWS_CUI загружает Kernel32.dll и Kernelbase.dll. Из Kernel32 достаёт адрес функции BaseThreadInitThunk и записывает в переменную ntdll.Kernel32ThreadInitThunkFunction.
  8. Вызывает функцию ntdll.LdrpMapAndSnapDependency для загружаемого .EXE модуля, которая разрезолвит импорты модуля и загрузит все зависимые DLL. Также выполнение этой функции приведёт к вызову ntdll.LdrpHandleTlsData, которая инициализирует статический TLS в загружаемом модуле при наличии в нём IMAGE_DIRECTORY_ENTRY_TLS, и ntdll.LdrpFindDllActivationContext, которая обработает так называемый файл манифеста, необходимый для работы SxS и ресурсов многоязычного пользовательского интерфейса (MUI).
  9. Инициализует shim engine вызовом ntdll.LdrpInitShimEngine. Необходимость инициализации shim engine выясняется на основе поля pShimData у PEB.
  10. Вызывает функцию ntdll.LdrpCallTlsInitializers для .EXE модуля с причиной DLL_PROCESS_ATTACH. Эта функция запускает TLS-колбеки указанного модуля (если они имеются) с указанной причиной.

C++:
NTSYSAPI VOID NTAPI LdrInitializeThunk(
  _In_ PCONTEXT ContextRecord,
  _In_ PVOID ImageBase // NtDoc называет его просто 'Parameter', но по факту это базовый адрес изображения
);

NTSTATUS NTAPI LdrpInitializeProcess(_Inout_ PCONTEXT ContextRecord, _In_ PVOID ImageBase);

VOID NTAPI LdrpInitializeThread(_In_ PCONTEXT ContextRecord);

VOID NTAPI LdrpInitializeNlsInfo(_Inout_ PPEB Peb);

VOID NTAPI LdrpMapAndSnapDependency(_Inout_ PLDRP_LOAD_CONTEXT LoadContext);

PLDR_DATA_TABLE_ENTRY NTAPI LdrpAllocateModuleEntry(_In_opt_ PLDRP_LOAD_CONTEXT LoadContext);

NTSTATUS NTAPI LdrpCallTlsInitializers(_In_ DWORD Reason, _In_ PLDR_DATA_TABLE_ENTRY Module);

NTSTATUS NTAPI LdrpProtectAndRelocateImage(_In_ PVOID ImageBase);

NTSTATUS NTAPI LdrpCorFixupImage(_In_ PVOID ImageBase);

VOID NTAPI LdrpInsertDataTableEntry(_Inout_ PLDR_DATA_TABLE_ENTRY Entry);

NTSTATUS NTAPI LdrpFindDllActivationContext(_Inout_ PLDR_DATA_TABLE_ENTRY Entry);

VOID NTAPI RtlInsertInvertedFunctionTable(_In_ PVOID ImageBase, _In_ ULONG SizeOfImage);

NTSTATUS __thiscall LdrpHandleTlsData(_In_ PLDR_DATA_TABLE_ENTRY Module); // Windows 8.1/Windows Server 2012 R2+
NTSTATUS NTAPI LdrpHandleTlsData(_In_ PLDR_DATA_TABLE_ENTRY Module); // Windows 8 and older

После успешного завершения также вызовется функция ntdll.LdrpInitializeThread, которая, в отличие от ntdll.LdrpInitializeProcess будет вызываться для каждого потока, как нетрудно догадаться. Она вызывает ntdll.LdrpAllocateTls для настройки TLS и триггерит TLS-колбеки и DllMain функции всех загруженных модулей (кроме тех, у кого установлен флаг LDRP_DONT_CALL_FOR_THREADS, который можно установить функцией ntdll.LdrDisableThreadCalloutsForDll или kernel32.DisableThreadLibraryCalls, которая является её обёрткой) с поводом DLL_THREAD_ATTACH.

Теперь, когда все приготовления закончены и все функции успешно завершились, ntdll.LdrInitializeThunk делает системный вызов NtContinue, передавая туда сохранённый ядром контекст из аргументов — тот самый, который хранит в Rcx (Eax для WoW64) регистре адрес точки входа, которую мы подменяем, когда пишем RunPE или его модификации.

Мы оказываемся в ntdll.RtlUserThreadStart, которая по сути просто оборачивает вызов точки входа в __try/__except, где __except блок пытается вызвать фильтр для неотловленных исключений (unhandled exception filter). ntdll.RtlUserThreadStart вызывает точку входа либо напрямую (нативные процессы), либо через функцию kernel32.BaseThreadInitThunk, адрес которой был сохранён ранее при инициализации процесса (именно по этой причине вы всегда видите этих двоих из ларца в стеке у любого потока). После возврата точки входа вызывается ntdll.RtlExitUserThread, который очищает ресурсы потока и убивает либо только себя (поток), либо целиком процесс, если этот поток был последним в процессе.

Вместо заключения


Это была вторая и заключительная часть обзора создания процессов в Windows. Она получилась обширнее первой, так как я решил обратиться к оригиналу, то есть к книге Windows Internals 7th Edition; также я дополнил 7 этап своими анализами PE загрузчика Windows. Возможно, при написании я даже где-то мог ошибиться, но так или иначе это будет полезно для крипторов.

Источники:
  1. https://docs.microsoft.com/en-us/
  2. Windows Internals 7th Edition, Part 1
  3. 8.8.8.8
 

Вложения

  • 23.png
    23.png
    47.9 КБ · Просмотры: 12
  • 1.png
    1.png
    41.9 КБ · Просмотры: 12
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх