Bit SUID en shell script (I)

Hace unos días, después de realizar una instalación de una red de detección de intrusos dedicada, nos quedamos unos cuantos compañeros hablando sobre temas de seguridad. Durante la charla, hubo un apartado acerca de los scripts con bit suid y cómo, mientras en Solaris con la shell ksh se permitía la ejecución de un script suidado, el núcleo de Linux evitaba la ejecución de un script con suid. Antes de continuar con la entrada en el blog, es recomendable conocer el funcionamiento del bit suid. Veámoslo brevemente:

El bit suid es un permiso que se puede conceder tanto a directorios como ficheros, en el caso a tratar nos interesará cuando éste es concedido a ficheros.

El suid en ficheros es un permiso muy especial, que permite a un usuario con permisos de ejecución para dicho fichero, tomar los permisos del dueño del fichero durante su ejecución. Un ejemplo sencillo para entender su funcionamiento el el binario passwd, que permite cambiar las contraseñas de los usuarios en entornos Unix/Linux. Este binario tiene como dueño el usuario root (administrador) y el bit suid activado, permitiendo de esta forma, a un usuario no privilegiado poder cambiar su contraseña. Sino sería imposible/muy inseguro que un usuario cambiara su contraseña, puesto que necesitaría permisos para leer y escribir en el fichero de contraseñas.

Para conceder los permisos suid a un fichero se realiza mediante el comando chmod con flag u+s o valor 4XXX de forma octal. Por ejemplo chmod 4711 binario. Cuando se lista un fichero, este bit se identifica con la letra “S” o “s” en la posición de ejecución del fichero para el usuario. Valdrá “s” cuando aparte del bit suid, tenga activado el bit de ejecución, mientras que valdra “S” cuando no tenga el bit de ejecución activado.

[Read more…]

Tecnología COW aplicada a la memoria principal

Como siempre ocurre cuando hay una actualización del núcleo de Linux, uno comprueba cuales son las mejoras que este aporta respecto a su última versión para realizar un estudio de riesgos que implicaría la actualización a dicha versión y si realmente vale la pena. Por ello hace unas semanas me encontraba leyendo la información acerca del nuevo núcleo (2.6.32) y leí un apartado enfocado a la virtualización. En él, los chicos de Red Hat habían aplicado un concepto que normalmente se emplea en los sistema de ficheros, pero esta vez aplicando la idea a la memoria principal; se trata del COW o copia en escritura (Copy On Write).

Para explicar la nueva mejora vamos a exponer primero el funcionamiento de la tecnología COW aplicada en los sistemas de ficheros de entornos virtuales. COW emplea ficheros Sparse: ficheros distribuidos en bloques, donde existe un índice que indica qué bloques están ocupados. Por ello, podemos crear ficheros de un determinado tamaño, y sólo ocupará espacio realmente el tamaño de aquellos bloques que tengan información valida. Por ejemplo, antes si yo quería crear un fichero vacío de 1GB, el fichero se creaba pero ocupaba 1GB aunque no contuviese ningún tipo de información. Con los ficheros sparse puedes crear un fichero vacío de 1GB y éste ocupara sólo 4Kbyte.

Y dirán, ¿pero cómo puedo emplear esto en la virtualización? Pues muy sencillo. Imagínense que tienen un entorno con 20 máquinas virtuales en las que la única diferencia entre cada máquina virtual son un par de binarios o ficheros de configuración; por ejemplo, varios servidores Linux Debian. Con la tecnología COW usted puede tener un único fichero de tamaño X para todas las máquinas virtuales y un fichero COW para cada máquina virtual. Los ficheros que se modifiquen en la máquina virtual se escribirán única y exclusivamente en el bloque modificado del fichero COW de la máquina por lo que, si la distribución ocupa 1GB, en vez de necesitar 20GB (1GB x 20 máquinas virtuales) necesitará 1GB más lo que ocupe cada COW, que serían de media entre 10 y 50MB. Es decir, que con menos de 2GB es posible tener 20 máquinas virtuales totalmente independientes en vez de necesitar 20GB.

