Integridad del flujo de control

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 6 de mayo de 2022; las comprobaciones requieren 3 ediciones .

La integridad del flujo de control ( CFI ) es un nombre general para las técnicas de seguridad informática destinadas a restringir las posibles rutas de ejecución del programa dentro de un gráfico de flujo de control predicho para aumentar su seguridad [1] . CFI hace que sea más difícil para un atacante tomar el control de la ejecución de un programa al hacer imposible que algunas formas reutilicen partes ya existentes del código de máquina. Técnicas similares incluyen la separación de puntero de código (CPS) y la integridad de puntero de código (CPI) [2] [3] .

La compatibilidad con CFI está presente en los compiladores Clang [4] y GCC [5] , así como en Control Flow Guard [6] y Return Flow Guard [7] de Microsoft y Reuse Attack Protector [8] del PaX Team.

Historia

La invención de formas de protegerse contra la ejecución de código arbitrario, como la prevención de ejecución de datos y NX-bit , ha llevado a la aparición de nuevos métodos que le permiten obtener control sobre el programa (por ejemplo, programación orientada al retorno ) [ 8] . En 2003, el equipo de PaX publicó un documento que describe posibles situaciones que conducen a la piratería del programa e ideas para protegerse contra ellas [8] [9] . En 2005, un grupo de investigadores de Microsoft formalizó estas ideas y acuñó el término Integridad del flujo de control para referirse a los métodos de protección contra cambios en el flujo de control original de un programa. Además de esto, los autores propusieron un método de instrumentación de código máquina ya compilado [1] .

Posteriormente, los investigadores, basados ​​en la idea de CFI, propusieron muchas formas diferentes de aumentar la resistencia del programa a los ataques. Los enfoques descritos no han sido ampliamente adoptados por razones que incluyen grandes retrasos en los programas o la necesidad de información adicional (por ejemplo, obtenida a través de perfiles ) [10] .

En 2014, un equipo de investigadores de Google publicó un artículo que analizaba la implementación de CFI para compiladores industriales GCC y LLVM para instrumentar programas C++. El soporte oficial de CFI se agregó en 2014 en GCC 4.9.0 [5] [11] y en 2015 en Clang 3.7 [12] [13] . Microsoft lanzó Control Flow Guard en 2014 para Windows 8.1 , agregando soporte del sistema operativo a Visual Studio 2015 [6] .

Descripción

Si hay saltos indirectos en el código del programa , es potencialmente posible transferir el control a cualquier dirección donde se pueda ubicar el comando (por ejemplo, en x86 será cualquier dirección posible, ya que la longitud mínima del comando es de un byte [14] ). Si un atacante puede modificar de alguna manera el valor por el cual se transfiere el control al ejecutar una instrucción de salto, entonces puede reutilizar el código del programa existente para sus propias necesidades.

En los programas reales, los saltos no locales suelen conducir al comienzo de las funciones (por ejemplo, si se utiliza una instrucción de llamada a procedimiento) oa la instrucción que sigue a la instrucción de llamada (retorno de procedimiento). El primer tipo de transiciones es una transición directa (del inglés forward-edge ), ya que se denotará mediante un arco directo en el gráfico de flujo de control. El segundo tipo se llama transición hacia atrás (ing. back-edge ), por analogía con el primero: el arco correspondiente a la transición será inverso [15] .

Transiciones directas

Para saltos directos, el número de direcciones posibles a las que se puede transferir el control corresponderá al número de funciones en el programa. Además, cuando se tiene en cuenta el sistema de tipos y la semántica del lenguaje de programación en el que está escrito el código fuente, son posibles restricciones adicionales [16] . Por ejemplo, en C++ , en un programa correcto , un puntero de función utilizado en una llamada indirecta debe contener la dirección de una función con el mismo tipo que el propio puntero [17] .

