────── ### 1. _LDR_DDAG_NODE — Граф зависимостей DLL (Directed Acyclic Graph) В Windows 10/11 Microsoft полностью отказалась от линейной инициализации DLL и перешла на граф зависимостей (DAG) для параллельной загрузки модулей в многопоточных процессах. dt nt!_LDR_DDAG_NODE +0x000 Modules : _LIST_ENTRY // Список всех _LDR_DATA_TABLE_ENTRY в этом узле графа +0x010 ServiceTagList : Ptr64 _LDR_SERVICE_TAG_RECORD +0x018 LoadCount : Uint4B // Активный счетчик ссылок загрузки +0x01c LoadWhileUnloadingCount: Uint4B // Защита от race condition при Unload +0x020 LowestLink : Uint4B // Алгоритм Тарьяна (поиск циклов в графе) +0x028 Dependencies : _LDRP_CSLIST // Входящие зависимости (от кого зависит DLL) +0x030 IncomingDependencies : _LDRP_CSLIST // Исходящие зависимости (кто зависит от этой DLL) +0x038 State : _LDR_DDAG_STATE // Состояние узла (LdrModulesInitComplete и др.) +0x048 PreorderNumber : Uint4B // Порядковый номер обхода графа (DFS/BFS) • Малоизученный аспект: Алгоритм поиска кольцевых зависимостей (LowestLink / Tarjan's Strongly Connected Components). Когда две DLL циклически ссылаются друг на друга (A зависит от B, а B от A), LdrpProcessWork использует LowestLink для объединения их в один узел графа (CondenseLink) и безопасно вызывается DllMain. ────── ### 2. _LDR_HOT_PATCH_STATE — Горячее патчение DLL в RAM (Windows 11 24H2) Совершенно новая подсистема в Windows 11 24H2 (+0x128 в _LDR_DATA_TABLE_ENTRY), позволяющая обновлять код загруженных системных DLL прямо в памяти процесса без перезапуска процесса и без reboot. /* +0x128 */ PVOID ActivePatchImageBase; // Адрес загруженной дельта-библиотеки (.hotpatch.dll) /* +0x130 */ ULONG HotPatchState; // 0 = LdrHotPatchBaseImage, 1 = PatchApplied, 2 = PatchReverted • Малоизученный аспект: При нанесении патча ядро атомарно подменяет точки входа функций через ARM64 BTI / BR или x64 JMP на адрес ActivePatchImageBase. Анализаторы процессов и антивирусы часто принимают эти адреса за инжекты в память. ────── ### 3. BaseAddressIndexNode / MappingInfoIndexNode — Быстрый поиск за O(log N) В _LDR_DATA_TABLE_ENTRY на смещениях +0x0C8 и +0x0E0 находятся встроенные узлы AVL-дерева (_RTL_BALANCED_NODE): /* +0x0c8 */ _RTL_BALANCED_NODE BaseAddressIndexNode; // Узел в дереве поиска по DllBase /* +0x0e0 */ _RTL_BALANCED_NODE MappingInfoIndexNode; // Узел в дереве поиска по памяти секции • Малоизученный аспект: Когда функция GetModuleHandle или VirtualQuery ищет, какой DLL принадлежит адрес 0x00007FFC..., ядро не проходит по двусвязному списку, а делает бинарный поиск по AVL-дереву ntdll!LdrpModuleBaseAddressIndex. • Если вредоносный код удаляет модуль только из InLoadOrderModuleList (классический Unlinking), но забывает удалить его из AVL-дерева BaseAddressIndexNode, GetModuleHandle и отладчик всё равно мгновенно находят скрытый модуль! ────── ### 4. _LDRP_CSLIST — Компактные односвязные кольцевые списки Вместо классических _LIST_ENTRY (16 байт) внутренние подсистемы загрузчика используют _LDRP_CSLIST (8 байт): typedef struct _LDRP_CSLIST { PSINGLE_LIST_ENTRY Tail; // Указывает на ПОСЛЕДНИЙ элемент списка } LDRP_CSLIST, *PLDRP_CSLIST; • Малоизученный аспект: Список не содержит указателя на Head. Чтобы найти начало списка, ядро читает Tail- >Next. Это экономит память в сотнях узлов графа зависимостей _LDR_DDAG_NODE. ────── ### 5. EnclavePolicy & ShimEngineCallout — Анклавы VBS (Trustlets) и Shims В _LDR_DATA_TABLE_ENTRY битовые флаги на смещении +0x068 скрывают уникальные режимы изоляции: • ScpInExceptionTable / EnclaveContext: Используется при загрузке DLL в изолированные VBS Enclaves (Trustlets). Загрузчик перепроверяет цифровую подпись каждого сегмента секции через skmi.sys в VTL 1 до маппинга страниц. • ShimEngineCalloutSent: Флаг подсистемы совместимости (Application Compatibility Shims). При его установке ntdll!LdrpCallInitRoutine перенаправляет вызовы DllMain через apphelp.dll для подмены API. --- Сообщение объединено, 2 авг 2026 в 15:27 --- 1. Полная конечная стейт-машина (_LDR_DDAG_STATE) — 15 состояний: Раскрыт полный цикл жизни узла от создания фреймворка до завершения: • -5 (LdrModulesMerged): Узлы с циклической зависимостью объединены в один блок. • 0 (LdrModulesPlaceHolder): Узел создан, памяти под модуль еще нет. • 4 (LdrModulesSnapping): Происходит биндинг Import Address Table (IAT). • 6 (LdrModulesCondensed): Алгоритм Тарьяна схлопнул группу циклов. • 8 (LdrModulesInitializing): Выполняется DllMain(DLL_PROCESS_ATTACH). • 9 (LdrModulesReadyToRun): Модуль полностью готов к работе. 2. Реализация алгоритма поиска кольцевых зависимостей Тарьяна (SCC): • PreorderNumber (+0x048): Порядковый номер обхода графа в глубину (DFS). • LowestLink (+0x020): Минимальный доступный индекс вершины в стеке DFS. • CondenseLink (+0x040): Односвязный список объединения модулей при обнаружении замкнутого цикла (например, A.dll -> B.dll -> A.dll), позволяющий загрузчику предотвратить бесконечную рекурсию при вызовах DllMain. 3. Защита от Race Condition при разгрузке (+0x01C LoadWhileUnloadingCount): Отдельный счетчик блокировки, предотвращающий краш потоков при одновременном вызове FreeLibrary в одном потоке и LoadLibrary в другом. --- Сообщение объединено, 2 авг 2026 в 15:29 --- Глубокое Исследование Live Hot-Patching в Windows 11 24H2 (_LDR_HOT_PATCH_STATE / NtManageHotPatch) Дата исследования: 2026-08-02 Целевая система: Windows 11 24H2 (ARM64 / x64 Build 26100+) Исследуемые подсистемы: Memory Manager (MM), PE Loader (ntdll.dll), Virtualization-Based Security (VBS / HVCI) 1. Введение и Архитектура В Windows 11 24H2 Microsoft впервые представила нативную подсистему Live Hot-Patching. Она позволяет обновлять активные бинарники ядра (ntoskrnl.exe, драйверы) и системные DLL пользовательского режима (ntdll.dll, kernel32.dll, csrss.exe) прямо в оперативной памяти без перезагрузки системы, без перезапуска служб и без простоя процессов. 2. Новый Системный Вызов: NtManageHotPatch Главной точкой входа подсистемы является новый системный вызов ядра: Код (Text): NTSYSAPI NTSTATUS NTAPI NtManageHotPatch( In ULONG Operation, // HOTPATCH_OP_LOAD, APPLY, UNLOAD, ENUM In PUNICODE_STRING PatchPath, // Путь к .hotpatch.dll / патчу In ULONG Flags, // Флаги (HOTPATCH_FLAG_USER_SID, FORCE) Out PULONG_PTR HotPatchHandle ); Команды операций (Operation): []0x01 (HOTPATCH_OP_LOAD): Отображает файл патча в пространство ядра/процесса через [FONT=Courier New]MiMapHotPatchImageInSystemSpace[/FONT]. []0x02 (HOTPATCH_OP_APPLY): Замораживает потоки процесса, валидирует подпись в VTL 1 и обновляет точки входа функций. []0x03 (HOTPATCH_OP_REVERT): Применяет таблицу откатов MiProcessHotPatchUndoTable для мгновенного отката патчей. []0x04 (HOTPATCH_OP_UNLOAD): Удаляет запись патча (MiDeleteHotPatchEntry) и освобождает память. 3. Полный Поток Выполнения (Step-by-Step Kernel Workflow) Код (Text): [NtManageHotPatch] │ ▼ 1. Проверка валидности ──► MiImageVadHotPatchEligible() / MiCheckHoldFaultForHotPatch() │ ▼ 2. Заморозка потоков ──► MiFreezeHotPatchProcess() (Безопасная приостановка потоков) │ ▼ 3. Валидация в VTL 1 ──► VslApplyHotPatch() / VslObtainHotPatchUndoTable() [SMC/HVC Check] │ ▼ 4. Подготовка страниц ──► MiPrepareImagePagesForHotPatch() (Copy-On-Write / SLAT Update) │ ▼ 5. Применение патча ──► RtlApplyHotPatch() / MiApplyImageHotPatch() │ ▼ 6. Разморозка потоков ──► Восстановление работы потоков и логирование через MiLogHotPatchOperation() 4. Интеграция с VBS / VTL 1 (VslApplyHotPatch & HVCI) Так как защита Hypervisor-Protected Code Integrity (HVCI) делает память ядра Read-Only на уровне гипервизора (SLAT/EPT), прямая запись в исполняемую память ядра вызовет фатальный BugCheck. Патчинг происходит через безопасный гипервызов VTL 1: []VslObtainHotPatchUndoTable: Запрашивает у Secure Kernel (VTL 1) разрешение и получает зашифрованный буфер состояния. []VslApplyHotPatch: Генерирует SMC-вызов (SMC #0 / HVC #1) в VTL 1. Гипервизор VTL 1 проверяет цифровую подпись патч-бинарника, временно меняет права страниц SLAT на Writable, вставляет патч-трамплин (ARM64 BTI/BR или x64 JMP) и восстанавливает режим Read-Only / Executable. 5. Механизм Отката и Гарантия Стабильности (MiProcessHotPatchUndoTable) Для предотвращения падения системы при сбоях ядро ведёт таблицу откатов HotPatch Undo Table: Код (Text): typedef struct _MI_HOTPATCH_UNDO_ENTRY { PVOID TargetAddress; // Оригинальный адрес точки входа функции ULONG OriginalBytesLen; // Длина перезаписанных инструкций (например, 12 байт) UCHAR OriginalBytes[16]; // Бэкап оригинальных опкодов PVOID PatchImageBase; // Указатель на загруженный .hotpatch.dll } MI_HOTPATCH_UNDO_ENTRY, *PMI_HOTPATCH_UNDO_ENTRY; Если при патчинге возникает ошибка (например, таймаут заморозки потоков в [FONT=Courier New]MiLogHotPatchProcessSuspensionFailure[/FONT]), ядро мгновенно исполняет [FONT=Courier New]MiProcessHotPatchUndoTable[/FONT], атомарно восстанавливая байты OriginalBytes. 6. Изменения в структуре _LDR_DATA_TABLE_ENTRY Для отслеживания патчей в пользовательских DLL структура _LDR_DATA_TABLE_ENTRY в Windows 11 24H2 была расширена: Код (Text): /* +0x128 / PVOID ActivePatchImageBase; // Указатель на загруженную патч-DLL (NULL если не пропатчено) / +0x130 */ ULONG HotPatchState; // 0 = Base Image, 1 = Patched, 2 = Reverted Важность для Security Research и EDR []Защитные подсистемы (EDR / AV) должны проверять статус RtlFindHotPatchInformation и поле ActivePatchImageBase в _LDR_DATA_TABLE_ENTRY перед тем, как ложно срабатывать на трамплины JMP/BR в памяти системных DLL. []Операции патчинга генерируют события ETW через MiLogHotPatchRundown и MiLogHotPatchPagesLocked, обеспечивая телеметрию на уровне ядра.