Метаданные PE
В этой статье приведены дополнительные сведения о метаданных Control Flow Guard (CFG) в изображениях PE. Предполагается знакомство со структурой метаданных CFG в изображениях PE. См. раздел формата PE для высокоуровневой документации по метаданным CFG в изображениях PE.
Функции, которые являются допустимыми целевыми объектами косвенного вызова, перечислены в GuardCFFunctionTable , присоединенном к каталогу конфигурации нагрузки, иногда называют таблицей GFIDS для краткости. Это отсортированный список относительных виртуальных адресов (RVA), содержащий сведения о допустимых целевых объектах вызовов CFG. Это, как правило, символы функции, принятые адресом. Изображение, которое требует применения CFG, должно перечислить все символы функции, принятые адресом в таблице GFIDS . Список RVA в таблице GFIDS должен быть отсортирован должным образом или изображение не будет загружено. Таблица GFIDS представляет собой массив из 4 + n байтов, где n дано ((GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT). GuardFlags — это поле GuardFlags каталога конфигурации загрузки. Это позволяет присоединять дополнительные метаданные к целевым объектам вызовов CFG в будущем. Единственным заданным в настоящее время метаданным является необязательное поле дополнительных флагов ("флаги GFIDS"), присоединенное к каждой записи GFIDS , если какие-либо целевые объекты вызова имеют метаданные. Определены два флага GFIDS :
IMAGE_GUARD_FLAG_FID_SUPPRESSED/0x1 Целевой объект вызова явно подавляется (не рассматривайте его как допустимое для целей CFG) IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED/0x2 Целевой объект вызова отключается. Дополнительные сведения см. в разделе "Подавление экспорта" Для обеспечения будущей совместимости средства не должны задавать флаги GFIDS , которые еще не определены и не должны включать дополнительные байты дополнительных метаданных GFIDS за пределами 1-байтов, определенных в настоящее время, так как значения для других флагов или дополнительных метаданных еще не назначены. Примеры изображений, включающих дополнительные байты метаданных, можно найти путем дампа таблицы GFIDS двоичных файлов, таких как Ntdll.dll в современной версии ОС Windows 10.
Средства должны объявлять только символы функций в качестве допустимых целевых объектов вызова, которые могут заслуживают дополнительного рассмотрения кода сборщика, где метки могут быть приняты. По историческим причинам код сборщика может полагаться на метки кода, отличные от PROC или ALTENTRY, так как не преобразуется в целевые объекты вызовов CFG компоновщиком.
Кроме того, по историческим причинам код может намеренно объявить код как данные, чтобы избежать включения в таблицу GFIDS . Например, один файл объекта может реализовать символ в виде кода, а другой может объявить его как данные, чтобы получить адрес символа без создания допустимой целевой записи CFG. Для обеспечения совместимости рекомендуется, чтобы наборы инструментов поддерживали эту практику.
Изображения, поддерживающие CFG и которые хотят или выполняют проверка CFG, должны задавать биты IMAGE_GUARD_CF_INSTRUMENTED и IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT GuardFlags, а также задать бит IMAGE_DLLCHARACTERISTICS_GUARD_CF DllCharacteristics в заголовках изображений.
Каталог конфигурации нагрузки объявляет два указателя функции: GuardCFCheckFunctionPointer и GuardCFDispatchFunctionPointer (последний поддерживается только для определенных архитектур, таких как AMD64). Эти указатели функций должны указывать на то, чтобы безопасность CFG была эффективной только для чтения памяти; Загрузчик DLL операционной системы повторно защищает память во время загрузки образа для хранения указателей функции. Обычное использование может быть слиянием этих данных в тот же раздел, который содержит таблицу адресов импорта (IAT). GuardCFCheckFunctionPointer предоставляет адрес предоставленного ос-загрузчика символа, который можно вызвать с указателем функции в первом регистре целочисленного аргумента (ECX на x86), который вернет успех или прервет процесс, если целевой объект вызова не является допустимым целевым объектом CFG. GuardCFDispatchFunctionPointer предоставляет адрес предоставленного символа, который принимает целевой объект вызова в регистре RAX и выполняет объединенные проверка CFG и хвостовую ветвь оптимизированного вызова к целевому объекту вызова (регистры R10/R11 зарезервированы для использования с помощью GuardCFDispatchFunctionPointer и целочисленных регистров аргументов зарезервированы для использования конечным целевым объектом вызова). Адрес по умолчанию символов CFG на изображении должен указывать на функцию, которая просто возвращает (GuardCFCheckFunctionPointer) или возвращает символ, подавляемый охранником (или предпочтительнее полностью опущен из символа таблицы GFIDS ), которая выполняет инструкцию jmp rax. Для AMD64 GuardCFDispatchFunctionPointer, когда образ загружается в операционной системе с поддержкой CFG, и CFG включен, загрузчик DLL OS установит соответствующие указатели функций, которые позволяют обеспечить обратную совместимость. Изображение может предоставить значение 0 для GuardCFDispatchFunctionPointer в конфигурации загрузки, если он не планирует использовать средство отправки CFG. Это необходимо сделать для архитектур, отличных от AMD64, для обеспечения будущей совместимости, если эти архитектуры в конечном итоге поддерживают механизм диспетчеризации CFG в какой-то форме. Обратите внимание, что Windows 8.1 AMD64 не поддерживает отправку CFG и оставляет указатель функции по умолчанию для GuardCFDispatchFunctionPointer. Диспетчеризация CFG поддерживается только в операционных системах Windows 10 и более поздних версий.
CfG в пользовательском режиме может применяться только для изображений, помеченных как выборка макета адресного пространства (ASLR), совместимой (задается параметром /DYNAMICBASE с компоновщиком Майкрософт). Это связано с тем, как ОПЕРАЦИОННая система внутренне обрабатывает CFG, где она по сути подключена к инфраструктуре ASLR. Как правило, пользователи CFG должны включить ASLR для своих образов в качестве первого шага. Средства не должны предполагать, что ОС всегда будет игнорировать CFG без набора ASLR, но обычно следует задавать оба одновременно.
Целевые объекты вызовов можно пометить как явно подавляемые модификатором __declspec(guard(suppress)) или директивой /guardymym:symname,S linker (например, для кода asm). Это приводит к тому, что целевой объект вызова будет включен в таблицу GFIDS , но помечается таким образом, что ОС будет рассматривать целевой объект вызова как недопустимый. Некоторые непроизводственные сценарии, такие как с определенным инструментированием проверки приложений, включенными в некоторых старых операционных системах, могут включать в себя недопустимые целевые объекты вызовов, но в целом эти сценарии не должны быть рабочими. Эта директива полезна для аннотирования "опасных" функций, которые не должны рассматриваться как допустимые целевые объекты вызова, даже если обычное правило CFG будет включать их.
Код может указывать, что проверка CFG не требуется с модификатором __declspec(guard(nocf). Это позволяет компилятору не вставлять проверка CFG для всей функции. Компилятор должен заботиться о распространении этой директивы на любой код, внесенный встроенной функцией, которая помечена как не требующая проверка CFG. Этот подход обычно используется только в определенных ситуациях, когда программист вручную вставил защиту "CFG-эквивалент". Программист знает, что они обращаются через некоторую таблицу функций только для чтения, адрес которого получен только через ссылки на память чтения и для которого индекс маскируется до предела таблицы функций. Этот подход также может применяться к небольшим функциям-оболочкам, которые не встраиваются, и это не делает ничего больше, чем вызов через указатель функции. Так как неправильное использование этой директивы может скомпрометировать безопасность CFG, программист должен быть очень осторожным с помощью директивы. Как правило, это использование ограничено очень небольшими функциями, которые вызывают только одну функцию.
Вызовы через IAT не должны использовать защиту CFG. IAT считывается только в современных изображениях (при условии, что IAT объявлен в заголовках PE, в этом случае он должен находиться на собственных страницах). IAT можно использовать для достижения функций, которые являются защищенными, поэтому это требование правильности. Только чтение защиты памяти с помощью IAT заменяет cfg, так как целевая привязка вызова неизменяема после разрешения оснастки импорта образа, а разрешение привязки — точное определение.
Защищенная загрузка задержки. Вызовы через IAT задержки не должны использовать защиту CFG по тем же причинам, что и стандартный IAT. IAT задержки должен находиться в собственном разделе, и изображение должно задать бит IMAGE_GUARD_CF_PROTECT_DELAYLOAD_IAT GuardFlags. Это означает, что загрузчик БИБЛИОТЕКи DLL операционной системы должен изменять защиту от задержки загрузки IAT во время разрешения экспорта при использовании отложенной загрузки операционной системы, встроенной в операционные системы Windows 8 и более поздних версий. Синхронизация этого шага управляется загрузчиком библиотеки DLL операционной системы, если поддержка загрузки задержки в собственной операционной системе используется (например, ResolveDelayLoadedAPI), поэтому другой компонент не должен повторно защитить страницы, охватывающие объявленную задержку загрузки IAT. Для обратной совместимости с более старыми операционными системами CFG средства могут включить возможность перемещения IAT задержки загрузки в свой собственный раздел (канонически .didat), защищенный как чтение и запись в заголовках изображений, а также задать флаг IMAGE_GUARD_CF_DELAYLOAD_IAT_IN_ITS_OWN_SECTION. Этот параметр приведет к тому, что загрузчики DLL операционной системы с поддержкой CFG будут повторно защитить весь раздел, содержащий таблицу IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT для чтения только памяти во время загрузки образа. Возможность размещения IAT задержки в собственном разделе может не потребоваться, если вы не заботитесь о запуске образа в операционных системах, которые предопределили поддержку CFG, но средства должны принять это решение на основе минимальной поддержки операционной системы, необходимой для образа.
Если образ не использует встроенную поддержку загрузки задержки операционной системы, он по-прежнему может задать биты защищенной задержки, связанные с GuardFlags. В этой конфигурации загрузчик операционной системы просто обеспечивает поддержку защиты от задержки загрузки IAT, как чтение только во время выполнения, если она поддерживается платформой, и она становится ответственностью за заглушки разрешения нагрузки от задержки образа для синхронизации и управления защитой отложенной нагрузки IAT. При условии, что таблица конфигурации загрузки хранится только в памяти только для чтения (рекомендуется), наличие или отсутствие защищенной загрузки нагрузки IAT в поле GuardFlags образа может оказаться полезным в качестве внутреннего указания на заглушки разрешения нагрузки задержки образа, чтобы указать, следует ли защитить отложенную нагрузку IAT.
Рекомендуется включить защищенную загрузку задержки по умолчанию, если включена функция CFG. Образы, работающие в старых версиях операционной системы и использующие встроенную поддержку загрузки задержки операционной системы, как отмечалось, могут использовать IAT задержки в собственном разделе для обеспечения обратной совместимости. Это отличается от маркировки задержки загрузки IAT как только для чтения и объединения ее с другим разделом, что приведет к разрыву на старых операционных системах, которые не понимают защищенные нагрузки задержки и которые обеспечивают поддержку разрешения нагрузки собственной задержки. Все выпуски Windows 10 и первые сборки Windows 8.1/Windows Server 2012 R2, поддерживающие CFG (то есть обновление 2014 ноября 2014 г.) обеспечивают поддержку защищенной задержки загрузки в операционной системе.
- Если это возможно, функции, принятые и включенные в таблицу GFIDS , должны быть выровнены по 16 байтам. Это может быть не всегда возможно. Например, для функций, отличных от COMDAT, которые являются частью файлов объектов, собранных вместе в одном модуле с помощью средств, не поддерживающих CFG, которые некоторые сборщики могут производить, пользователь средства, создавшего файлы, должен соответствующим образом установить выравнивание. Средства могут выбрать для выдачи предупреждения диагностики в этой ситуации, чтобы пользователь мог предпринять соответствующие действия по исправлению. Причиной этого является то, что CFG помечает целевые объекты вызовов как допустимые или недопустимые на 16-байтовых границах для повышения эффективности быстрых проверка CFG. Если функция не выровнена по 16 байтам, весь 16-байтовый слот должен быть помечен как допустимый, что не так безопасно, так как можно вызвать неправильное преобразование в код, который не находится в самом начале функции. Этот сценарий поддерживается для удобства взаимодействия при первом запуске CFG для проекта. Образы, не поддерживаемые CFG, помечаются как допустимые для любого выравнивания целевого объекта вызова для обеспечения совместимости. Как и раньше, при неправильном изменении целевых объектов вызовов снижается преимущества безопасности CFG, поэтому средства должны автоматически выравнивать границу 16-байтов для всего, что находится в таблице GFIDS , когда CFG требуется для изображения. Символы, которые не находятся в таблице GFIDS , не должны иметь определенных выравниваний для CFG.
Подавление экспорта CFG (CFG ES) — это необязательный режим, позволяющий указать, что целевые объекты вызова, которые были допустимыми только из-за того, что они были символами dllexport, и которые еще не были динамически разрешены GetProcAddress, будут считаться недопустимыми для целей CFG. Это уменьшает область поверхности CFG из экспорта системной библиотеки DLL. Подавление экспорта включает обмен данными с соответствующими целевыми объектами вызова dllexport "экспорт отключается", помечая их флагами IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS . Символы Dllexport и точка входа изображения PE должны неявно рассматриваться средствами для создания таблицы GFIDS . Если символ экспорта выровнен по 16 байтам и он принимается не по какой-либо причине, кроме библиотеки dllexport, его можно пометить флагом GFIDS экспорта в таблице функций. Целевые объекты вызова, которые не соответствуют 16 байтам, не должны быть помечены флагом GFIDS IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED и не могут быть ограничены только динамически включенными в качестве допустимых целевых объектов вызова во время GetProcAddress.
Изображение, поддерживающее CFG ES, включает в себя GuardAddressTakenIatEntryTable, число которого предоставляется GuardAddressTakenIatEntryCount в рамках своего каталога конфигурации загрузки. Эта таблица структурирована так же, как и таблица GFIDS . Он использует тот же механизм GuardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK для кодирования дополнительных необязательных байт метаданных в таблице IAT, хотя все байты метаданных должны быть ноль для адреса, взятой таблицы IAT, и зарезервированы. Адрес, принятый в таблице IAT, указывает отсортированный массив RVA импорта thunks, которые импортируются в качестве целевого объекта вызова для адреса символов. Эта конструкция поддерживает символы, которые существуют в удаленном модуле, и которые являются dllexports, с использованием CFG ES. Примером такой конструкции кода будет:
mov rcx, [__imp_DefWindowProc] call foo ; where foo takes the actual address of DefWindowProc.
Все такие адреса, принятые при импорте, необходимо перечислить, чтобы загрузчик операционной системы смог найти их и сделать соответствующие целевые объекты вызова допустимыми при загрузке образа и привязке его импорта. Таблица и число могут быть 0, если не было импорта thunks, которые были приняты.
Модуль задает бит IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT GuardFlags, чтобы указать, что он перечислил все адреса, принятые в таблице IAT, и все экспорты, которые являются допустимыми CFG ES, помечены флагом IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS . Обратите внимание, что может быть ноль таких thunks и что также может быть ноль таких символов dllexport. Сбой в обслуживании таблицы IAT может быть проблемой правильности, так как некоторые целевые объекты вызова могут быть недопустимыми, если они должны находиться во время загрузки DLL.
Модуль задает бит IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION GuardFlags, чтобы указать, что он хочет включить CFG ES для процесса. На практике это важно только для EXEs сегодня. Процесс, позволяющий CFG ES, не должен загружать библиотеки DLL, не созданные с помощью CFG ES или сбоев среды выполнения, могут возникать из-за неспроектированного адреса, взятого символами IAT. Поддержка включения CFG ES должна быть отдельным вариантом выбора от включения CFG. Предоставление метаданных CFG ES безопасно и рекомендуется по умолчанию с помощью CFG, хотя наборы инструментов должны заботиться о том, чтобы они могли создавать правильные метаданные. В противном случае созданные образы могут работать неправильно в процессе CFG ES. Такая поддержка должна быть тщательно проверена в тестовом процессе, который применяет CFG ES. Встроенные системные библиотеки DLL поддерживают метаданные CFG ES для современных версий операционной системы Windows 10, которые понимают CFG ES. Версии операционной системы до этой поддержки вообще не понимают CFG ES и будут игнорировать все связанные директивы CFG ES на изображении. Такие образы по-прежнему совместимы с более старыми версиями операционной системы.
Поддержка CFG ES является необязательной с точки зрения набора инструментов, но рекомендуется, чтобы наборы инструментов по крайней мере включали поддержку перечисления достаточной информации для выполнения изображений в процессе, который требует CFG ES. Как упоминание, важно тщательно проверить поддержку набора инструментов, чтобы обеспечить совместимость с CFG ES, так как большинство процессов еще не поддерживают CFG ES.
Специальные обработчики языка, такие как __C_specific_handler, как указано сведения обработчика исключений в регистрации PDATA, не должны быть помечены как допустимые целевые объекты вызова в таблице GFIDS . Вместо этого они ищутся путем обхода только памяти для чтения. Аналогичным образом, специальный обработчик языка Microsoft C использует поиск только в памяти для поиска funclets для обработчиков исключений и поэтому не объявляет свои воронки в качестве допустимых целевых объектов вызова в таблице GFIDS .
Обработка прыжков (для целевых объектов, отличных от x86, таких как AMD64): наборы инструментов, компилируемые с помощью CFG и поддерживающие setjmp()/longjmp() должны реализовать длинный прыжок как "безопасный длинный прыжок", который взаимодействует с структурированной обработкой исключений (SEH). Это означает, что длинный переход реализуется как вызов RtlUnwindEx с STATUS_LONGJUMP в качестве кода состояния в предоставленной записи исключений и стандартной _JUMP_BUFFER, на которую указывает ExceptionInformation[0]. Целевой объект очистки прыжка должен быть targetIp для очистки. Буфер перехода представляет контекст регистра, который восстанавливается операционной системой после завершения длительного перехода. RtlUnwind(Ex) при вызове с STATUS_LONGJUMP имеет особое значение, уникальное для CFG. Цель длинного прыжка (_JUMP_BUFFER. Rip или _JUMP_BUFFER. Lr в ARM64) просматривается в загруженном списке модулей, поддерживаемых операционной системой в памяти только для чтения. Если в поле GuardFlags установлен флаг IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT для целевого целевого объекта (целевой модуль), то в каталоге конфигурации загрузки есть guardLongJumpTargetTable, указанное в поле load Configuration GuardLongJumpTargetCount. Эта таблица структурирована так же, как и таблица GFIDS и использует тот же механизм GuardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK для кодирования дополнительных байт метаданных в таблице длинного прыжка. Все байты метаданных должны быть нулевыми для таблицы прыжков в длину и зарезервированы.
Таблица длинного прыжка представляет отсортированный массив RVA, которые являются допустимыми целевыми объектами длинного прыжка. Если целевой модуль длинного прыжка задает IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT в поле GuardFlags, то все целевые объекты для прыжков в длину должны быть перечислены в LongJumpTargetTable. Даже если модуль имеет нулевое целевое значение для прыжков в длину, он по-прежнему должен задать флаг IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT, если набор инструментов поддерживает затверждение длинного прыжка для CFG. Это явным образом означает, что образ не имеет целевых объектов длинного прыжка и не является старым изображением, которое операционная система должна предположить, что может иметь допустимые целевые объекты прыжка в немаркированных местах, для которых он не может выполнять целевой объект прыжка длинного прыжка проверка.
При поддержке CFG рекомендуется включить многопрыговую ужесточение по умолчанию. Это ликвидация компиляторов Майкрософт. Операционные системы, которые не понимают закалку в длину (до Windows 10 или более ранних версий Windows 10) не будут выполнять многолетнее ужесточение проверка и игнорировать любые метаданные закалки в длину, поэтому долгое ужесточение переходов обратно совместимо со старыми выпусками операционной системы.
Для образов режима ядра таблица целевого объекта для перехода в длину охранника не должна быть включена в раздел dis карта able. Целевая таблица для прыжков в длину охранника всегда должна храниться в памяти только для чтения, чтобы его свойства безопасности были эффективными.
Существуют метки файлов объектов, чтобы объявить, соответствует ли файл объекта CFG или нет. Файл объекта, соответствующий CFG, будет перечислять допустимые целевые объекты вызова, которые он создает, явным образом, а также любые адреса, принятые метаданными IAT. Объектный файл, который не соответствует CFG, должен иметь целевые объекты вызова, выводимые путем изучения перемещения obj-файла для поиска перемещений, указывающих на начало символа функции. Это может перенацелить допустимые целевые объекты вызовов CFG, поэтому желательно, чтобы средства помечали свои obj-файлы, поддерживающие CFG, и включают метаданные OBJ-файла CFG при компиляции с помощью CFG.
Существуют метки файлов объектов для объявления целевых объектов длинного прыжка для затверденного длинного прыжка CFG, который должен быть заполнен для режима компиляции CFG.