Una forma de implementar la integridad del flujo de control para saltos directos es que puede analizar el programa y determinar el conjunto de direcciones legales para diferentes instrucciones de bifurcación [1] . Para construir un conjunto de este tipo, el análisis de código estático generalmente se usa en algún nivel de abstracción (a nivel de código fuente , representación interna del analizador o código de máquina [1] [10] ). Luego, utilizando la información recibida, se inserta un código junto a las instrucciones de la rama indirecta para verificar si la dirección recibida en tiempo de ejecución coincide con la calculada estáticamente. En caso de divergencia, el programa generalmente falla, aunque las implementaciones le permiten personalizar el comportamiento en caso de una violación del flujo de control previsto [18] [19] . Por lo tanto, el gráfico de flujo de control se limita solo a aquellos bordes (llamadas a funciones) y vértices (puntos de entrada a funciones) [1] [16] [20] que se evalúan durante el análisis estático, por lo que al intentar modificar el puntero utilizado para salto indirecto , el atacante fallará.

Este método le permite evitar la programación orientada a saltos [21] y la programación orientada a llamadas [22] , ya que esta última utiliza activamente saltos indirectos directos.

Transiciones inversas

Para las transiciones hacia atrás, son posibles varios enfoques para la implementación de CFI [8] .

El primer enfoque se basa en los mismos supuestos que CFI para saltos directos, es decir, la capacidad de calcular direcciones de retorno a partir de una función [23] .

El segundo enfoque es tratar la dirección del remitente específicamente. Además de simplemente guardarlo en la pila , también se guarda, posiblemente con algunas modificaciones, en un lugar especialmente asignado para él (por ejemplo, en uno de los registros del procesador). Además, antes de la instrucción de devolución, se agrega un código que restaura la dirección de devolución y la compara con la que está en la pila [8] .

El tercer enfoque requiere soporte adicional del hardware. Junto con CFI, se utiliza una pila de sombra , un área de memoria especial inaccesible para un atacante, en la que se almacenan las direcciones de retorno cuando se llaman funciones [24] .

Al implementar esquemas CFI para saltos hacia atrás, es posible evitar un ataque de retorno a la biblioteca y una programación orientada al retorno basada en cambiar la dirección de retorno en la pila [ 23] .

Ejemplos

En esta sección, se considerarán ejemplos de implementaciones de integridad de flujo de control.

Comprobación de llamada de función indirecta de Clang

La comprobación de llamadas de funciones indirectas (IFCC) incluye comprobaciones de saltos indirectos en un programa, con la excepción de algunos saltos "especiales", como las llamadas de funciones virtuales. Al construir un conjunto de direcciones a las que puede ocurrir una transición, se tiene en cuenta el tipo de función. Gracias a esto, es posible evitar no solo el uso de valores incorrectos que no apunten al comienzo de la función, sino también la conversión incorrecta de tipos en el código fuente. Para habilitar las comprobaciones en el compilador, hay una opción -fsanitize=cfi-icall[4] .

// clang-ifcc.c #incluye < stdio.h> int suma ( int x , int y ) { devuelve x + y _ } int doble ( int x ) { devuelve x + x ; } void call_fn ( int ( * fn )( int )) { printf ( "Valor del resultado: %d \n " , ( * fn )( 42 )); } void erase_type ( void * fn ) { // El comportamiento no está definido si el tipo dinámico de fn no es el mismo que int (*)(int). llamada_fn ( fn ); } int principal () { // Al llamar a erase_type, se pierde la información de tipo estático. borrar_tipo ( suma ); devolver 0 ; }

Un programa sin controles se compila sin ningún mensaje de error y se ejecuta con un resultado indefinido que varía de una ejecución a otra:

$ clang -Wall -Wextra clang-ifcc.c $ ./a.fuera Valor del resultado: 1388327490

Compilado con las siguientes opciones, obtiene un programa que aborta cuando se llama a call_fn.

$ clang -flto -fvisibility=oculto -fsanitize=cfi -fno-sanitize-trap=todo clang-ifcc.c $ ./a.fuera clang-ifcc.c:12:32: error de tiempo de ejecución: la verificación de integridad del flujo de control para el tipo 'int (int)' falló durante la llamada de función indirecta (./a.out+0x427a20): nota: (desconocido) definido aquí

Clang Forward-Edge CFI para llamadas virtuales

Este método tiene como objetivo comprobar la integridad de las llamadas virtuales en el lenguaje C++. Para cada jerarquía de clases que contiene funciones virtuales , se crean mapas de bits que muestran qué funciones se pueden llamar para cada tipo estático. Si durante la ejecución en el programa, la tabla de funciones virtuales de cualquier objeto se corrompe (por ejemplo, el tipo incorrecto reduce la jerarquía o simplemente la corrupción de la memoria por parte de un atacante), entonces el tipo dinámico del objeto no coincidirá con ninguno de los predichos estáticamente. [10] [25] .

