ATAQUE “SMASH THE STACK” SENCILLO
El objetivo de los ejercicios de este apartado es lograr que al ejecutar el programa se imprima el mensaje “you win!”, para lo cual se seguirán diferentes estrategias.
Última actualización
El objetivo de los ejercicios de este apartado es lograr que al ejecutar el programa se imprima el mensaje “you win!”, para lo cual se seguirán diferentes estrategias.
Última actualización
Artículo copiado de la Fundación Sadosky escrito por Teresa Alberto.
Se declaran dos variables locales cookie
(numérica) y buf
(array de char o, en C, string). gets()
toma datos de la entrada estándar y los guarda en buf
, cookie
no se inicializa pero se evalúa si su valor es 0x41424344
(“ABCD” en ASCII), si es así se imprime el mensaje ganador.
Compilamos y ejecutamos el programa con los flags necesarios y vemos su funcionamiento:
Se imprime la dirección de las variables locales y se espera una entrada del usuario. Ingresamos cualquier entrada y el programa finaliza.
Antes de ejecutar gets(buf)
el mapa de la pila del programa es el siguiente:
Si pensamos de manera simplificada el punto de entrada de un binario, dentro de _start
se hace un call main()
. La instrucción call
apila la dirección de retorno (para que luego de ejecutar main()
se retorne a _start
) y se apila el frame pointer actual (ebp
), cuyo valor se almacena porque se va a adecuar al frame de main()
. Acto seguido se pasa el control a la función llamada main()
que apila primero la variable local cookie
y después buf
.
Si tenemos en mente la convención del llamado a funciones es posible saber en qué parte de la pila se encuentran los valores que nos interesan:
Como lo compilamos con gcc -g
, se puede desensamblar el programa intercalado con el código fuente con objdump -M intel -S stack1
.
lea vs mov:
En el programa aparece la instrucción lea
(en inglés Load Effective Address) que carga una dirección (dada por el operando fuente) en dónde indique el operando destino y funciona de manera similar al operador de dirección “&” en C. En el programa aparece de la siguiente forma: lea eax,[ebp-0x4]
. En esta instrucción se recupera la dirección de cookie
en la pila y se la almacena en eax
.
Por su uso similar a mov
, es posible confundirse. Con lea eax,[ebp-0x4]
no se dereferencia ebp-0x4
como puntero sino que sólo se está calculando la dirección de ebp-0x4
para almacenarla en el primer operando. En cambio una instrucción como mov eax,[ebp-0x4]
sí considera a su segundo operando un puntero y se copia el valor al que éste apunta.
Directivas de tamaño: DWORD.
En el ejemplo anterior la instrucción mov eax, DWORD PTR [ebp-0x4]
al especificar el segundo operando incluye una directiva de tamaño DWORD
que implica que lo que se debe copiar a eax
son 32 bits. Las diferentes directivas de tamaño están especificadas en el gráfico del manual de Intel a continuación:
Si lo pensaramos en C un char
es un BYTE
(8 bits), un short
es una WORD
(16 bits), un int
es una DOUBLE WORD
(32 bits) y un double
es un QUAD WORD
(64 bits).
gets()
?Consultamos man gets
:
Es una función que lee caracteres por entrada estándar pero no verifica la longitud de lo que almacena respecto al espacio del búfer donde se lo va a almacenar.
El ataque conocido como Smash the stack publicado por Aleph One en la revista Phrack consiste en corromper la pila de ejecución de un programa vulnerable escribiendo por fuera de los límites de un búfer. Este ataque consiste en aprovechar una vulnerabilidad del tipo “buffer overflow” o desbordamiento de un búfer almacenado en la pila. Un programa con una función vulnerable (del tipo gets()
que no chequea el tamaño de un búfer) permite escribir en el búfer más datos que los que éste puede contener. Si abusamos de la vulnerabilidad y escribimos datos que superan el tamaño del búfer logramos desbordarlo y escribir por fuera de los límites de ese bloque de memoria.
En este ataque básico el objetivo será a través de una corrupción de la pila modificar una variable local (cookie
con el valor 0x41424344
lograr imprimir el mensaje ganador). No obstante las posibilidades que brinda la escritura por fuera de los límites del búfer no se reducen a ello. Más adelante se verá cómo lo primordial que querremos escribir fuera de los límites del búfer va a ser información de control como la dirección de retorno.
Entonces en el Stack 1 la “corrupción” de la pila tendrá como objetivo particular sobreescribir la variable local cookie
con el valor 0x41424344
para que la condición que lo evalúa sea verdadera y se imprima el mensaje ganador “you win!”. Como se indicó la función gets(buf)
nunca evalúa la cantidad de caracteres de buf
por lo que es posible escribir la pila por fuera de los límites de buf
hasta sobreescribir cookie
con el valor deseado.
Para eso, primero calculamos la cantidad de caracteres exacta que debemos ingresar por entrada estándar.
Con el mapa de la pila en la memoria, entendemos que primero debemos ingresar un dato cualquiera hasta completar los 80 bytes de buf
y los siguientes 4 bytes van a sobreescribir el contenido de cookie
.
Como el caracter “A” en ASCII es un byte (\x41
) lo usamos de relleno 80 veces, seguido del valor de cookie
deseado.
Y logramos imprimir el mensaje ganador.
A medida que los exploits se complejicen será de utilidad armar un archivo en Python con el exploit que funcionará como input: exploit.py
.
Y al ejecutar el programa con ese input logramos el mismo resultado:
Si utilizamos gdb
a la hora de debugear el programa es importante tener en cuenta las diferencias en las direcciones al ejecutar un programa y al debugearlo con gdb
para lograr los resultados esperados.
Cuando debugeamos con gdb
es posible ingresar un input por stdin al programa vulnerable de la siguiente manera:
Gráficamente logramos el siguiente resultado:
Puede practicar con los siguientes ejemplos:
[1]. Aleph One. (Noviembre de 1996). Smashing the Stack for Fun and Profit. Phrack, 7. Disponible en: http://phrack.org/issues/49/14.html