libsafe - Protección contra desbordamiento de pila

Texto íntegro de Carlos Morales, 1 Noviembre del 2003
Resumen
-

Este documento explica de qué trata un ataque de desbordamineto de buffer y como protegerse de este tipo de ataques con la herramienta libsafe. También se hace un repaso al funcionamiento de libsafe, cómo instalarlo, configurarlo y un breve ejemplo de su efectividad a través de un ejemplo práctico mediante un exploit.

Comentarios:

-

La Desbordamiento de Pila o Stack Overflow es posiblemente el tipo de ataque más común, puede ejecutarse sobre cualquier programa que no se haya programado correctamente, lo que que todos los programas sean objetivos potenciales. Si pudiésemos controlar los programas y evitar desbordamientos de sus pilas no solo evitamos los ataques de stack overflow descubiertos sino también los futuros. Y eso es lo que hace libsafe, detectar y tratar este tipo de exploits. Todo ello de forma invisible a los programas y a los usuarios.

-

Una de las formas más comunes de tomar el control de una máquina ilegalmente es ejectuar un ataque de "Desbordamiento de Pila" (Stack Overflow), este tipo de ataque intenta que un programa normal y corriente ejecute otro proceso mediante la desbordamiento de la pila. A grosso modo este tipo de ataques buscan errores de programación escribiendo datos no intencionados en la parte de la memoria del programa, si este programa no protege su pila de memoria un proceso atacante puede sobreescribirla y si consigue sobreescribir la dirección de retorno con una dirección de un segundo programa este segundo programa se ejecutará. Si el programa defectuoso es por ejemplo un servidor FTP y este está ejecutándose con permisos de root, un atacante puede sobreescribir la pila del servidor FTP y ejecutar un intérprete de comandos (bash por ej.) o cualquier otro proceso con los mismos derechos que el servidor FTP en este caso root, tomándo el control de la máquina.

Los programas 'inseguros' usan funciones como pueden ser: strcpy, strcat, getwd, gets, scanf, realpath y spritnf. Éstas funciones copian información a las variables del progarama pero no controlan que lo que se copia es mayor al tamaño de la variable. Estas funciones son muy usadas por los programadores, y hacen que el programa sea inseguro. Si se ejecuta una de estas funciones y puede copiarse esta información ...

	"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    	"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    	"\x80\xe8\xdc\xff\xff\xff/bin/sh";
	
... en una direción de retorno sin estar protegidos por libsafe, se ejecutará /bin/sh.

Es por eso que es realmente importante protegerse de estos ataques y eso es lo que hace la herramienta libsafe.

-

Libsafe es una librería que se carga dinámicamente en el kernel. Libsafe está basado en un software que intercepta todas las llamadas hechas a funciones que pueden ser vulnerables, al interceptar las llamadas las substituye de manera que tengan la misma funcionalidad pero que no puedan hacer ningún ataque a la pila, de manera que no puedan sobreescribir ninguna dirección de retorno para redirigir el flujo del programa que se esté ejecutando.

Este programa trabaja con programas binarios ya existentes, lo que significa que no es necesario modificarlos. Eso hace que sea independiente y que proteja a todo el sistema de manera transparente al usuario.

Los pasos de libsafe son los siguientes: intercepta una llamada a una función vulnerable y ejecuta su propia versión. Determina si puede ejecutarse de forma segura, en caso de no ser así libsafe termina la ejecución de la función y crea una alerta, todo esto según se haya configurado.

Interceptación
libsafe se basa en el funcionamiento de las librerias compartidas de Linux glibc. Si libsafe está funcionando carga su libreria con las funciones seguras como libreria compartida (shared library), y es así como se carga antes que las librerias estándar. De esta manera se ejecutan las funciones de libsafe en vez de ejecutarse las funciones vulnerables, y es así porque libsafe carga la libreria con funciones seguras que tienen el mismo nombre que las funciones vulnerables. Por ejemplo un programa cualquiera en vez de llamar a la función sprintf() llama a snprintf().
Ejecución segura
La comprobación de que se está ejecutando de manera segura es específica a cada función. Por ejemplo para la función sprintf(), libsafe mira que no se le pase como argumento ninguna dirección de memoria y también comprueba que no se muestre el contenido de dos frames diferentes. Si el resultado de las comprobaciones indica que ha habido una operación sospechosa crea una alerta y se comporta como haya sido configurado.
Tratar la violación
Cuando se declara una violación se muestra un mensaje de error al programa, se crea un mensaje de error en /var/log/secure y recibe una señal de SIGKILL que matará el proceso que está siendo atacado. Opcionalmente se puede configurar el programa a la hora de compilarlo para que envie un email de alerta o para que cree un core dump (volcado de pila) donde muestra la situación del programa en el momento del supuesto ataque.

