sábado, 22 de marzo de 2008

Protección del código con las defensas de Visual C++ (Parte 1 de 2)

Pinceladas sobre seguridad
Protección del código con las defensas de Visual C++
Michael Howard


Gran parte del código está escrito en C y C++ y desgraciadamente una gran parte de este código tiene vulnerabilidades de seguridad que muchos desarrolladores no conocen.
Los programas escritos en cualquier lenguaje pueden tener vulnerabilidades que dejan a sus usuarios abiertos a un ataque, pero los lenguajes C y C++ ocupan un lugar especial en la historia de Internet porque muchas de sus vulnerabilidades de seguridad se deben al mismo motivo por el que estos dos lenguajes de programación son tan populares: el acceso sin restricciones al hardware del equipo y el rendimiento que esto conlleva. Cuando lee acerca de la seguridad y surge C o C++, las palabras "búfer" y "saturación" suelen aparecer muy cerca la una de la otra, ya que los búferes son un ejemplo muy común del acceso directo a la memoria. Este tipo de acceso directo es muy eficaz, pero también muy, muy peligroso.

Existen varias razones por las que se producen saturaciones de búfer en la producción de código C y C++. La primera ya la he mencionado: estos lenguajes proporcionan acceso directo a la memoria vulnerable. Segundo, los desarrolladores cometen errores. Y tercero, normalmente los compiladores no ofrecen defensas. Es posible proporcionar un remedio para el primer problema, pero entonces C y C++ comenzarían a transformarse en lenguajes distintos.
El que los desarrolladores cometan errores se puede resolver en parte mediante métodos educativos, pero realmente no veo ningún paso adelante de las instituciones pertinentes. Seguro que hay un lugar en la industria para la educación en materia de seguridad, pero todos somos parte de la solución o parte del problema y me encantaría que las universidades dedicaran más esfuerzos a la educación de sus alumnos en cuestiones de seguridad de software. Probablemente se estará preguntando, "¿por qué las instituciones educativas no intentan formar a los alumnos en esta materia tan crucial?" Francamente, no tengo la menor idea. Es algo deprimente, la verdad.
Finalmente, aún con una educación excelente, algunos problemas de seguridad son tan complejos que incluso los ingenieros más punteros no podrían resolverlos en su totalidad. Nosotros, los humanos, no somos perfectos.
La necesidad de crear más defensas en los compiladores es algo en lo que el equipo de Microsoft Visual C++ ha estado trabajando y, aunque lentamente, se han conseguido algunas mejoras con el paso de los años y la ayuda de nuestro equipo de la seguridad. Esta columna resume algunas de las defensas frente a la saturación del búfer disponibles en Visual C++® 2005 y versiones superiores. Tenga en cuenta que algunos compiladores ofrecen defensas, pero Visual C++ tiene dos ventajas muy importantes frente a los compiladores estilo gcc. Primero, todas estas defensas están en el conjunto de herramientas de forma predeterminada; no hay necesidad de descargar ningún complemento extraño. Segundo, las opciones son fáciles de usar.
Si ningún orden particular, las defensas ofrecidas por el conjunto de herramientas de Visual C++ son:
  • Detección de la saturación del búfer basada en pilas (/GS)
  • Controlador seguro de excepciones (/SafeSEH)
  • Compatibilidad de prevención de ejecución de datos (DEP) (/NXCompat)
  • Selección aleatoria de imágenes (/DynamicBase)
  • Uso automático de llamadas de función más seguras
  • C++ operator::new

Antes de tratar cada una de estas opciones en detalle quiero dejar claro que estas defensas no compensan la existencia de un código inseguro. Debe esforzarse siempre para crear el código más seguro posible, y si no sabe cómo hacerlo, entonces vaya corriendo a leer alguno de los excelentes libros que tratan sobre esta materia.

Pila típica comparada con una compilada con /GS (Hacer clic en la imagen para ampliarla)