A su vez, se habrán dado cuenta que es la tecnología que emplean la mayoría de productos de virtualización para hacer una captura del estado actual de una máquina virtual, ya que lo que hacen es guardar el estado actual y todas las modificaciones posteriores se realizan sobre un fichero COW en lugar de sobre la imagen, por lo que para volver a un estado anterior, únicamente requerirá eliminar el fichero COW.

Además, esto presenta una mejora de rendimiento, puesto que es muy probable que una máquina solicite al sistema de ficheros un dato que previamente haya sido ya solicitado por otra máquina virtual y se encuentre ya en la memoria principal, por lo que no será necesario tener que cargarlo de disco a memoria, obteniendo un aumento considerable de la velocidad. Incluso se permite el lujo de poder cargar mediante tmpfs la imagen de la distribución totalmente en memoria principal puesto que no es lo mismo tener cargado 20GB en memoria principal que 2GB, ¿verdad?

El problema de esta tecnología es cuando se emplean máquinas distintas puesto que al final hay más bloques modificados en el fichero COW que en la imagen centralizada. A su vez, no todas las tecnologías de virtualización soportan este tipo de ficheros por lo que hacen que no se explote toda la capacidad de la tecnología COW.

Entonces, han pensado los chicos de Red Hat… ¿Por qué no aplicamos directamente el principio de la tecnología COW a la memoria principal? Si un dato que fue solicitado anteriormente por una máquina virtual se encuentra ya en memoria, cuando otra máquina virtual solicite ese mismo dato ¿que lógica tendría solicitar el dato al fichero en disco, tardando más y ocupando en la memoria datos replicados? Para ello, lo que se hace es revisar la memoria en busca de datos replicados, eliminando el dato replicado y suministrando a las máquinas virtuales los datos de la única copia que hay en memoria principal. De esta forma se consigue incrementar el rendimiento, puesto que se solicitan menos datos al sistema de ficheros, mucho más lento que la memoria, y se consigue un ahorro de memoria principal muy grande, por lo que con una misma cantidad de memoria principal se podrá virtualizar un número mayor de máquinas.

A la aplicación de los principios de la tecnología COW en memoria se conoce como deduplicación de memoria. En el caso del kernel Linux usa el hilo KSMd (Kernel Samepage Merging) para revisar la memoria en busca de áreas idénticas, eliminando las copias y conservando solo una de éstas. La prueba de concepto fue realizada sobre un servidor de virtualización con 16GB de memoria principal donde se hizo funcionar hasta 52 máquinas virtuales Windows XP concediendo 1GB de memoria principal a cada máquina virtual.

¿Les ha quedado alguna duda?

¿Por qué tanta prisa en Ubuntu?

(Una vez pasadas totalmente las fiestas navideñas, retomamos con esta entrada la programación y periodicidad habitual, esperando que sigan con nosotros y que los Reyes Magos les hayan traído muchas cosas)

Hace cerca de 10 años que soy usuario de Linux; no por que lo considere mejor ni peor que otras opciones, sino porque es la opción que más se adapta a mis necesidades. Desde mi primera Linux SuSE 5.3 que compré en un kiosco con su increíble KDE 1.0 recién salido al no disponer de Internet por aquellos entonces, he usado en casa Linux. De SuSE pase a RedHat, luego a Debian, seguir con Gentoo durante 3 años, y actualmente uso Ubuntu. Sí, sé que es raro pasar de una Gentoo a una Ubuntu, pero la comodidad que me aporta, el gran número de paquetes disponibles y la versión actualizada de éstos me ha convencido para ser mi distribución.

Como ustedes sabrán las versiones de Ubuntu están formadas por dos cifras separadas por un punto: la primera cifra indica el año y la segunda el mes de la fecha de cuándo se va a lanzar la versión final. En concreto la última versión ha sido la 9.10 (Octubre del 2009).

[Read more…]

Curiosidades del Traceroute

Para la construcción de la topología de una red de caja negra se emplean herramientas como Traceroute; seguro que la conocen. Esta herramienta emplea el campo TTL o tiempo de vida de la cabecera IP para averiguar por qué dispositivos de red pasan los paquetes hasta llegar al host destino.

