Por todos lados escuchamos alertas de seguridad acerca de nuevos virus y programas maliciosos que hace cosas como “desbordamiento de búferes” y otras cosas raras. En realidad, lo único que hacen dichos programas es aprovechar una falla en el código del programador… y los programadores no pueden estar en todo. Para ello, los compiladores se mejoran cada día más dejándole tiempo al programador de pensar lo que realmente importa.
Veamos uno de los tipos de desbordamiento más comunes: el “pisado de pila” (stack smashing en inglés). Una excelente explicación de lo que es, cómo se hace y cómo se lo previene. Con el valor agregado de posibilitar una nueva herramienta que nos ayudará a explicar errores de programación. Un artículo inspirado en la funcionalidad incluída en el nuevo compilador por defecto de la distribución Debian - en su versión inestable -.
Notas de Traducción
- Esta traducción se realizó con autorización por escrito (en un e-mail) de su autor: Steve Komp
- Los vínculos no fueron modificados, por lo que seguramente apuntarán a páginas en inglés
- Artículo Original: Stack Smashing Protection for Debian
La traducción
De un tiempo a esta parte, el compilador por defecto de Debian Sid fue actualizado para incluir Protección de Pisado de Pila (SSP según sus siglas en inglés) sin necesitar un parche. A continuación, ejemplos de cómo puede ser utilizada para prevenir ataques.
El compilador por defecto de Sid, que será utilizado en Etch también, es GCC v4.1. Esta última versión contiene el parche SSP que anteriormente debíamos agregar a mano si lo queríamos (ya mostramos cómo aplicar el parche de SSP a GCC 3.4). Al estar incluído, ahora es mucho más sencillo trabajar con él.
Un programa vulnerable
Miremos, primero que nada, un ejemplo. Este es un ejemplo común de un programa C vulnerable:
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
// Buffer estático en la pila.
char buffer[1024];
if ( argc != 2 )
{
printf("Uso: %s argumento\n", argv[0] );
return( -1 );
}
// Copiado de cadenas sin control.
strcpy( buffer, argv[1]);
printf( "Argumento copiado\n" );
return(0);
}
Este progrma simple acepta un argumento y lo copia a un buffer estático. Este es un error de programación clásico, si este programa fuese compilado a un ejecutable setuid/setgid (ejecutable por cualquiera como si fuese el dueño) permitiría a un atacante ganar permisos fácilmente.
Como vamos a demostrar las nuevas funciones del nuevo compilador, asegúrense de compilar el ejemplo con gcc-3.3. Para probarlo, compilarlo normalmente:
toote@pc:/tmp$ gcc-3.3 -o buggy buggy.cVeamos ahora si lo podemos romper. Primero dos pruebas de ejecución.
toote@pc:/tmp$ /tmp/buggy
Uso: /tmp/buggy argumento
toote@pc:/tmp$ /tmp/buggy test
Argumento copiado
Ambas ejecuciones funcionaron como se esperaba. Ahora probemos pasarle un argumento más largo para ver si podemos desbordar el buffer estático:
toote@pc:/tmp$ ./buggy `perl -e 'print "X"x2048'`
Argumento copiado
Violación de segmento
Tuvimos éxito: desbordamos el buffer con nuestro argumento de 2k, lo que resultó en una violación de segmento. Ahora, si podemos producir un archivo de núcleo podríamos debuguearlo:
toote@pc:/tmp$ ulimit -c 09999999
toote@pc:/tmp$ ./buggy `perl -e 'print "X"x3333'`
Argumento copiado
Violación de segmento (con arhivo de núcleo)
Al correr gdb podremos ver el programa:
toote@pc:/tmp$ gdb ./buggy core
GNU gdb 6.4.90-debian
...
El programa finalizó con señal 11, Violación de segmento
#0 0x58585858 in ?? ()
(gdb) info registers eip
eip 0x58585858 0x58585858
Aquí podemos ver que el puntero de instrucciones - registro del procesador que indica cuál será lo que se ejecutará a continuación - es 0×58585858 (0×58 es ‘X’ en hexadecimal). Esto significa que efectivamente tomamos el control del ejecutable con nuestro script malicioso.
El realmente explotar el ejecutable para correr una línea de comandos habiendo hecho esto es trivial y, por lo general, puede ser automatizado:
toote@pc:~/cmd-overflow$ make
gcc-3.3 -o cmd-overflow -Wall -ggdb cmd-overflow.c
gcc-3.3 -o cmd-vuln -Wall -ggdb cmd-vuln.c
toote@pc:~/cmd-overflow$ ./cmd-overflow --target=/tmp/buggy --args='%' --size=2048
Argumento copiado
shell-3.1$ id
uid=1000(toote) gid=1000(toote) groups=29(audio), 44(video), 46(plugdev), 100(users), 1000(toote)
shell-3.1$ exit
exit
Utilizamos aquí un simple programa para crear un argumento de 2048 bytes de longitud que contiene el código requerido para correr una línea de comandos, y luego corrimos nuestro programa defectuoso con este argumento construído a medida.
Se desbordó el buffer al correr nuestro código, lo que resultó en la ejecución de una línea de comandos (¿qué es un shellcode? - en español). Si nuestro programa defectuoso hubiese sido del super-usuario y ejecutado en setuid ¡¡hubiésemos ganado privilegios de súper-usuario!!
Nota:
En algún punto de la vida de la serie de kernels de Linux 2.6.x se introdujo una nueva medida de seguridad para aleatorizar las direcciones de la pila. Si se está corriendo alguno de dichos kernels, estos ejemplos fallarán.Para desactivar esta protección basta con correr:
root@pc:~# sysctl -w kernel.randomize_va_space=0Estpo te permitirá experimentar con desbordamiento de buffers y evitar, al tiempo, el uso de técnicas más avanzadas de explotación (que pueden ser muy divertidas si estás aburrido
).
Evitando este ataque con SSP
Ahora que debian contiene el compilador 4.1 podemos utilizar el nuevo argumento -fstack-protector para compilar con protección de desbordamiento de buffer automática.
Antes de ello, seguramente necesitaremos instalar un nuevo paquete:
root@pc:~# aptitude install libssp0-dev
Leyendo lista de paquetes... Hecho
Construyendo árbol de dependencias... Hecho
Se instalarán los siguientes paquetes:
libssp0
Paquetes sugeridos:
lib64ssp0
Se instalarán los siguientes NUEVOS paquetes
libssp0 libssp0-dev
Una vez instalados estos paquetes, podemos recompilar nuestro programa defectuoso:
toote@pc:/tmp$ gcc-4.1 -fstack-protector -o buggy buggy.c
buggy.c: In function ¡Æmain¡Ç:
buggy.c:16: alerta: declaración implícita incompatible de función ¡Æstrcpy¡Ç
Este ejecutable debería de estar protegido contra simpes desbordamiento de buffers. Probemos romperlo como antes:
toote@pc:/tmp$ ./buggy `perl -e 'print "x"x2048';`
Argumento copiado
*** stack smashing detected ***: xxxx .. xxxx terminated
Illegal instruction
Exelente ¿no? Probemos ahora explotar la falla que antes pudimos:
toote@pc:~/cmd-overflow$ ./cmd-overflow --target=/tmp/buggy --size=2048 --args='%'
Argumento copiado
*** stack smashing detected ***: terminated
Illegal instruction
Todo parece bien, y la protección funciona como esperábamos. (Nótese que esta protección no ayudará contra explotaciones avanzadas de errores de programación).
Al utilizar esta librería se puede agregar una simple protección contra desbordamiento de buffers a nuestros ejecutables con una mínima influencia en la performance. Si mantienes un paquete, podría ser útil re-compilar utilizando esta nueva funcionalidad para ver cuán bien funciona en la práctica.
La re-compilación de todos los paquetes en servidores con niveles críticos de seguridad sería algo realmente útil de hacer, ahora que las herramientas están fácilmente disponibles.
One Comment
Hacia una semana que habia dejado este artículo por la mitad y por fín me regale el tiempo para completarlo. Está excelente, ahora me tengo que permitir meter mano a los ejemplos… Pero creo que eso ya es utopía.