Advertencia! libsafe no funcionará correctamente en dos situaciones: no soporta libc5 ni tampoco si se compila usando -fomit-frame-pointer.

Debido a la forma de interceptar las llamadas los programas que se ejecuten a través de libsafe tienen que estar compilados con libc6, sino dará un segmentation fault. Para saber con qué se ha linkado un porgrama podemos usar la herramienta ldd sobre el programa que queramos averiguar de la siguiente manera:

	# ldd /sbin/init
        	libc.so.6 => /lib/libc.so.6 (0x4001d000)
        	/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
	
En este caso /sbin/init está compilado con libc.so.6, es decir que este programa soportará libsafe. Si no es así debemos recompilar el programa con libc6 o bien añadir el programa al fichero /etc/libsafe.exclude como se explica más adelante.

La segunda situación donde no funciona libsafe es si el programa se ha compilado con la opción gcc -fomit-frame-pointer, ya que al compilarlo de esta manera libsafe no puede controlar correctamente el tamaño del frame, y no sabe si se está saltando fuera del frame. Pero esta opción de compilación es extraña, así que la mayoría de programas que nos interesa (servidores principalmente). Y en el caso de no ser así el programa libsafe simplemente no busca si hay violaciones, es decir no modificará el funcionamiento del programa.

-

La instalación de libsafe es sencilla. Primero deberemos bajarnos la última versión del programa de la página de Avaya. Actualmente se trata de la versión 2.0-16. Y seguiremos los siguientes pasos:

	$ cd /usr/src
	$ wget http://www.research.avayalabs.com/project/libsafe/src/libsafe-2.0-16.tgz
	$ tar xvzf libsafe-2.0-16.tgz
	$ cd libsafe-2.0-16
	$ make
		gcc -M  util.c intercept.c > dep
		gcc -c -o util.o -O2 -Wall ...
		...
		...
	$ su
	# make install
		cd src; make install ...
		...
 	

Para hacer el make install es necesario tener permisos de root, ya que copiara la librería libsafe.so en /lib, igual que instala las páginas de ayuda man, ejecuta /sbin/ldconfig que es necesario tras instalar nuevas librerías y nos preguntará si queremos que se ejecute libsafe para todos los procesos, mejor responder que no así tenemos la posibilidad de configurarlo como deseemos.

Ahora que ya está instalado se puede configurar de dos maneras: una para que se cargue por defecto al arrancar el sistema y otra mediante una variable de entorno.

  • Usando la variable de entorno LD_PRELOAD es suficiente para que todos los programas ejecutados en ese entorno pasen por libsafe. La manera más cómoda de hacerlo es usando el script de /etc/bashrc (/etc/bash.bashrc para Debian) y /etc/csh.cshrc, así estará disponible para todos los intérpretes bash y csh.
    	# echo "LD_PRELOAD=libsafe.so.2" >> /etc/bash.bashrc
    	# echo "export LD_PRELOAD" >> /etc/bash.bashrc
    	
    	# echo "setenv LD_PRELOAD libsafe.so.2" >> /etc/csh.cshrc
    	
  • Cargar la librería usando la variable de entorno es efectiva para los programas que usen los usuarios pero no para los programas ejecutados con setuid. Para que esté disponible para todo el sistema debemos añadir al fichero /etc/ld.so.preload el nombre de la libreria (libsafe.so.2) que deseamos cargar nada más se inicie el sistema. La siguiente manera es un forma de hacerlo:
    	# echo "/lib/libsafe.so.2" >> /etc/ld.so.preload
    	

Para evitar que un programa pase por libsafe debemos añadir el programa con path completo a /etc/libsafe.exclude, en cada línea un programa diferente. Esta opción puede sernos útil si tenemos algún programa que esté compilado con libc5 o bien que desborda la pila intencionadamente (poca utilidad en un servidor, pero es posible). Debido a lo que esto implica /etc/libsafe.exclude solo debería poderse modificar por root.

Si deseamos configurar libsafe para que nos notifique una violación de la pila con un email, debemos modificar el Makefile y añadir a los CFLAGS la opción -DNOTIFY_WITH_EMAIL. Y lo hacemos de la siguiente manera:

	$ cd src/
	$ vim Makefile
		CCFLAGS		= -O2 -Wall -fPIC ....
	
Modificar a:
		CCFLAGS		= -O2 -Wall -fPIC .... -DNOTIFY_WITH_EMAIL
	