La aplicación manda 3 paquetes con TTL valor 1 hacia la máquina objetivo; cuando estos paquetes llegan al primer dispositivo intermedio, éste decrementa el TTL en 1, pasando a valer 0, y es descartado notificándonos mediante un paquete ICMP tipo 11 Time Exceeded. La siguiente vez se enviará con TTL valor 2, posteriormente con valor 3… así hasta llegar a la máquina destino, la cual no contestará con el Time Exceeded.

Lo más importante para una auditoría de seguridad son los últimos saltos que nos permitirán obtener la topología de la red que se está auditando. Ahora bien, seguramente nos habrá ocurrido que en ocasiones los resultados obtenidos no son los correctos puesto que llegado a un firewall de la red no conseguimos poder avanzar hasta el host; incluso dependiendo del programa que empleemos los resultados son distintos. Esto es debido a combinación de las reglas de filtrado de los firewalls y los protocolos empleados por cada programa “traceroute”.

[Read more…]

Bastionar una DMZ

Muchas veces hemos oído el termino bastión o bastionar un servidor en una red DMZ. Este término es empleado cuando a una configuración por defecto de una instalación en un servidor se le realizan modificaciones para fortalecer la seguridad del sistema. A muchos de ustedes les vendrá a la mente Bastille en entornos RedHat.

Dichas modificaciones suelen estructurarse comúnmente en tres etapas:

  • La primera etapa se centra en cerrar los puertos de aquellos servicios que escuchan por defecto y que no se requieren para las necesidades solicitadas para dicho servidor.
  • La segunda etapa consiste en fortalecer la configuración de los servicios que si se necesitan mediante la restricción de los usuarios que pueden acceder a los servicios, restricción de ordenes que pueden realizar, control y filtrado de los datos que se le envían al servidor (XSS, SQL injection, etc), política de actualizaciones…
  • La tercera etapa se centra en instalar el servidor en la red DMZ, la cual dispone de un firewall que restringe el acceso de las máquinas externas hacia la DMZ.

[Read more…]

Anécdotas del kernel de Linux: SysMagic

La tecla mágica es una interrupción que permite comunicarse con el kernel en situaciones de inestabilidad en una máquina Linux, que viene habilitada por defecto en la gran mayoría de kernels precompilados de las distribuciones actuales. En caso de que compilemos el núcleo a mano, la opción “Magic SysRq Key” se encuentra en el menú “Kernel Hacking”. En el fichero de configuración del kernel recibe el nombre de CONFIG_MAGIC_SYSRQ por lo que realizando un “grep” de éste veremos si la hemos habilitado (debe aparecer CONFIG_MAGIC_SYSRQ=Y).

[Read more…]

¿Vulnerable? No… a primera vista (II)

Tal como vimos en la entrada anterior, estamos intentando contrastar la información respecto a un fallo de seguridad en el FTP del IIS. Nos hemos dado cuenta de que tras ejecutar el exploit en todas sus versiones, no ha funcionado pese a ejecutarse sin fallos.

Revisando los logs del ftp, comprobando el tráfico con el tcpdump, y revisando el código del exploit seguíamos sin saber el motivo por el cual éste no funcionaba. La última opción que nos faltaba era usar el ollydbg con el inetinfo.exe, así como todo aquel programa que tenga relación con el FTP e intentar averiguar por qué el exploit fallaba. Pero lógicamente esto requería de un tiempo del que no disponíamos.

Analizando los motivos por el cual era posible que el exploit no funcionara decidimos realizar una maqueta lo más real posible. Instalamos un windows sobre una máquina física y conectamos a ésta otra máquina física que sería la máquina atacante. Pero el resultado fue el mismo: el exploit no funcionaba.

Analizando de nuevo el código, nos dimos cuenta que había direcciones estáticas, en concreto esta:

$retaddr = "\x9B\xB1\xF4\x77";

Esto nos hizo pensar que posiblemente esa dirección funcionara únicamente para la versión de Windows que tuviese el creador del exploit, la que resultó ser ni más ni menos que un windows 2000 pero en INGLÉS. Efectivamente, instalamos una versión en ingles sobre una máquina virtual para intentarlo de nuevo, y esta vez sí, el exploit funcionó concediendo una shell en el sistema atacado con permisos de usuario SYSTEM:

captura

