sábado, 22 de marzo de 2008

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

Controlador seguro de excepciones (/SafeSEH)
El gusano CodeRed que afectó a Internet Information Server (IIS) 4.0 fue causado por una saturación del búfer basado en pilas. Curiosamente, /GS no habría detectado el problema explotado por el gusano porque el código no saturó la dirección de devolución de la función afectada, sino que dañó un controlador de excepciones de la pila. Este es un buen ejemplo de por qué usted debe centrarse constantemente en escribir código seguro y no confiar únicamente en estos tipos de defensas basadas en el compilador.
Un controlador de excepciones es un código que se ejecuta cuando ocurre una condición excepcional, como la división entre cero. La dirección del controlador se conserva en el marco de pila de la función y, por lo tanto, está sujeto a daños. El vinculador que se incluye con Visual Studio® 2003 y sus versiones posteriores incluye una opción que permite almacenar la lista de controladores de excepciones válidos en el encabezado PE de la imagen en el tiempo de compilación. Cuando se genera una excepción en tiempo de ejecución, el sistema operativo comprueba el encabezado de la imagen para determinar si la dirección de controlador de excepciones es correcta. Si no lo es, la aplicación finaliza. Esto habría evitado la acción de CodeRed si hubiera existido la tecnología en el momento en que se vinculó el código. La opción /SafeSEH no causa ningún tipo de descenso del rendimiento, salvo que cuando se genera una excepción, siempre debe vincularse a esta opción.

Compatibilidad de DEP (/NXCompat)
Prácticamente todas las CPU que se fabrican hoy en día son compatibles con la capacidad de no ejecución (NX), lo que significa que la CPU no ejecutará páginas sin código. Piense por un momento en las implicaciones de esto: casi todas las vulnerabilidades de saturación del búfer son errores de datos. Entonces, el atacante inserta los datos en el proceso mediante la saturación del búfer y continúa la ejecución dentro del búfer de datos malintencionados. ¿Por qué ejecuta la CPU los datos?
Al vincular con la opción de /NXCompat su archivo ejecutable estará protegido por la capacidad de no ejecución de la CPU. Según nuestra experiencia, el equipo de seguridad de Microsoft ha tenido muy pocos problemas de compatibilidad debidos a esta opción y, hasta ahora, no ha habido degradación del rendimiento.
Windows Vista SP1 agrega también una nueva API que habilita DEP para su proceso de ejecución y que una vez establecido, no se puede desactivar:
SetProcessDEPPolicy(PROCESS_DEP_ENABLE);

Selección aleatoria de imágenes (/DynamicBase)
Windows Vista y Windows Server® 2008 son compatibles con la selección aleatoria de imágenes. Esto significa que cuando se arranca el sistema, éste distribuye el orden aleatorio de las imágenes de sistema operativo en la memoria. El propósito de esta característica es simplemente eliminar parte de la capacidad de previsión de los atacantes. Esto se conoce también como selección aleatoria de la distribución del espacio de direcciones (ASLR, Address Space Layout Randomization). Tenga en cuenta que para que la ASLR sea útil, debe tener también DEP habilitado.
De manera predeterminada, Windows® sólo se permitirá mover los componentes del sistema. Si desea que su imagen circule por el sistema operativo (altamente recomendado), debe vincular con la opción /DynamicBase. Esta opción está disponible en Visual Studio 2005 SP1 y los conjuntos de herramientas posteriores. También se produce un efecto secundario interesante al vincular con /DynamicBase, que consiste en que el sistema operativo distribuye el orden aleatorio de la pila, lo que a su vez reduce la capacidad de previsión y dificulta que los atacantes pongan en peligro un sistema. Tenga en cuenta que también se distribuye el orden aleatorio del montón en Windows Vista y Windows Server 2008. No obstante, esto ocurre de forma predeterminada, por lo que no es necesario compilar ni vincular con ninguna opción especial.