// llamadas-virtuales.cpp #incluir <cstdio > estructura B { vacío virtual foo () = 0 ; virtual ~ B () {} }; estructura D : público B { anular foo () anular { printf ( "Funcion derecha \n " ); } }; Estructura mala : pública B { anular foo () anular { printf ( "Funcion incorrecta \n " ); } }; int principal () { mal mal ; // El estándar C++ permite realizar conversiones de esta manera: B & b = static_cast < B &> ( bad ); // Derivado1 -> Base -> Derivado2. D & normal = static_cast < D &> ( b ); // Como resultado, el tipo dinámico del objeto es normal normal . foo (); // será malo y se llamará a la función incorrecta. devolver 0 ; }

Después de compilar sin controles habilitados:

$ clang++ -std=c++11 virtual-calls.cpp $ ./a.fuera función incorrecta

En el programa, en lugar de llamar a la implementación de la fooclase desde el . Este problema se detectará si compila el programa con : DfooBad-fsanitize=cfi-vcall

$ clang++ -std=c++11 -Wall -flto -fvisibility=oculto -fsanitize=cfi-vcall -fno-sanitize-trap=todas las llamadas virtuales.cpp $ ./a.fuera virtual-calls.cpp: 24: 3: error de tiempo de ejecución: la verificación de integridad del flujo de control para el tipo 'D' falló durante la llamada virtual (dirección de vtable 0x000000431ce0) 0x000000431ce0: nota: vtable es del tipo 'Malo' 00 00 00 00 30 a2 42 00 00 00 00 00 e0 a1 42 00 00 00 00 00 60 a2 42 00 00 00 00 00 00 00 00 00 ^