También quiero señalar que todo esto es parte de los requisitos del Ciclo de vida de desarrollo de seguridad (SDL, Security Development Lifecycle) en Microsoft, lo que significa que los códigos C y C++ deben usar estas opciones para salir al mercado. Hay excepciones puntuales, pero se producen con poca frecuencia, así que no voy a hablar de ellas aquí.
Finalmente, debe tener presente un punto importante: Es posible, dependiendo del código en cuestión, sortear estas defensas tan elaboradas. Cuantas más defensas use el código, más difícil será evitarlas, pero ninguna defensa es perfecta. Todas ellas son obstáculos para reducir la posibilidad de éxito de una vulnerabilidad. ¡Queda advertido! La única variante es el uso de llamadas de función más seguras, que son verdaderas defensas y pueden eliminar vulnerabilidades. Observemos cada defensa en detalle.


Detección de la saturación del búfer basada en pilas (/GS)
La detección de la saturación del búfer basada en pilas es la defensa más antigua y más conocida disponible en Visual C++. El objetivo del marcador del compilador /GS es sencillo: reducir la oportunidad de que el código malintencionado se ejecute correctamente. La opción /GS está activada de forma predeterminada en Visual C++ 2003 y versiones posteriores, y detecta ciertas clases de desbordamiento de pila en tiempo de ejecución. Se trata de hacer esto incluyendo un número aleatorio en la pila de una función justo antes de la dirección de devolución de la pila. Cuando vuelva la función, el código de epílogo de función comprueba este valor para asegurarse de que no ha cambiado. Si la cookie, como así se llama, ha cambiado, la ejecución se detiene.
El código de prólogo de la función que establece la cookie se parece a esto:
sub esp, 8
mov eax, DWORD PTR ___security_cookie
xor eax, esp
mov DWORD PTR __$ArrayPad$[esp+8], eax
mov eax, DWORD PTR _input$[esp+4]


Y aquí se muestra el código de epílogo de función que comprueba la cookie:
mov ecx, DWORD PTR __$ArrayPad$[esp+12]
add esp, 4
xor ecx, esp
call @__security_check_cookie@4
add esp, 8


Visual C++ 2005 también mueve los datos alrededor de la pila para que sea más difícil dañar los datos. Uno de los ejemplos sería el desplazamiento de los búferes a una memoria superior que los no búfer. Por ejemplo, este paso puede ayudar a proteger los punteros de función que residen en la pila. De manera alternativa, al desplazar los argumentos de puntero y búfer a una memoria inferior en el tiempo de ejecución se mitigan varios ataques de saturación del búfer. Consulte el diagrama para comparar una pila típica con una pila /GS.
La opción de compilador /GS no se aplica en ninguna de las situaciones siguientes:
--Las funciones no contienen un búfer.
--Las optimizaciones no están habilitadas.
--Las funciones se han definido para tener una lista de argumentos variable.
--Las funciones se han marcado con la palabra clave sencilla (C++).
--Las funciones contienen código ensamblador en línea en la primera instrucción.
--El búfer no es del tipo de 8 bytes y tiene un tamaño inferior a 4 bytes.
Se ha agregado una opción a Visual C++ 2005 SP1 para hacer la heurística de /GS mucho más agresiva y proteger así más funciones. Microsoft agregó esta opción porque se publicó un pequeño número de boletines de seguridad con código que tenía saturación del búfer basada en pilas y el código, a pesar de estar compilado con /GS, no estaba protegido por una cookie. Esta nueva opción aumenta el número de funciones protegidas de manera significativa.
Use esta opción colocando la línea siguiente en módulos en los que desee protección adicional, como el código que administra datos de Internet:
#pragma strict_gs_check(on)

Esta pragma es sólo un ejemplo de cómo Microsoft evoluciona constantemente la capacidad /GS. La versión original en Visual C++ 2003 era bastante sencilla, después se actualizó en Visual C++ 2005 SP1 y una vez más en Visual C++ 2008, ya que tuvimos conocimiento de nuevos ataques y maneras para sortear los ataques existentes. En nuestro análisis, hemos descubierto que /GS no causa problemas de compatibilidad ni de rendimiento.

Via msdn2.microsoft.com

No hay comentarios: