Protección de flujo de control para la seguridad de la plataforma
Qué es la protección de flujo de control?
Protección de flujo de control (CFG) es una característica de seguridad de plataforma muy optimizada que se creó para luchar contra vulnerabilidades de corrupción de memoria. Al imponer restricciones estrictas sobre el lugar desde el que una aplicación puede ejecutar código, hace mucho más difícil que las vulnerabilidades ejecuten código arbitrario a través de vulnerabilidades como los desbordamientos de búfer. CFG amplía las tecnologías de mitigación de vulnerabilidades anteriores, como /GS (Comprobación de seguridad del búfer), Prevención de ejecución de datos (DEP) y Selección aleatoria del diseño del espacio de direcciones (ASLR).
El uso de CFG puede ayudar a:
- Evitar daños en la memoria y ataques de ransomware.
- Restringir las funcionalidades del servidor solo a lo que se necesita en un momento dado para reducir la superficie expuesta a ataques.
- Hacer que sea más difícil aprovechar el código arbitrario a través de vulnerabilidades como desbordamientos de búfer.
Esta característica está disponible en Microsoft Visual Studio y se ejecuta en versiones compatibles con CFG de Windows; Windows 10 y Windows 11 en el cliente y Windows Server 2019 y versiones posteriores en el servidor.
Se recomienda encarecidamente a los desarrolladores habilitar CFG para sus aplicaciones. No tiene que habilitar CFG para cada parte de su código, ya que una mezcla de código habilitado para CFG y no habilitado para CFG se ejecutará sin problemas. Sin embargo, no habilitar CFG para todo el código puede abrir brechas en la protección. Además, el código habilitado para CFG funciona correctamente en las versiones no compatibles con CFG de Windows y, por lo tanto, es totalmente compatible con ellas.
¿Cómo puedo habilitar CFG?
En la mayoría de los casos, no es necesario cambiar el código fuente. Lo único que tiene que hacer es agregar una opción al proyecto de Visual Studio y el compilador y enlazador habilitarán CFG.
El método más sencillo es navegar a Proyecto | Propiedades | Propiedades de configuración | C/C++ | Generación de código y elegir Sí (/guard:cf) para Protección de flujo de control.
Como alternativa, agregue /guard:cf a Proyecto | Propiedades | Propiedades de configuración | C/C++ | Línea de comandos | Opciones adicionales (para el compilador) y /guard:cf para Proyecto | Propiedades | Propiedades de configuración | Enlazador | Línea de comandos | Opciones adicionales (para el enlazador).
Consulte /guard (Habilitar Protección de flujo de control) para obtener información adicional.
Si va a compilar el proyecto desde la línea de comandos, puede agregar las mismas opciones. Por ejemplo, si va a compilar un proyecto denominado test.cpp, use cl /guard:cf test.cpp /link /guard:cf.
También tiene la opción de controlar dinámicamente el conjunto de direcciones de destino de icall que CFG considera válidas mediante SetProcessValidCallTargets de la API de administración de memoria. La misma API se puede usar para especificar si las páginas no son válidas o son destinos válidos para CFG. Las funciones VirtualProtect y VirtualAlloc tratarán de forma predeterminada una región especificada de páginas ejecutables y confirmadas como destinos de llamadas indirectas válidos. Es posible invalidar este comportamiento, como al implementar un compilador Just-In-Time, especificando PAGE_TARGETS_INVALID al llamar a VirtualAlloc o PAGE_TARGETS_NO_UPDATE al llamar a VirtualProtect como se detalla en Constantes de protección de memoria.
¿Cómo se indica que un binario está bajo protección de flujo de control?
Ejecute la herramienta dumpbin (incluida en la instalación de Visual Studio) desde el símbolo del sistema de Visual Studio con las opciones /headers y /loadconfig: dumpbin /headers /loadconfig test.exe. La salida de un binario en CFG debe mostrar que los valores de encabezado incluyen "Guard" y que los valores de configuración de carga incluyen "CF Instrumented" y "FID table present".
¿Cómo funciona realmente CFG?
Las vulnerabilidades de software suelen aprovecharse al proporcionar datos poco probables, inusuales o extremos a un programa en ejecución. Por ejemplo, un atacante puede aprovechar una vulnerabilidad de desbordamiento de búfer proporcionando más entradas a un programa de lo esperado y saturar el área reservada por el programa para contener una respuesta. Esto podría dañar la memoria adyacente que podría contener un puntero de función. Cuando el programa llama a través de esta función, puede saltar a una ubicación no deseada especificada por el atacante.
Sin embargo, una potente combinación de compatibilidad en tiempo de compilación y ejecución de CFG implementa la integridad del flujo de control que restringe estrechamente dónde se pueden ejecutar las instrucciones de llamada indirecta.
El compilador hace lo siguiente:
- Agrega comprobaciones de seguridad ligeras al código compilado.
- Identifica el conjunto de funciones de la aplicación que son destinos válidos para llamadas indirectas.
La compatibilidad en tiempo de ejecución, proporcionada por el kernel de Windows:
- Mantiene de forma eficaz el estado que identifica los destinos de llamadas indirectas válidas.
- Implementa la lógica que comprueba que un destino de llamada indirecta es válido.
A modo de ejemplo:
Cuando la comprobación de CFG falla en tiempo de ejecución, Windows termina inmediatamente el programa, interrumpiendo cualquier ataque que intente llamar indirectamente a una dirección no válida.