Esto confirmaba nuestras sospechas: el motivo del fallo en la ejecución del exploit era a causa de que éste toma las direcciones estáticas pensadas para funcionar sobre un windows en ingles. Lógicamente, si pensamos apilar la palabra “hello“, ésta requiere de 5 bytes mientras que la palabra “hola” requiere de 4 bytes, por lo que es muy posible que la dirección no fuera la misma en la versión en castellano.

Para terminar de confirmarlo pensamos en qué usuarios que no fueran de habla inglesa habrían buscado cual era la dirección correcta para ejecutar el exploit en sus sistemas. Me vinieron rápidamente a la cabeza los alemanes, así que me decidí a realizar búsquedas en foros alemanes que trataran el tema y por fin lo localice. Un usuario había publicado una variante del exploit para aquellas versiones de Windows en alemán, y tal como pensábamos, únicamente había que modificar la dirección del “retaddr”.

Por tanto, la vulnerabilidad existe, es un zero-day —ya que el creador del exploit no notificó al fabricante—, no hay todavía parche que la solucione y lo más importante, los sistemas en castellano si son vulnerables; lo único que el atacante debe hacer es modificar el retaddr del exploit por el que toca para entornos en castellano.

Entonces, ¿es una alerta grave? Si se cumplen los requisitos es gravísima, pero seamos sinceros, ¿qué administrador de sistemas concede permisos de escritura a usuarios anónimos o otros usuarios genéricos que no tengan contraseña? (alguno dirá que muchos, pero yo no llamaría a esos “administrador de sistemas”).

Donde reside realmente el problema de esta vulnerabilidad es en aquellos usuarios, que aunque protegidos con contraseñas, tengan permisos de escritura en el ftp. Si el atacante mediante diversos medios consigue obtener la contraseña del ftp del usuario podrá obtener una shell en el servidor. Teniendo en cuenta que las contraseñas del ftp se transmiten en texto plano, y que los usuarios por naturaleza no suelen tener excesivo cuidado en cuanto a la seguridad de sus máquinas personales, esta vulnerabilidad se convierte en un fallo de seguridad importante.

¿Solución? Pues hasta que salga el parche correspondiente para esta vulnerabilidad, la solución no es otra que revocar permisos de escritura a todos los usuarios, y emplear otros medios para subir los datos al ftp. De esta forma, todas las aplicaciones que dependan de la lectura de datos del ftp seguirán funcionando. Es algo “estricta”, pero si alguno de ustedes tiene una idea mejor, por favor que la indique en los comentarios.

¿Vulnerable? No… a primera vista

Les voy a contar mi pequeña experiencia con un incidente ocurrido hace unas semanas atrás relacionado con una vulnerabilidad en el FTP del IIS. Ésta afecta tanto a la versión 5.0, 5.1, 6.0 y 7.0, provocando una denegación de servicios [Véase boletín de seguridad de Microsoft], pero es en la versión 5.0 donde la vulnerabilidad es más grave, debido a que hay suficiente espacio como para introducir código. Esto ha dado lugar a dos versiones adicionales del exploit: una permite crear usuarios en el sistemas y la otra la ejecución de una shell remota.

Era finales de agosto cuando comenzaron a llegar noticias de un zero-day en el ftp del IIS. Como todo lo que ocurre con vulnerabilidades de entornos Microsoft, siempre es difícil medir cuánto de toda la información que te llega es verdad. Lo primero es comprobar que realmente no es un bulo y efectivamente, no lo era; demasiadas fuentes apuntaban a este fallo de seguridad grave.

Nos pusimos manos a la obra, y obtuvimos los exploits, los nse del nmap para comprobar la vulnerabilidad, y preparamos una maqueta virtual de un entorno de pruebas que cumpliera todos los requisitos: un servidor FTP del IIS con un usuario con permisos de escritura. Pese a que algunas informaciones apuntaban a que no se requería permisos de escritura, todas las pruebas realizadas apuntan a que se requieren permisos de escritura en el FTP para poder ejecutar dicha vulnerabilidad.

Una vez preparada la maqueta, un Windows 2000 SP4 con el FTP IIS 5.0 habilitado con el usuario anónimo con permisos de escritura, ejecutamos el nse del nmap para comprobar si el sistema lo detectaba como vulnerable y de esta forma poder saber que máquinas eran vulnerables.

