Stack smashing
De Wikipedia, la enciclopedia libre
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 -. 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.c
Veamos 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!!