Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se proporcionan detalles adicionales sobre los metadatos de Control Flow Guard (CFG) en imágenes PE. Se supone que está familiarizado con la estructura de los metadatos de CFG en imágenes PE. Consulte el tema formato PE de para obtener documentación de alto nivel sobre los metadatos de CFG en imágenes pe.
Las funciones que son destinos de llamadas indirectas válidas se enumeran en el guardCFFunctionTable adjunto al directorio de configuración de carga, a veces se denomina la tabla GFIDS para mayor brevedad. Se trata de una lista ordenada de direcciones virtuales relativas (RVA) que contienen información sobre los destinos de llamadas cfG válidos. Estos son, por lo general, símbolos de función tomadas por direcciones. Una imagen que quiera aplicar CFG debe enumerar todos los símbolos de función tomadas por la dirección en su tabla de GFIDS. La lista de RVA de la tabla de GFIDS debe ordenarse correctamente o no se cargará la imagen. La tabla gfIDS de es una matriz de 4 + n bytes, donde n se proporciona ((GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT). "GuardFlags" es el campo GuardFlags del directorio de configuración de carga. Esto permite adjuntar metadatos adicionales a los destinos de llamadas de CFG en el futuro. Los únicos metadatos definidos actualmente son un campo opcional de marcas adicionales de 1 byte ("marcas GFIDS") que está asociado a cada entrada de GFIDS si algún destino de llamada tiene metadatos. Hay dos marcas de GFIDS definidas:
Para la compatibilidad futura, las herramientas no deben establecer marcas de GFIDS que aún no se han definido y no deben incluir GFIDS adicionales bytes de metadatos adicionales más allá del 1 byte definido actualmente, ya que los significados de otras marcas o metadatos adicionales aún no están asignados. Puede encontrar ejemplos de imágenes que incluyen bytes de metadatos adicionales al volcar el GFIDS tabla de archivos binarios, como Ntdll.dll en una versión moderna del sistema operativo Windows 10.
Las herramientas solo deben declarar símbolos de función como destinos de llamada válidos, lo que puede merecer una consideración adicional para el código del ensamblador en el que se puedan abordar las etiquetas. Por motivos históricos, el código ensamblador puede basarse en etiquetas de código distintas de PROC o .altentry, ya que el enlazador no se convierte en destinos de llamada cfG.
Además, por motivos históricos, el código puede declarar deliberadamente código como datos para evitar la inclusión en la tabla GFIDS. Por ejemplo, un archivo de objeto puede implementar un símbolo como código, mientras que otro puede declararlo como datos para tomar la dirección del símbolo sin generar un registro de destino CFG válido. Por motivos de compatibilidad, se recomienda que los conjuntos de herramientas admitan esta práctica.
Las imágenes que admiten CFG y que quieran o realicen comprobaciones de CFG deben establecer los bits de IMAGE_GUARD_CF_INSTRUMENTED y IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT GuardFlags, y deben establecer el bit de IMAGE_DLLCHARACTERISTICS_GUARD_CF DllCharacteristics en los encabezados de imagen.
El directorio de configuración de carga anuncia dos punteros de función: GuardCFCheckFunctionPointer y GuardCFDispatchFunctionPointer (el último solo se admite para determinadas arquitecturas como AMD64). Estos punteros de función deben apuntar a la memoria de solo lectura para que la seguridad cfG sea eficaz; El cargador dll del sistema operativo volverá a proteger la memoria transitoriamente durante la carga de imágenes para almacenar los punteros de función. El uso típico podría ser combinarlos en la misma sección que contiene la Tabla de direcciones de importación (IAT). GuardCFCheckFunctionPointer proporciona la dirección de un símbolo proporcionado por el sistema operativo que se puede llamar con un puntero de función en el primer registro de argumentos enteros (ECX en x86), que devolverá correctamente o anulará el proceso si el destino de llamada no es un destino CFG válido. GuardCFDispatchFunctionPointer proporciona la dirección de un símbolo proporcionado por el sistema operativo que toma un destino de llamada en el registro RAX y realiza una comprobación combinada de CFG y una llamada optimizada de rama final al destino de llamada (los registros R10/R11 están reservados para su uso por parte de GuardCFDispatchFunctionPointer y los registros de argumentos enteros están reservados para su uso por el destino de llamada final). La dirección predeterminada de los símbolos CFG de una imagen debe apuntar a una función que simplemente devuelve (GuardCFCheckFunctionPointer) o que devuelve un símbolo suprimido por protección (o se omite completamente del símbolo de tabla GFIDS) que ejecuta una instrucción "jmp rax". Para AMD64 GuardCFDispatchFunctionPointer, cuando se carga una imagen en un sistema operativo compatible con CFG y CFG está habilitado, el cargador dll del sistema operativo instalará los punteros de función adecuados, lo que proporciona compatibilidad con versiones anteriores. Una imagen puede proporcionar 0 para GuardCFDispatchFunctionPointer en la configuración de carga si no pretende usar la instalación de distribución de CFG. Esto debe hacerse para arquitecturas que no sean AMD64 para una compatibilidad futura, en caso de que estas arquitecturas admitan finalmente el mecanismo de distribución de CFG de algún modo. Tenga en cuenta que Windows 8.1 AMD64 no admitía el envío de CFG y dejaría el puntero de función predeterminado en su lugar para GuardCFDispatchFunctionPointer. La distribución de CFG solo se admite en sistemas operativos Windows 10 y versiones posteriores.
Es posible que cfG del modo de usuario solo se aplique para las imágenes marcadas como aleatorias de diseño de espacio de direcciones (ASLR) compatibles (especificadas por la opción /DYNAMICBASE con el enlazador de Microsoft). Esto se debe a cómo el sistema operativo controla internamente CFG donde básicamente se conecta a la infraestructura de ASLR. En general, los usuarios de CFG deben habilitar ASLR para sus imágenes como primer paso. Las herramientas no deben suponer que el sistema operativo siempre omitirá CFG sin el conjunto de ASLR, pero normalmente debe establecer ambos al mismo tiempo.
Directivas del compilador
Los destinos de llamada se pueden marcar como suprimidos explícitamente con el modificador __declspec(guard(suppress)) o con la directiva /guardym:symname,S linker (por ejemplo, para el código asm). Esto hace que el destino de llamada se incluya en la tabla de GFIDS, pero marcado de tal manera que el sistema operativo tratará el destino de llamada como no válido. Algunos escenarios que no son de producción, como con cierta instrumentación de comprobador de aplicaciones habilitada en algunos sistemas operativos anteriores, pueden permitir que los destinos de llamada suprimidos se traten como válidos, pero en general no se espera que estos escenarios sean escenarios de producción. Esta directiva es útil para anotar funciones "peligrosas" que no se deben considerar como destinos de llamada válidos, aunque la regla de CFG normal las incluya.
El código puede indicar que no se quieren comprobaciones de CFG con el modificador __declspec(guard(nocf)). Esto dirige al compilador a que no inserte ninguna comprobación de CFG para toda la función. El compilador debe tener cuidado de propagar esta directiva a cualquier código aportado por una función insertada marcada como no deseada. Este enfoque se usa normalmente solo con moderación en situaciones específicas en las que el programador ha insertado manualmente la protección "equivalente a CFG". El programador sabe que llama a través de alguna tabla de funciones de solo lectura cuya dirección se obtiene a través de referencias de memoria de solo lectura y para las que el índice se enmascara al límite de la tabla de funciones. Este enfoque también se puede aplicar a funciones contenedoras pequeñas que no están insertadas y que no hacen nada más que realizar una llamada a través de un puntero de función. Dado que el uso incorrecto de esta directiva puede poner en peligro la seguridad de CFG, el programador debe tener mucho cuidado con la directiva . Normalmente, este uso se limita a funciones muy pequeñas que solo llaman a una función.
Control de importación
Las llamadas a través de IAT no deben usar la protección CFG. El IAT es de solo lectura en imágenes modernas (suponiendo que el IAT se declara en los encabezados PE en cuyo caso debe estar en sus propias páginas). El IAT se puede usar para llegar a funciones que se suprimen, por lo que se trata de un requisito de corrección. La protección de memoria de solo lectura a través de IAT sustituye a la de CFG, ya que el enlace de destino de llamada es inmutable después de que se resuelvan los acoplamientos de importación de imágenes y la resolución de enlace esté específica.
Carga de retraso protegida: las llamadas a través de la IAT de carga retrasada no deben usar la protección CFG, por las mismas razones que la IAT estándar. La IAT de carga retrasada debe estar en su propia sección y la imagen debe establecer el bit IMAGE_GUARD_CF_PROTECT_DELAYLOAD_IAT GuardFlags. Esto indica que el cargador dll del sistema operativo debe cambiar las protecciones de la IAT de carga retrasada durante la resolución de exportación si se usa la compatibilidad con la carga retrasada del sistema operativo nativa de Windows 8 y sistemas operativos posteriores. El cargador DLL del sistema operativo administra la sincronización de este paso si la compatibilidad con la carga de retraso del sistema operativo nativo está en uso (por ejemplo, ResolveDelayLoadedAPI), por lo que ningún otro componente debe volver a proteger las páginas que abarcan la IAT de carga de retraso declarada. Para la compatibilidad con versiones anteriores con sistemas operativos anteriores a CFG, las herramientas pueden habilitar la opción de mover el IAT de carga retrasada a su propia sección (canónicamente ".didat"), protegido como lectura y escritura en los encabezados de imagen y, además, establecer la marca IMAGE_GUARD_CF_DELAYLOAD_IAT_IN_ITS_OWN_SECTION. Esta configuración hará que los cargadores DLL del sistema operativo compatibles con CFG vuelvan a proteger toda la sección que contiene la tabla de IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT para leer solo la memoria durante la carga de imágenes. Es posible que no se requiera la opción para colocar el IAT de carga retrasada en su propia sección si no le importa ejecutar una imagen en sistemas operativos que predescriben la compatibilidad con CFG, pero las herramientas deben tomar esa decisión en función de la compatibilidad mínima del sistema operativo que necesita una imagen.
Si una imagen no usa la compatibilidad nativa con la carga de retraso del sistema operativo, todavía puede establecer los bits GuardFlags relacionados con la carga de retraso protegida. En esta configuración, el cargador del sistema operativo solo proporcionará compatibilidad para proteger la IAT de carga retrasada como de solo lectura en tiempo de ejecución si es compatible con la plataforma y se convierte en responsabilidad de los códigos auxiliares de resolución de carga de retraso interno de la imagen para sincronizar y administrar la protección de la IAT de carga retrasada. Siempre que la tabla de configuración de carga se almacene en memoria de solo lectura (lo que se recomienda), la presencia o ausencia del bit IAT de carga de retraso protegido en el campo GuardFlags de la imagen podría ser útil como sugerencia interna para los códigos auxiliares de resolución de carga de retraso interno de la imagen para indicar si debe proteger la IAT de carga retrasada.
Se recomienda habilitar la carga retrasada protegida de forma predeterminada si CFG está habilitado. Las imágenes que se ejecutan en versiones anteriores del sistema operativo y que usan la compatibilidad nativa con la carga de retraso del sistema operativo, como se indica, pueden usar el IAT de carga retrasada en su propia sección compatible con versiones anteriores. Esto se opone a marcar la IAT de carga retrasada como de solo lectura y combinarla con otra sección, lo que interrumpiría en las cargas de retraso protegidas del sistema operativo anteriores y que proporcionan compatibilidad nativa con la resolución de carga de retraso. Todas las versiones de Windows 10 y las primeras compilaciones de Windows 8.1/Windows Server 2012 R2 compatibles con CFG (lo que significa la actualización de noviembre de 2014) presentan compatibilidad con la carga de retraso protegida en el sistema operativo.
Alineación de funciones
- Las funciones que se abordan y, por lo tanto, se incluyen en la tabla de GFIDS deben alinearse de 16 bytes, si es posible. Esto puede no ser siempre posible. Por ejemplo, para las funciones que no son COMDAT que forman parte de los archivos de objeto ensamblados como una unidad por herramientas no compatibles con CFG, que algunos ensambladores pueden producir, el usuario de la herramienta que produjo los archivos debe establecer correctamente la alineación. Las herramientas pueden optar por emitir una advertencia de diagnóstico en esta situación para que el usuario pueda tomar las medidas correctivas adecuadas. La razón de esto es que CFG marca los destinos de llamada como válidos o no válidos en límites de 16 bytes para la eficacia de las comprobaciones rápidas de CFG. Si una función no está alineada con 16 bytes, la ranura completa de 16 bytes debe marcarse como válida, lo que no es tan seguro, ya que puede llamar desalineado al código que no está al principio de una función. Este escenario es compatible con la facilidad de interoperabilidad cuando primero se incorpora CFG para un proyecto. Las imágenes que no son compatibles con CFG se marcan de forma similar como válidas para cualquier alineación de destino de llamada para la compatibilidad. Como antes, tener destinos de llamada mal alineados reduce las ventajas de seguridad de CFG, por lo que las herramientas deben alinearse automáticamente con un límite de 16 bytes para cualquier cosa de la GFIDS tabla cuando se desea CFG para una imagen. Los símbolos que no están en la tabla de GFIDS no necesitan tener alineaciones concretas para CFG.
Eliminación de exportación
La supresión de exportación de CFG (CFG ES) es un modo opcional que permite a un proceso indicar que los destinos de llamada que solo eran válidos porque eran símbolos dllexport y que GetProcAddress aún no han resuelto dinámicamente, se considerarán como no válidos para fines de CFG. Esto reduce el área expuesta de CFG de las exportaciones dll del sistema. La supresión de exportación implica comunicar los destinos de llamada dllexport "suprimidos" aptos marcandolos con las marcas de gfIDS IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED. Los símbolos dllexport y el punto de entrada de imagen PE deben considerarse implícitamente la dirección tomada por las herramientas con el fin de generar la tabla de GFIDS de. Si un símbolo de exportación está alineado con 16 bytes y se toma la dirección sin ningún otro motivo que ser un archivo dllexport, se puede marcar con la marca GFIDS GFIDS en la tabla de funciones. Los destinos de llamada que no están alineados con 16 bytes no deben marcarse con la marca de GFIDS IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED y no se pueden restringir a que solo se habiliten dinámicamente como destinos de llamada válidos en la hora getProcAddress.
Una imagen que admite CFG ES incluye una GuardAddressTakenIatEntryTable cuyo recuento proporciona GuardAddressTakenIatEntryCount como parte de su directorio de configuración de carga. Esta tabla tiene el mismo formato estructural que la tabla de GFIDS. Usa el mismo mecanismo guardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK para codificar bytes de metadatos adicionales opcionales en la tabla IAT tomada, aunque todos los bytes de metadatos deben ser cero para la tabla IAT tomada de la dirección y están reservados. La tabla IAT tomada indica una matriz ordenada de RVAs de importación thunks que tienen la dirección de símbolo importada como destino de llamada tomado. Esta construcción admite símbolos tomados de direcciones que existen en un módulo remoto y que son dllexports, con CFG ES en uso. Un ejemplo de este tipo de construcción de código sería:
mov rcx, [__imp_DefWindowProc] call foo ; where foo takes the actual address of DefWindowProc.
Todas estas direcciones tomadas importan thunks deben enumerarse para que el cargador del sistema operativo pueda encontrarlos y hacer que los destinos de llamada adecuados sean válidos al cargar una imagen y ajustar sus importaciones. La tabla y el recuento pueden ser 0 si no hay matones de importación que se hayan tomado.
Un módulo establece el bit IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT GuardFlags para indicar que ha enumerado todas las direcciones tomadas en su tabla de IAT tomadas y que todas las exportaciones que son aptas para CFG ES se marcan con la marca de IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS. Tenga en cuenta que puede haber cero tales thunks y que también puede haber cero tales símbolos dllexport. Si no se mantiene la dirección tomada, la tabla IAT puede ser un problema de corrección, ya que es posible que algunos destinos de llamada no se hagan válidos cuando deben estar en tiempo de carga de DLL.
Un módulo establece el bit IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION GuardFlags para indicar que quiere habilitar CFG ES para el proceso. En la práctica, esto solo es significativo para los EXE en la actualidad. Un proceso que habilita CFG ES no debe cargar archivos DLL no compilados con CFG ES o los errores en tiempo de ejecución pueden producirse debido a símbolos IAT no designados. La compatibilidad con la habilitación de CFG ES debe ser una opción de participación independiente de habilitar CFG. Proporcionar metadatos de CFG ES es seguro y recomendado de forma predeterminada con CFG, aunque los conjuntos de herramientas deben tener cuidado para asegurarse de que producen metadatos correctos. Si no es así, es posible que sus imágenes generadas no se ejecuten correctamente en un proceso de CFG ES. Esta compatibilidad debe probarse exhaustivamente en un proceso de prueba que aplique CFG ES. Los archivos DLL del sistema operativo integrados admiten metadatos de CFG ES para las versiones modernas del sistema operativo Windows 10 que comprenden CFG ES. Las versiones del sistema operativo anteriores a esta compatibilidad no comprenden CFG ES en absoluto y omitirán las directivas relacionadas con CFG ES en la imagen. Estas imágenes siguen siendo compatibles con versiones anteriores del sistema operativo.
La compatibilidad con CFG ES es opcional desde la perspectiva de un conjunto de herramientas, pero se recomienda que los conjuntos de herramientas incluyan al menos compatibilidad para enumerar información suficiente para que las imágenes se ejecuten en un proceso que desee CFG ES. Como se mencionó, es fundamental que el conjunto de herramientas admita pruebas exhaustivas para asegurarse de que es compatible con CFG ES, ya que la mayoría de los procesos aún no habilitan CFG ES.
Control de excepciones y desenredado
Los controladores específicos del lenguaje, como __C_specific_handler, designados por la información del controlador de excepciones en un registro .pdata, no deben marcarse como destinos de llamada válidos en la tabla de GFIDS. En su lugar, se buscan cruzando la memoria de solo lectura. Del mismo modo, el controlador específico del lenguaje C de Microsoft usa búsquedas de memoria de solo lectura para buscar funclets para controladores de excepciones y, por tanto, no declara sus funclets como destinos de llamada válidos en la tabla de GFIDS.
Control de saltos largos (para destinos que no son x86 como AMD64): conjuntos de herramientas que se compilan con CFG y admiten setjmp()/longjmp() deben implementar el salto largo como "salto largo seguro" que interopera con control de excepciones estructurado (SEH). Esto significa que el salto largo se implementa como una llamada a RtlUnwindEx con STATUS_LONGJUMP como código de estado en el registro de excepción proporcionado y un _JUMP_BUFFER estándar al que apunta ExceptionInformation[0]. El destino de desenredado de salto debe ser targetIp del desenredado. El búfer de salto representa el contexto de registro restaurado por el sistema operativo después de que se haya completado el salto largo. RtlUnwind(Ex) cuando se llama con STATUS_LONGJUMP tiene un significado especial único para CFG. Destino de salto largo (_JUMP_BUFFER. Desgar o _JUMP_BUFFER. Lr en ARM64) se busca en la lista de módulos cargados mantenida por el sistema operativo en memoria de solo lectura. Si el módulo contenedor para el destino de salto (el "módulo de destino") tiene la marca de IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT establecida en su campo GuardFlags, el directorio de configuración de carga tiene un botón GuardLongJumpTargetTable con un recuento de elementos especificado por el campo GuardLongJumpTargetCount de configuración de carga. Esta tabla tiene el mismo formato estructural que el tabla de GFIDS y usa el mismo mecanismo guardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK para codificar bytes de metadatos adicionales opcionales en la tabla de saltos largos. Todos los bytes de metadatos deben ser cero para la tabla de saltos largos y están reservados.
La tabla de saltos largos representa una matriz ordenada de RVAs que son destinos de salto largo válidos. Si un módulo de destino de salto largo establece IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT en su campo GuardFlags, todos los destinos de salto largo deben enumerarse en LongJumpTargetTable. Incluso si un módulo tiene cero destinos de salto largo, todavía debe establecer la marca de IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT si el conjunto de herramientas admite la protección de saltos largos para CFG. Esto significa explícitamente que la imagen no tiene destinos de salto largo y no es una imagen antigua que el sistema operativo debe asumir podría tener destinos de salto largo válidos en ubicaciones sin marcar para las que no puede realizar la comprobación de destino de salto largo.
Se recomienda habilitar la protección de saltos largos de forma predeterminada si se admite CFG. Esta es la disposición de los compiladores de Microsoft. Los sistemas operativos que no entienden la protección de saltos largos (versiones anteriores a Windows 10 o anteriores de Windows 10) no realizarán comprobaciones de protección de saltos largos e ignorarán los metadatos de protección de salto largo, por lo que la protección de saltos largos es compatible con versiones anteriores del sistema operativo.
En el caso de las imágenes en modo kernel, la tabla de destino de salto largo de protección no debe incluirse en una sección descartable. La tabla de destino de salto largo de protección siempre debe almacenarse en memoria de solo lectura para que sus propiedades de seguridad sean eficaces.
Información de COFF
Hay marcas de archivo de objeto para declarar si un archivo de objeto se ajusta a CFG o no. Un archivo de objeto que se ajusta a CFG enumerará los destinos de llamada válidos que genera, explícitamente, así como cualquier dirección tomada de metadatos de IAT. Un archivo de objeto que no se ajusta a CFG debe tener destinos de llamada inferidos examinando las reubicaciones coff del archivo obj para encontrar reubicaciones que apuntan al inicio de un símbolo de función. Esto puede sobreaplicaroximato destinos de llamadas CFG válidos, por lo que es deseable que las herramientas marquen sus archivos obj que son compatibles con CFG e incluyan los metadatos del archivo obj cfG si se compilan con CFG.
Hay marcas de archivo de objeto para declarar destinos de salto largo largo para el salto largo protegido CFG que se debe rellenar para el modo de compilación cfG.