Исследование Control Flow Guard (CFG)

Тема в разделе "WASM.WIN32", создана пользователем galenkane, 13 мар 2026.

  1. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    Исследование 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Преобразует адрес в позицию бита bitmap
    ScpCfgValidateUserCallTargetntdll!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Бинарник инструментирован CFG
    IMAGE_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Таблица longjmp
    IMAGE_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

    Требования

    1. IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT
    2. IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED
    3. GuardAddressTakenIatEntryTable
    4. IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION
    Защита Long Jump

    CFG усиливает защиту setjmp/longjmp.
    Механизм:
    1. longjmp реализуется через RtlUnwindEx
    2. используется статус STATUS_LONGJUMP
    3. цели проверяются через GuardLongJumpTargetTable
    Соображения безопасности

    Выравнивание функций

    Функции должны быть выровнены на:
    16 байт

    Если нет — вся 16-байтовая область считается валидной.
    Это снижает безопасность.
    Снижение поверхности атаки

    Export Suppression

    Ограничивает допустимые цели.
    Защита IAT

    Import Address Table → read-only

    Ограничения

    CFG:
    1. не защищает от всех hijack-атак
    2. JIT-код требует ручного управления
    3. misaligned функции снижают точность защиты
    4. возможны обходы через невыравненные цели
    Ключевое открытие (WinDbg)

    Когда бинарник компилируется с:
    /guard:cf

    загрузчик Windows выполняет:
    1. выделяет CFG bitmap
    2. заменяет NOP-заглушки на реальные валидаторы
    3. включает принудительное выполнение 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
    Где хранится hash8 байт перед функцией
    Используемые регистры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