Al ejecutarlo nos llevamos el primer chasco: el nse que se ofrecía en las webs de seguridad nos decía que el servidor no era vulnerable. Mirando el código vimos que el nse comprobaba si las respuestas del servidor se correspondían con la firma típica del FTP IIS. Si esto era cierto, se intentaba conectar con usuario anónimo y escribir un directorio en el FTP, tras cuya escritura (y por tanto vulnerable el FTP) eliminaba el directorio creado y notificaba que el servidor es vulnerable. No obstante, en nuestro caso nos indicaba que no lo era:

# nmap -p 21 -sV 192.168.244.129 --script=IIS-FTP --script-trace

Starting Nmap 5.00 ( http://nmap.org ) at 2009-09-01 22:12 CEST
NSOCK (0.3340s) nsock_loop() started (timeout=50ms). 0 events pending
...
NSOCK (5.3400s) nsock_loop() started (timeout=50ms). 0 events pending
NSE: TCP 192.168.244.1:54540 > 192.168.244.129:21 | CLOSE
Interesting ports on 192.168.244.129:
PORT STATE SERVICE VERSION
21/tcp open ftp Microsoft ftpd 5.0

Mirando y analizando detenidamente paso por paso el código del nse, vemos que la negociación entre el nmap y el servidor FTP no coincide con los mensajes que está devolviendo el servidor FTP al iniciar la conexión. Esta es la razón de que indicase que no era vulnerable cuando realmente cumplía todos los requisitos. Además, hay que tener en cuenta que un servidor que respondiese con una firma modificada por el administrador no cumpliría con los requisitos del script y nos diría que no era vulnerable (de ahí la importancia de modificar la “imagen pública” de un servidor web). Por último, el script estaba preparado para entrar con cuenta anónima con permisos de escritura, pero lógicamente podría tratarse de un FTP con cuentas con permisos de escritura, pero que tuviera la cuenta anónima deshabilitada. Por todo ello el nse ofrecido no era una solución adecuada.

Analizando alternativas, llegamos a la conclusión de que con un simple bucle que usaras las marcas de la firma (-sV) cuya firma fuera “Microsoft ftpd” sería un servidor vulnerable a este fallo de seguridad. Un vez ejecutado y obtenido que servidores eran vulnerables podríamos notificar a los administradores que máquinas tenían el fallo de seguridad y estos comprobaran que usuarios tenían permisos de escritura para revocarlos temporalmente. El script empleado fue el siguiente:

#!/bin/sh 
[ $# -eq 1 ] || { echo "USAGE: $0 RED" && exit 1; } 
for i in `seq 1 254`; 
do 
        RES=`nmap -p 21 -sV $1.$i | grep -i 'Microsoft ftpd' 2> /dev/null` 
        [ "$RES" = "" ] || { echo "$1.$i" >> "vulnerable_FTP.txt"; } 
done

Su ejecución sobre la red virtual de pruebas fue la siguiente:

$ ./miscript.sh 192.168.244
$ cat vulnerable_FTP.txt
192.168.244.129

Una vez tenemos un mecanismo que nos permite detectar los servidores vulnerables, nos dedicamos a analizar cual era el funcionamiento del exploit. Tal como pensábamos, son necesarios permisos de escritura para ir sobrescribiendo la pila hasta tenerla justo donde el atacante quiere, y modificar entonces la dirección de vuelta. Como se ha comentado al principio del artículo existen tres versiones del exploit para el FTP IIS 5.0: una deniega el servicio, otra crea un usuario y la última obtiene una shell.

Así que nos ponemos manos a la obra y cogemos el exploit que permite crear una shell remota. Ejecutamos el exploit sobre nuestra máquina virtual, y en este caso parece que funciona ya que se ejecuta correctamente… pero no. Llega la segunda sorpresa del día: el exploit tampoco en este caso funciona, ni tampoco las otras versiones del exploit. Ni usuario, ni shell ni denegación de servicios… el exploit sencillamente no funciona. ¿Por qué?

Lo veremos mañana, en la siguiente entrada. Permanezcan atentos.

Buffer Overflows y protección del kernel: práctica (II)

Para acabar con esta serie de entradas sobre los ataques de buffer overflow, y una vez explicadas las bases, vamos a ejecutar el programa que vimos en la entrada anterior varias veces, para ver cual es la posición del EPI (dirección de la siguiente línea a ejecutar):

user1@base:~/Desktop> for i in `seq 1 5`; do ./prueba | grep Premio; done
Premio: Valor: 0x29 PosMem: 0xBFFFF04C
Premio: Valor: 0x29 PosMem: 0xBFFFF04C
Premio: Valor: 0x29 PosMem: 0xBFFFF04C
Premio: Valor: 0x29 PosMem: 0xBFFFF04C
Premio: Valor: 0x29 PosMem: 0xBFFFF04C

[Read more…]

Buffer Overflows y protección del kernel: práctica (I)

Tras el pequeño rollo teórico que vimos ayer, hoy toca entrar de lleno en las cuestiones prácticas, que espero que aclaren todas las dudas que ayer pudieran surgir. Veamos el siguiente trozo de código en C de 4 líneas. La primera instrucción asigna el valor 9 a la variable X, la segunda llama a una función, la tercera le asigna a X el valor 1 y la siguiente línea mostrará el valor de X:

x = 9;
funct(5,6,7);
x = 1;
printf("El valor de X es: %d\n", x);

La función lo que va a hacer es obtener la dirección donde se guarda el EIP (dirección de la siguiente
línea a ejecutar), de tal forma que modificaremos su contenido para que en vez de saltar a la
siguiente línea que sería “x = 1″, salte al “printf” mostrando que x es igual a 9, y no 1 que sería lo esperado.

Antes de mostrar el código completo recordar un par de cosas:

  • En relación con los punteros, recordar que si yo declaro un puntero “int *p;”, para modificar la dirección donde apunta debo hacer “p = dirección;”, y a su vez, para modificar el valor (el contenido) del dato donde apunta el puntero se realiza “*p = dato;”.
  • La segunda es que la memoria se numera por bytes (8 bits) y está agrupada por palabras, siendo una palabra 32 bits es decir, 4 bytes. Por tanto, un registro que guarde una dirección de memoria ocupa una palabra, es decir 4 bytes (32 bits). Por último, recordar que un char ocupa 1 byte, y en una palabra caben 4 chars.

Con esto, pasemos al código:

#include <stdio.h>
void funct(int a, int b, int c){
     int i, j;
     char buffer[4];
     char *ret;
     buffer[0] = 'A';
     buffer[1] = 'B';
     buffer[2] = 'C';
     buffer[3] = 'D';
     for(i = 0; i < (9*4); i += 4){
          ret = buffer + i;
          if( i == 0){
               printf("Vector:\n");
               for( j=0; j < 4; j++){
                    printf("Valor: 0x%X PosMem: 0x%X\n", *ret, ret);
                    ret += 1;
               }
               printf("-----------\n");
          }
          else{
               printf("Valor: 0x%X PosMem: 0x%X\n", *ret, ret);
          }
     }
     // Aquí es donde esta la fiesta
     // Retrocedemos hasta donde esta el EIP

     ret -= 12;

     // Modificamos la dirección de vuelta y le sumamos 7 
     // que es la siguiente instrucción

     *ret += 7;
     printf("Premio: Valor: 0x%X PosMem: 0x%X\n", *ret, ret);
}

int main(void){
     int x;
     x = 9;
     funct(4,5,6);
     x = 1;
     printf("El valor de X es: %d\n", x);
     return 0;
}

Recapitulemos. Como vemos, asignamos 9 a la variable X. La siguiente instrucción llama a la función pasándole los números 4, 5 y 6 como parámetros. Por tanto, apilará el valor 6, 5 y 4. A continuación apila el valor del EIP que es el valor de la siguiente instrucción a ejecutar, que en nuestro caso es la instrucción “x = 1″. Encima del EIP apilara el valor del EBP de la pila actual, y sobre éste las variables locales de la función.

Dentro de la función apilamos variables locales para los bucles así como un vector de 4 caracteres “ABCD” o lo que es lo mismo en hexadecimal “0x41 0x42 0x43 0x44″, el cual ocupa una palabra (4 x char (1 byte) = 4 bytes = 32 bits).

Una vez realizadas estas operaciones, se muestra el contenido de la pila de la función seguido de la posición de memoria donde se guardan el dato, que corresponde con lo explicado en teoría. Ya por último se mueve el puntero “ret” a la posición donde se guarda el EIP (próxima linea a ejecutar) y se modifica con un valor de tal forma que cambia la posición de la siguiente instrucción a ejecutar, que seria “x = 1”, por la instrucción “printf”. Compilamos y ejecutamos el programa:

user1@base:~/Desktop> gcc -ggdb --no-stack-protector prueba.c -o prueba
user1@base:~/Desktop> ./prueba
Vector:
Valor: 0x41 PosMem: 0xBF869098
Valor: 0x42 PosMem: 0xBF869099
Valor: 0x43 PosMem: 0xBF86909A
Valor: 0x44 PosMem: 0xBF86909B
--------
Valor: 0x4 PosMem: 0xBF86909C
Valor: 0x4 PosMem: 0xBF8690A0
Valor: 0xFFFFFFA4 - PosMem: 0xBF8690A4
Valor: 0xFFFFFFD8 - PosMem: 0xBF8690A8
Valor: 0x22 - PosMem: 0xBF8690AC
Valor: 0x4 PosMem: 0xBF8690B0
Valor: 0x5 PosMem: 0xBF8690B4
Valor: 0x6 PosMem: 0xBF8690B8
Premio: Valor: 0x29 PosMem: 0xBF8690AC
El valor de X es: 9

Analicemos el resultado de la ejecución. Comienza mostrando la cima de la pila que es el vector de caracteres (char) de nombre “buffer”, donde podemos ver que las posiciones son consecutivas (claro, un char ocupa un byte). Posteriormente muestra los datos de 4 en 4 bytes (una palabra), puesto que lo que nos interesa son los registros que guardan direcciones de memoria que ocupan 32 bits (4 bytes, una palabra). Se muestra un
registro sin importancia (0xBF8690A4) que se emplea en la ejecución de los bucles de la función, y luego hay dos registros en la posición 0xBF8690A8 y 0xBF8690AC (nos interesa el último). A continuación vienen 3 registros que son el valor 4, 5 y 6 que son los valores que hemos pasado a la función en su llamada.

Como podemos imaginar, el dato que hay en la posición de memoria 0xBF8690AC cuyo valor es 0x22 corresponde con el EIP (dirección de la siguiente instrucción a ejecutar una vez termine la función). Por tanto lo que hacemos es ir a dicha posición de memoria, y modificarla incrementando su valor en 7 para que salte a la posición de memoria donde nosotros queremos: la instrucción “printf”. ¿Pero, cómo sabía que tenia que sumarle 7? Veamos:

user1@base:~/Desktop> gdb -q prueba
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disassemble main
0x080484ee <main+0>: lea 0x4(%esp),%ecx
...
0x080484ff <main+17>: movl $0x9,0x8(%ebp)
...
0x0804851d <main+47>: call 0x8048404 <funct>
0x08048522 <main+52>: movl $0x1,-0x8(%ebp)
0x08048529 <main+59>: mov -0x8(%ebp),%eax
...

¡No salgais corriendo! Fijaos bien… hay una llamada (main+17) que mueve al registro el valor 9 ($0x9), posteriormente llama a la función (main+47) y la siguiente línea de código es la misma que la de (main+17), pero esta vez le asigna el valor 1 ($0x1). Por tanto, claramente (main+17) es x = 9 y (main+52) es x = 1.

Por lo que si la instrucción “x = 1” se encuentra en la posición de memoria 0x08048522, la siguiente línea de ejecución 0x08048529 será el printf. Si restamos las posiciones de memoria obtenemos el 7 empleado en el programa. Pero es más, fijaros bien, ¿en qué terminan las posiciones de memoria de estas dos líneas de código? En 22 y en 29 ¿Os suenan? Fijaos lo que mostraba el programa en su ejecución:

...
Valor: 0x22 PosMem: 0xBF8690AC
...
Premio: Valor: 0x29 PosMem: 0xBF8690AC

En la próxima entrada, una vez vistos los detalles técnicos y teóricos de los buffer overflow, pasaremos a comentar brevemente una de las principales protecciones del kernel de Linux frente a este tipo de ataques. Hasta entonces, buen fin de semana a todos.