Una vez configurado podemos pasar a ver un ejemplo de uso, de esta manera podremos identificar los intentos de desbordamiento de pila que se hayan ejecutado a nuestro sistema.

-

Una vez configurado libsafe pasemos a ver un ejemplo práctico de un exploit antes y después de instalar libsafe. A modo de ejemplo usaremos el programa t1.c, incluido en el directorio exploits con el que viene libsafe. Este programa busca una dirección de retorno y desborda la pila copiando un puntero para ejecutar el intérprete de órdenes (shell en inglés) de /bin/sh. Probar el exploit antes de instalar libsafe y nos mostrará lo siguiente:

	[carlosm:~/exploits]$ gcc -o test_exploit t1.c
	[carlosm:~/exploits]$ ./test_exploit
		This program tries to use strcpy() to overflow the buffer.
		If you get a /bin/sh prompt, then the exploit has worked.
		Press any key to continue...
	sh-2.04$
	

El exploit ha tenido éxito: ha ejecutado /bin/sh, de esta manera el atacante tiene oportunidad de abrir un shell con los permisos del propietario del fichero.

Para protegerse deberemos tener instalado libsafe y después activarlo. Para activarlo seguir los pasos antes comentados, pero en este ejemplo usaremos las variables de entorno:

	$ LD_PRELOAD=libsafe.so.2
	$ export LD_PRELOAD
	$ ./test_exploit
		This program tries to use strcpy() to overflow the buffer.
		If you get a /bin/sh prompt, then the exploit has worked.
		Press any key to continue...

		Libsafe version 2.0.16
		Detected an attempt to write across stack boundary.
		Terminating /home/carlosm/exploits/test_exploit.
		    uid=1000  euid=1000  pid=4294
		Call stack:
		    0x400169b0  /lib/libsafe.so.2.0.16
		    0x40016ad4  /lib/libsafe.so.2.0.16
		    0x80485fc   /home/carlosm/exploits/test_exploit
		    0x8048612   /home/carlosm/exploits/test_exploit
		    0x4003c14a  /lib/libc-2.2.5.so
		Overflow caused by strcpy()
		Killed
	$ 
	

Hemos visto como ha evitado el desbordamiento de la pila de manera exitosa. Otra cuestión importante es ver en los logs si ha habido algún intento de desbordamiento. Este paso es muy importante ya que si no se ha configurado el programa para que envie emails de alerta la única manera de saber si ha habido un intento de ataque es a través de los ficheros de log.

Bajo Debian las alertas van al fichero /var/log/auth.log y esta es la alerta que nos ha dado libsafe al ejecutar el exploit anterior:

	libsafe.so[4294]: Libsafe version 2.0.16
	libsafe.so[4294]: Detected an attempt to write across stack boundary.
	libsafe.so[4294]: Terminating /home/carlosm/exploits/test_exploit.
	libsafe.so[4294]:     uid=1000  euid=1000  pid=4294
	libsafe.so[4294]: Call stack:
	libsafe.so[4294]:     0x400169b0  /lib/libsafe.so.2.0.16
	libsafe.so[4294]:     0x40016ad4  /lib/libsafe.so.2.0.16
	libsafe.so[4294]:     0x80485fc  /home/carlosm/exploits/test_exploit
	libsafe.so[4294]:     0x8048612  /home/carlosm/exploits/test_exploit
	libsafe.so[4294]:     0x4003c14a  /lib/libc-2.2.5.so
	libsafe.so[4294]: Overflow caused by strcpy()
	
-

libsafe es una herramienta muy pontente disponible para GNU/Linux con licencia GPL, que nos evitará ataques de desbordamiento de pila. La instalación es sencilla y su funcionamiento es efectivo. Y es efectivo no solo para los ataques de desbordamiento de pila conocidos, sino también para ataques futuros todavía desconocidos. Es a la vez un detector de intrusiones y una forma de seguridad activa en la misma herramienta. Así que añadamos esta herramienta en la política de seguridad y dispongámonos a instalar libsafe en todas las máquinas Linux que dispongamos en producción.

Todos los comentarios/sugerencias/ideas/críticas mejoran esta documentación y por consiguiente tus conocimientos:

-
libsafe
Página oficial de libsafe, donde podemos bajarnos el programa. También contiene documentación técnica sobre libsafe.
Writing Safe Setuid Programs.
No sería necesario esta herramienta si si escribiesen los programas de forma segura, aquí explica como.
Avoid Buffer Overflow.
Otro documento que enseña como crear programas para protegerlos de desbordamientos de pila.