Исследование Control Flow Guard (CFG) Обзор Control Flow Guard (CFG) — это высокооптимизированный механизм безопасности платформы, созданный Microsoft для борьбы с уязвимостями повреждения памяти. Он накладывает строгие ограничения на то, откуда приложение может выполнять код, что значительно усложняет эксплуатацию уязвимостей, позволяющих выполнять произвольный код, например: переполнение буфера (buffer overflow) повреждение указателей функций corruption vtable Как работает CFG Основная концепция CFG проверяет цели косвенных вызовов (indirect calls) во время выполнения, чтобы убедиться, что они указывают на допустимые точки входа функций. Когда выполняется косвенный вызов (через): указатель функции vtable таблицу переходов другие механизмы косвенных вызовов CFG проверяет, находится ли адрес цели в битовой карте допустимых целей вызова. Компоненты архитектуры 1. CFG Bitmap Расположение: хранится в Process Environment Block (PEB) Структура: битовая карта, где каждый бит соответствует 16-байтовой выровненной области памяти Вычисление позиции Byte index = (address >> 4) / 4 Bit index = (address >> 4) % 8 Каждый бит покрывает: 16 bytes (0x10) адресного пространства. 2. Процесс проверки Indirect Call → _guard_check_icall → CfgAddressToBitState → Проверка bitmap ↓ Валиден? → продолжить выполнение Нет? → завершить процесс Основные функции (ntdll) ФункцияАдрес (пример)НазначениеLdrControlFlowGuardEnforcedntdll!LdrControlFlowGuardEnforcedПроверяет включён ли CFG для процессаCfgAddressToBitStatentdll!CfgAddressToBitStateПреобразует адрес в позицию бита bitmapScpCfgValidateUserCallTargetntdll!ScpCfgValidateUserCallTargetБыстрая процедура проверкиScpCfgDispatchUserCallTargetntdll!ScpCfgDispatchUserCallTargetПроверка + переход_guard_check_icallntdll!_guard_check_icallПроверка, вставляемая компилятором_guard_dispatch_icallntdll!_guard_dispatch_icallПроверка + переход (оптимизация)Структуры метаданных PE Load Configuration Directory Исполняемый файл содержит метаданные CFG в Load Configuration. GuardCFFunctionTable (GFIDS Table) Отсортированный массив RVA допустимых целей косвенных вызовов Каждая запись имеет размер: 4 + n байт где n определяется GuardFlags Если таблица не отсортирована, образ не загрузится. Значения GuardFlags ФлагЗначениеОписаниеIMAGE_GUARD_CF_INSTRUMENTED0x00000100Бинарник инструментирован CFGIMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT0x00000200Таблица GFIDS присутствуетIMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT0x00004000Есть метаданные подавления экспортовIMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION0x00008000Подавление экспортов включеноIMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT0x00010000Таблица longjmpIMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK0xF0000000Маска дополнительных метаданныхФлаги GFIDS (для каждой записи) ФлагЗначениеОписаниеIMAGE_GUARD_FLAG_FID_SUPPRESSED0x01Цель вызова явно подавленаIMAGE_GUARD_FLAG_EXPORT_SUPPRESSED0x02Экспорт подавленДополнительные таблицы GuardAddressTakenIatEntryTable — импорт-thunks, адрес которых берётся GuardLongJumpTargetTable — допустимые цели setjmp/longjmp Анализ в WinDbg Проверка статуса CFG ; Проверить включён ли CFG u ntdll!LdrControlFlowGuardEnforced ; Найти все CFG символы x ntdll!*Guard* x ntdll!*Cfg* Проверка указателей CFG функций ; Проверить check функцию dq ntdll!_guard_check_icall_fptr L1 u poi(ntdll!_guard_check_icall_fptr) ; Проверить dispatch функцию dq ntdll!_guard_dispatch_icall_fptr L1 u poi(ntdll!_guard_dispatch_icall_fptr) Просмотр информации о процессе ; Показать PEB !peb ; Список модулей lm ; Проверить заголовки dumpbin /headers /loadconfig <module.exe> Установка breakpoint для отладки CFG bp ntdll!ScpCfgHandleInvalidCallTarget bp ntdll!CfgAddressToBitState bp ntdll!ScpCfgValidateUserCallTarget Анализ GFIDS таблицы dq ntdll!_guard_fids_table dq ntdll!_guard_longjmp_table Интеграция с компилятором Включение CFG Visual Studio Project Properties → C/C++ → Code Generation → Control Flow Guard = Yes (/guard:cf) Командная строка cl /guard:cf source.cpp /link /guard:cf Директивы компилятора Запрет функции как цели CFG __declspec(guard(suppress)) void dangerous_function() { } Отключить CFG для функции __declspec(guard(nocf)) void manually_protected_function() { } Инструментирование компилятором Компилятор вставляет проверку перед косвенным вызовом. ; Перед косвенным вызовом mov rax, [function_pointer] call _guard_check_icall Функция проверки проверяет RAX. если цель валидна → выполнение продолжается если нет → процесс завершается Export Suppression (CFG ES) Подавление экспортов уменьшает поверхность атаки. Экспортируемые функции не считаются допустимыми целями, пока не будет вызван: GetProcAddress Требования IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GuardAddressTakenIatEntryTable IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION Защита Long Jump CFG усиливает защиту setjmp/longjmp. Механизм: longjmp реализуется через RtlUnwindEx используется статус STATUS_LONGJUMP цели проверяются через GuardLongJumpTargetTable Соображения безопасности Выравнивание функций Функции должны быть выровнены на: 16 байт Если нет — вся 16-байтовая область считается валидной. Это снижает безопасность. Снижение поверхности атаки Export Suppression Ограничивает допустимые цели. Защита IAT Import Address Table → read-only Ограничения CFG: не защищает от всех hijack-атак JIT-код требует ручного управления misaligned функции снижают точность защиты возможны обходы через невыравненные цели Ключевое открытие (WinDbg) Когда бинарник компилируется с: /guard:cf загрузчик Windows выполняет: выделяет CFG bitmap заменяет NOP-заглушки на реальные валидаторы включает принудительное выполнение CFG Сравнение бинарников Параметрloader_pe (без CFG)cfg_demo (/guard:cf)Guard flagотсутствуетприсутствуетguard_check_icallNOPреальный валидаторdispatchNOPреальный валидаторCFG bitmapNULLвыделенповедениепросто jumpпроверка + crashОсновные наблюдения система ARM64, выполняющая x64 код через CHPEv2 ntdll — гибридный бинарник (ARM64 + x64) инфраструктура CFG присутствует но для процесса loader_pe.exe она не активна Ключевые факты CFG bitmap = NULL все _guard_* указатели указывают на NOP stubs ntdll содержит 0x180A GFIDS entries все они помечены export suppression Итог Главные выводы 1️⃣ Магия загрузчика PE файл содержит NOP заглушки, но loader патчит их во время загрузки. 2️⃣ CFG bitmap Хранится по адресу вроде: 0x7DF5E5E00000 3️⃣ Два режима проверки режимфункцияcheckпроверкаdispatchпроверка + переход4️⃣ Обработка выравнивания aligned → один бит unaligned → два бита 5️⃣ Fast-fail При нарушении CFG вызывается: int 29h с кодом FAST_FAIL_GUARD_ICALL_CHECK_FAILURE (0xA) 6️⃣ CET / Shadow Stack Флаг: CF_RETURN_FLOW_INSTRUMENTED означает поддержку: Intel CET Shadow Stack --- Сообщение объединено, 13 мар 2026 --- по XFG там вообще непонятно есть ли он или нету ) --- Сообщение объединено, 13 мар 2026 --- Исследование XFG (eXtended Flow Guard) Обзор XFG (eXtended Flow Guard) — это расширенная версия Control Flow Guard, разработанная Microsoft. Она добавляет проверку на основе типов (type-based validation) к обычной проверке адресов в CFG. Если CFG проверяет только: является ли адрес допустимой точкой входа функциито XFG проверяет: соответствует ли прототип функции ожидаемой сигнатуре (тип возвращаемого значения, типы параметров, calling convention).Сравнение XFG и CFG ВозможностьCFGXFGГранулярностьПроверка адреса (16-байтовое выравнивание)Проверка типа (hash прототипа функции)ПроверкаЯвляется ли адрес допустимой функциейСовпадает ли сигнатура функцииBitmapCFG bitmap на процессСравнение type-hashГде хранится hash—8 байт перед функциейИспользуемые регистрыRAX (адрес цели)RAX (цель) + R10 (ожидаемый hash)Флаг компилятора/guard:cf/guard:xfg (требует /guard:cf)Как работает XFG 1. Генерация hash Компилятор MSVC генерирует hash на основе сигнатуры функции. Компоненты hash 1️⃣ Количество параметров DWORD 2️⃣ Типы параметров Каждый тип кодируется как: 8 bytes (усечённый SHA256) 3️⃣ Variadic flag 1 byte указывает, variadic ли функция. 4️⃣ Calling Convention DWORD masked with 0x0F например: 0x1 → default x64 5️⃣ Тип возвращаемого значения 8-byte SHA256 truncation Детали хеширования типов В hash включаются: const volatile указатели структуры примитивные типы Типы группируются: pointer struct primitive Для указателей используется рекурсивное хеширование. Финальный hash Берутся первые 8 байт SHA256. После этого backend применяет маску: hash = (hash & 0xFFFDBFFF7EDFFB70) | 0x8000060010500070 2. Размещение hash Hash размещается за 8 байт до функции. Memory Layout: [Function Hash: 8 bytes] [Function Entry Point: code...] ^ address-8 3. Процесс проверки Косвенный вызов mov rax, [function_pointer] mov r10, <expected_hash> call _guard_xfg_dispatch_icall Проверка XFG (ntdll) 1️⃣ загрузить hash [rax - 8] 2️⃣ выполнить OR r10, 1 3️⃣ сравнить hash 4️⃣ если совпадает jmp target 5️⃣ если нет terminate process Основные функции Указатели функций XFG (ntdll) СимволНазначение_guard_xfg_check_icall_fptrуказатель функции проверки_guard_xfg_dispatch_icall_fptrпроверка + переход_guard_xfg_table_dispatch_icall_fptrвариант для таблицguard_xfg_dispatch_icall_nopNOP-заглушкаФункции проверки ФункцияНазначениеLdrpDispatchUserCallTargetXFGXFG dispatch с проверкой типаScpCfgValidateUserCallTargetобщая процедура проверкиScpCfgDispatchUserCallTargetпроверка + переходАнализ в WinDbg Поиск XFG x ntdll!*xfg* Проверка dispatch pointer dq ntdll!_guard_xfg_dispatch_icall_fptr L1 u poi(ntdll!_guard_xfg_dispatch_icall_fptr) Проверка check pointer dq ntdll!_guard_xfg_check_icall_fptr L1 u poi(ntdll!_guard_xfg_check_icall_fptr) Анализ hash функций Чтобы посмотреть hash функции: dq <function_address> - 8 L1 пример dq 00007ff6`25080ff8 L1 Трассировка XFG bp ntdll!LdrpDispatchUserCallTargetXFG bp ntdll!ScpCfgHandleInvalidCallTarget следить за: R10 → expected hash [RAX-8] → actual hash Как выглядит XFG в дизассемблировании Косвенный вызов mov rax, [rip+function_ptr] mov r10, 0x123456789ABCDEF0 call qword ptr [_guard_xfg_dispatch_icall_fptr] Hash перед функцией dq 0x8000060010500070 Пролог функции mov [rsp+8], rcx Анализ структуры hash Hash занимает 8 байт. Bits 0-31 → типы Bits 32-47 → дополнительные данные Bits 48+ → фиксированные биты backend mask Применение backend mask uint64_t raw_hash = sha256_truncated(components); uint64_t final_hash = (raw_hash & 0xFFFDBFFF7EDFFB70) | 0x8000060010500070; Восстановление типов Исследователи безопасности могут: 1️⃣ извлечь hash из бинарников 2️⃣ перебрать прототипы 3️⃣ восстановить сигнатуры функций пример void *function_name(void *arg1, int arg2); Метаданные PE XFG использует тот же Load Configuration, что и CFG. Дополнительные GuardFlags ФлагЗначениеIMAGE_GUARD_XFG_ENABLEDXFG инструментированиеПоля LoadConfig GuardXFGCheckFunctionPointer GuardXFGDispatchFunctionPointer GuardXFGTableDispatchFunctionPointer Интеграция с компилятором Включение XFG Visual Studio Project Properties → C/C++ → Code Generation → Control Flow Guard = Yes with XFG Командная строка cl /guard:cf /guard:xfg Требования Windows 10 2004+ Visual Studio 2019 16.7+ /guard:cf должен быть включён Анализ безопасности Преимущества XFG 1️⃣ type safety 2️⃣ более точная проверка 3️⃣ защита от COOP 4️⃣ защита vtable hijack Ограничения 1️⃣ коллизии hash 2️⃣ эквивалентные типы 3️⃣ не все вызовы защищены 4️⃣ overhead производительности Возможные обходы Исследователи изучают: коллизии hash функции с одинаковыми hash type equivalence вызовы без XFG Результаты WinDbg исследования Система: Windows 11 24H2 ARM64 + CHPEv2 x64 emulation Таблица указателей XFG Адрес: ntdll+0x3B0000 dq 00007ff9`c0ee0000 L5 XFG NOP stubs Check ret 0 Dispatch jmp rax Анализ hash функций Перед функциями: 00000000 что означает: XFG hash отсутствует Вывод ntdll не скомпилирован с XFG. Инфраструктура существует, но: type hash отсутствуют loader_pe.exe XFG указатели указывают на stub функции. ret 0 jmp rax KERNEL32.dll Поведение аналогично: XFG инфраструктура есть но активна не полностью Главные наблюдения 1️⃣ XFG инфраструктура есть 2️⃣ hash функций отсутствуют 3️⃣ dispatch chain просто делает jmp rax 4️⃣ check функция ret Эксперимент: xfg_demo.exe Бинарник: /guard:cf /guard:xfg Результат XFG НЕ включён во время выполнения Доказательства XFG pointer __guard_xfg_dispatch_icall_fptr указывает на: CFG validator hash функций 0x00000000 R10 XFG требует: mov r10, hash но его нет в коде. Итог ПроверкаСтатусCFGработаетXFGне работаетФинальный вывод XFG не включён публично в Windows 11 24H2 Microsoft: ✔ компилятор принимает /guard:xfg ✔ линкер добавляет метаданные ✔ dumpbin показывает XFG но ❌ loader не включает XFG ❌ компилятор не вставляет hash ❌ код не содержит R10 ❌ runtime проверка не выполняется Последствия 1️⃣ XFG — внутренняя технология Microsoft 2️⃣ сторонние разработчики не могут её использовать 3️⃣ CFG остаётся единственной доступной защитой Что было бы при настоящем XFG mov rax, [ptr] mov r10, hash call _guard_xfg_dispatch_icall Research continue