Notas

  1. ↑ 1 2 3 4 5 Martín Abadi, Mihai Budiu, Úlfar Erlingsson, Jay Ligatti. Integridad del flujo de control  // Actas de la 12.ª Conferencia ACM sobre seguridad informática y de las comunicaciones. - Nueva York, NY, EE. UU.: ACM, 2005. - S. 340-353 . — ISBN 1595932267 . -doi : 10.1145/ 1102120.1102165 .
  2. Volodymyr Kuznetsov, László Szekeres, Mathias Payer, George Candea, R. Sekar. Integridad de puntero de código  // Actas de la 11.ª Conferencia USENIX sobre diseño e implementación de sistemas operativos. - Berkeley, CA, EE. UU.: Asociación USENIX, 2014. - Pág. 147-163 . — ISBN 9781931971164 .
  3. ↑ Sobre las diferencias entre las propiedades CFI, CPS y CPI  . nebelwelt.net. Consultado el 22 de diciembre de 2017. Archivado desde el original el 22 de diciembre de 2017.
  4. ↑ 1 2 Integridad del flujo de control: documentación de Clang 5 . releases.llvm.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 23 de diciembre de 2017.
  5. ↑ 1 2 vtv-CCG Wiki . gcc.gnu.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 11 de julio de 2017.
  6. 1 2 Protección de flujo de control (Windows  ) . msdn.microsoft.com. Consultado el 22 de diciembre de 2017. Archivado desde el original el 22 de diciembre de 2017.
  7. ↑ Guardia de flujo de retorno - Laboratorio Xuanwu  de Tencent . xlab.tencent.com. Consultado el 22 de diciembre de 2017. Archivado desde el original el 23 de diciembre de 2017.
  8. ↑ 1 2 3 4 5 grseguridad  . _ www.grsecurity.net. Consultado el 22 de diciembre de 2017. Archivado desde el original el 17 de febrero de 2018.
  9. [1] Archivado el 5 de agosto de 2017 en Wayback Machine PaX future.
  10. ↑ 1 2 3 Caroline Tice, Tom Roeder, Peter Collingbourne, Stephen Checkoway, Úlfar Erlingsson. Aplicación de la integridad del flujo de control de vanguardia en GCC y LLVM  // Actas de la 23.ª conferencia USENIX sobre el simposio de seguridad. - Berkeley, CA, EE. UU.: Asociación USENIX, 2014. - S. 941-955 . — ISBN 9781931971157 .
  11. GCC 4.9 Release Series - Proyecto GNU - Free Software Foundation (FSF  ) . gcc.gnu.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 15 de enero de 2018.
  12. Notas de la versión de Clang 3.7 — Documentación de Clang 3.7 . releases.llvm.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 26 de noviembre de 2017.
  13. Lanzamientos de LLVM . releases.llvm.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 15 de diciembre de 2017.
  14. Manuales para desarrolladores de software de las arquitecturas Intel® 64 e IA-32 |  Software Intel® . software.intel.com. Consultado el 22 de diciembre de 2017. Archivado desde el original el 25 de diciembre de 2017.
  15. Seguridad - WebAssembly . webassembly.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 23 de diciembre de 2017.
  16. ↑ 1 2 Aho, Alfred W.; Seti, Ravi; Ullman, Jeffrey D. Compiladores: principios, tecnologías, herramientas, 2.ª ed . —Williams. - 2008. - S.  1062 -1066. - ISBN 978-5-8459-1349-4 .
  17. ISO/IEC 14882:2014 - Tecnología de la información - Lenguajes de programación - C++ . —ISO . _ - 2014. - Pág. 105. Copia de archivo fechada el 29 de abril de 2016 en la Wayback Machine .
  18. Verificación de Vtable - Guía del usuario . docs.google.com. Consultado el 22 de diciembre de 2017. Archivado desde el original el 12 de junio de 2019.
  19. Integridad del flujo de control: documentación de Clang 5 . releases.llvm.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 23 de diciembre de 2017.
  20. Muchnick, Steven S. Diseño e implementación avanzados del compilador . - Morgan Kaufmann Publishers , 1997. - S.  609 -618. - ISBN 1-55860-320-4 .
  21. Tyler Bletsch, Xuxian Jiang, Vince W. Freeh, Zhenkai Liang. Programación orientada a saltos: una nueva clase de ataque de reutilización de código  // Actas del sexto simposio de ACM sobre seguridad de la información, la informática y las comunicaciones. - Nueva York, NY, EE. UU.: ACM, 2011. - Pág. 30-40 . — ISBN 9781450305648 . -doi : 10.1145/ 1966913.1966919 .
  22. Ali Akbar Sadeghi, Salman Niksefat, Maryam Rostamipour. Programación orientada a llamadas puras (PCOP): encadenar los dispositivos mediante instrucciones de llamada  //  Journal of Computer Virology and Hacking Techniques. — 2017-05-15. - Pág. 1-18 . — ISSN 2263-8733 . -doi : 10.1007/ s11416-017-0299-1 . Archivado desde el original el 22 de diciembre de 2017.
  23. ↑ 1 2 RAP: RIP ROP - Protector de ataque de reutilización (enlace descendente) . Equipo PaX . Consultado el 22 de diciembre de 2017. Archivado desde el original el 20 de mayo de 2020. 
  24. Vista previa de la tecnología de cumplimiento de flujo de control . Zona de desarrolladores de Intel . Consultado el 22 de diciembre de 2017. Archivado desde el original el 14 de agosto de 2017.
  25. Documentación de diseño de integridad de flujo de control: documentación de Clang 5 . releases.llvm.org. Consultado el 22 de diciembre de 2017. Archivado desde el original el 23 de diciembre de 2017.

Literatura

Libros Artículos
  • Martín Abadi, Mihai Budiu, Úlfar Erlingsson, Jay Ligatti. Integridad del flujo de control  // Actas de la 12.ª Conferencia ACM sobre seguridad informática y de las comunicaciones. - Nueva York, NY, EE. UU.: ACM, 2005. - S. 340-353 . — ISBN 1595932267 . -doi : 10.1145/ 1102120.1102165 .
  • Caroline Tice, Tom Roeder, Peter Collingbourne, Stephen Checkoway, Ulfar Erlingsson. Aplicación de la integridad del flujo de control de vanguardia en GCC y LLVM  // Actas de la 23.ª conferencia USENIX sobre el simposio de seguridad. - Berkeley, CA, EE. UU.: Asociación USENIX, 2014. - S. 941-955 . — ISBN 9781931971157 .

Enlaces