Llamadas de función más seguras
Observe las siguientes líneas de código:
void func(char *p) { char d[20]; strcpy(d,p); // etc
}

Suponiendo que *p contiene los datos que no son de confianza, este código representa una vulnerabilidad de seguridad. El aspecto más negativo de este código es que el compilador podría haber trasladado la llamada a strcpy a una llamada de función más segura que enlazara la operación de copia al tamaño del búfer de destino. ¿Por qué? Porque el tamaño del búfer es estático y conocido a la hora de compilar
Con Visual C++ puede agregar la siguiente línea a su archivo de encabezado stdafx.h:
#define _CRT_SECURE_COPP_OVERLOAD_STANDARD_NAMES 1

El compilador entonces seguirá adelante y emitirá el siguiente código desde la función inicial no segura:
void func(char *p) {
char d[20];
strcpy_s(d,__countof(d), p);
// etc}

Como puede ver, ahora el código es seguro, y el desarrollador sólo ha agregado un #define. Esta es una de mis adiciones favoritas a Visual C++, porque cerca del 50 por ciento de las llamadas de función no seguras pueden ascender a llamadas más seguras automáticamente.


C++ Operator::new
Finalmente, Visual C++ 2005 y las versiones posteriores agregan una defensa que detecta el desbordamiento de enteros al llamar a operator::new. Puede empezar con código de esta manera:
CFoo *p = new CFoo[count];

Visual C++ lo compila a:
00004 33 c9 xor ecx, ecx
00006 8b c6 mov eax, esi
00008 ba 04 00 00 00 mov edx, 4
0000d f7 e2 mul edx
0000f 0f 90 c1 seto cl
00012 f7 d9 neg ecx
00014 0b c8 or ecx, eax
00016 51 push ecx
00017 e8 00 00 00 00 call ??2@YAPAXI@Z ; operator new

Una vez calculada la cantidad de memoria para asignar (mul edx), se establece o no el registro de CL dependiendo del valor del indicador de desbordamiento después de la multiplicación, de modo que ECX será 0x00000000 o 0xFFFFFFFF. A causa de la operación siguiente (o ecx), el registro ECX será 0xFFFFFFFF o el valor incluido en EAX, que es el resultado de la multiplicación inicial. Esto entonces pasa a operator::new, que generará un error en el caso de la asignación 2^N-1.
Esta defensa es gratuita. No hay modificador de compilador; esto es simplemente lo que el compilador hace.

¿Qué ocurre si hay errores?
¡Ah! ¡Esa es la cuestión espinosa! Si cualquiera de las defensas enumeradas se activa, se produce un resultado bastante desagradable: la aplicación finaliza. Esto no es ideal, pero es mucho mejor que ejecutar el código malintencionado del atacante.
El SDL obliga a que el código nuevo use todas estas opciones defensivas, ya que hay tantos ataques que nunca puede comprobar que su código esté totalmente libre de vulnerabilidades. Una de las frases clásicas del SDL es "asuma que su código tendrá errores... ¿ahora qué?" En el mundo verdadero, "ahora que" significa oponer resistencia. No le ponga las cosas fáciles al código del atacante. Al contrario, vaya un paso por delante para que no consiga sus objetivos. ¡No se rinda!
Así que compile con la última versión del compilador C++ para obtener un mejor /GS y vincule con el último vinculador para beneficiarse de las defensas de CPU y sistemas operativos.
Envíe sus preguntas y comentarios a briefs@microsoft.com.
Michael Howard es administrador principal de programas de seguridad en Microsoft y se encarga de mejorar los procesos seguros y las prácticas recomendadas. Es coautor de cinco libros sobre seguridad, incluidos Writing Secure Code for Windows Vista, The Security Development Lifecycle, Writing Secure Code y 19 Deadly Sins of Software Security.
Consulte su blog en blogs.msdn.com/michael_howard

No hay comentarios: