Saltar al contenido

Ataques de desbordamiento de pila (Stack Overflow)

Hoy me gustaría escribir sobre un tema del que se habla mucho y se entiende poco, los ataques de desbordamiento de pila. Seguramente os suene cuando se detecta una vulnerabilidad y se suelta la coletilla “un atacante podría ejecutar código arbitrario”.

Este tipo de vulnerabilidades o ataques aprovechan errores de codificación y la estructura de la memoria en los ordenadores para «engañar» al sistema. De esta manera, logran que el ordenador, en lugar de ejecutar las instrucciones previstas y autorizadas, ejecute instrucciones sin control que pueden ser maliciosas.

Arquitectura de la memoria

La memoria de un ordenador no es simplemente un espacio donde se almacenan datos al azar. De hecho, tiene zonas específicamente designadas para diferentes tipos de información. Una de estas zonas es la «pila» (o stack), que se complementa con otra zona conocida como el «heap» o memoria dinámica.

La pila se usa para almacenar datos temporales, como variables locales y llamadas a funciones, mientras que el heap se utiliza para datos que deben durar más tiempo o que no se sabe cuánto espacio necesitarán al principio. Ambas trabajan juntas para que el programa gestione su memoria de forma eficiente.

Cuando hablamos de la pila, es esencial entender que funciona bajo un sistema LIFO (Last In, First Out). Es como un montón de platos: el último plato que colocas encima es el primero que tomas cuando los necesitas.

Cada vez que un programa invoca a una función, se crea algo llamado «stack frame». Aquí es donde las cosas se ponen interesantes. Este marco es fundamental para pasar argumentos a procedimientos y funciones y para mantener nuestras variables locales en orden. La pila se va llenando a medida que definimos más y más variables. Estas variables se almacenan en un formato peculiar llamado «little-endian».

Sin embargo, la cosa se complica porque las direcciones de retorno de las funciones también se almacenan en la pila. Si una variable ocupa más espacio del debido, puede “desbordarse” y sobrescribir estas direcciones. Esto es peligroso porque un atacante podría aprovechar este error para hacer que el programa ejecute comandos no deseados.

No es ninguna sorpresa que los ataques de «buffer overflow», incluyendo el desbordamiento de pila y de heap, sean técnicas muy explotadas, aunque no es tan sencillo conseguir expoits para ejecutar con garantías. Estos errores en la gestión de memoria pueden ser puertas abiertas a vulnerabilidades graves en nuestros sistemas. Por lo tanto, la prevención y la conciencia al escribir código son esenciales para mantener nuestros programas y sistemas seguros.

Los Registros en la Mirilla: EIP, ESP y EBP en Arquitectura x86

Adentrándonos un poco mundo de la arquitectura x86, comprender cómo los registros juegan un papel en la gestión del sistema y de los programas es importante. Aunque es posible generar diferentes códigos en ensamblador dependiendo del compilador y del lenguaje de programación (tomando ANSI C como ejemplo), existe una consistencia en el uso de ciertos registros e instrucciones entre los compiladores más prevalentes.

Hagamos una pausa aquí y profundicemos en tres registros que suelen destacar en este entorno: EIP, ESP y EBP.

  • EIP (Extended Instruction Pointer): Este registro es algo así como nuestro GPS en el código. Contiene la dirección de la próxima instrucción que la CPU deberá ejecutar. Cuando una función (digamos, la función A) llama a otra función (función B), la dirección siguiente (a la que se debe volver después de divertirse en la función B) se almacena cuidadosamente en la pila. Al retornar de la función B, la CPU recoge esta dirección y la sitúa en EIP, determinando así el rumbo del programa.
  • ESP (Extended Stack Pointer): Este es el fiel guardian de la cima de nuestra pila. Si imaginamos la pila como una torre, ESP apunta siempre a la cima, indicándonos dónde se encuentra el último valor añadido. No olvidemos que la pila tiene su propio estilo, creciendo de manera inversa, es decir, decreciendo en dirección de memoria a medida que añadimos más platos a nuestra torre de datos.
  • EBP (Extended Base Pointer): EBP, por otro lado, nos indica dónde comienza esta torre, señalando la base de la pila. Dado que la pila crece de manera invertida, este será el valor de dirección más grande dentro de ella.

Además, un guiño al registro EAX, que frecuentemente se utiliza como una especie de mochila auxiliar, facilitando la movilización de valores entre variables mientras se ejecuta una función.

Entendiendo estos registros, nos adentramos en la coreografía de la ejecución de un programa: EIP nos guía a través del flujo de control, ESP y EBP gestionan nuestra pila con maestría, y registros como EAX ofrecen su apoyo logístico.

Construir un entendimiento robusto de estos conceptos no solo enriquece nuestro conocimiento como programadores, sino que también fortalece nuestras habilidades para escribir código seguro y eficiente. Mantente atento para más inmersiones profundas en el vasto océano de la programación a bajo nivel y, mientras tanto, ¡sigue codificando!

Gestión de la Pila y Coordinación de Funciones: Un Vistazo Rápido

En el ámbito de la memoria, específicamente en la gestión de la pila, nos encontramos con el stack frame, una zona de memoria definida entre dos registros cruciales: EBP y ESP, delineando así la región de memoria que una función asigna en la pila.

La función POP asigna al registro el valor ubicado en la posición de memoria a la que apunta ESP, desplazando posteriormente el ESP, mientras que la función PUSH posiciona un valor o registro en la cima de la pila y ajusta ESP acordemente. Estas funciones modifican el ESP por 4 en arquitecturas de 32 bits, pero en direcciones opuestas, garantizando el manejo adecuado de los datos en la pila.

Por otro lado, al sumergirnos en el llamado y retorno de funciones, notamos el protagonismo de CALL, LEAVE, y RTN. CALL realiza automáticamente una serie de acciones para facilitar la llamada a otra función, implicando un PUSH de EIP y un movimiento de EIP a la dirección de la función llamada. Por otro lado, LEAVE nos prepara para salir de una rutina, reposicionando la cabeza de la pila y restaurando el valor original de EBP para señalar adecuadamente al siguiente punto de retorno de la llamada. Finalmente, RTN marca el término de una función actualizando EIP con el valor de ESP, en esencia, realizando un POP de EIP. Este trío de funciones coreografía un manejo sistemático del flujo de control y la memoria durante la ejecución del programa.

Ambos puntos, la gestión de la pila y el proceso de llamada/retorno de funciones, se entrelazan estrechamente para formar la base de la ejecución programática, influenciando cómo los datos y las instrucciones se gestionan y navegan en tiempo de ejecución. Conocer estos mecanismos fortalece nuestras competencias en desarrollo de código y en la construcción de sistemas eficientes y seguros.

Ejemplo desbordamiento de pila

Entorno de Desarrollo:

  • Sistema operativo: Linux Ubuntu 18.04 LTS, operando en una máquina virtual.
  • Virtualización: VMware Workstation 15.
  • Compilador C: GCC 7.4.0
  • Depurador: GDB 8.1

Escenario: Desarrollamos un programa en C que realiza autenticación de usuario basada en una combinación de nombre de usuario y contraseña ingresados por el usuario.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int authenticate(char* username, char* password) {
    char stored_password[12] = "securePass";
    char input_password[12];
    strcpy(input_password, password); // [VULNERABILIDAD]
    return strcmp(stored_password, input_password) == 0;
}
int main(int argc, char** argv) {
    if(argc != 3) {
        printf("Usage: %s <username> <password>\n", argv[0]);
        return 1;
    }
    if(authenticate(argv[1], argv[2])) {
        printf("Access Granted\n");
    } else {
        printf("Access Denied\n");
    }
    return 0;
}
  • Descripción del Código: Este programa se diseñó para autenticar usuarios comparando una contraseña ingresada con una almacenada en el código (stored_password).

Vulnerabilidad: La función strcpy() no verifica los tamaños de los búferes, pudiendo provocar un desbordamiento de búfer si la contraseña ingresada supera el espacio asignado para input_password.

Exploración:

Funcionamiento Esperado: Si el usuario ingresa una contraseña de hasta 11 caracteres (12 contando el caracter nulo de terminación), el programa compara está con stored_password y otorga o deniega el acceso.

./authenticator user securePass
Access Granted

Desbordamiento de Pila: Si se introduce una contraseña que excede los 11 caracteres permitidos, se sobrescribirá la memoria adyacente al búfer input_password.

./authenticator user AAAAAAAAAAAAAAAABBBBCCCC

Utilizando un depurador, como GDB, y un disasembler, podemos ver que la memoria contigua se modifica y cómo los registros y la pila se ven afectados durante la ejecución del programa.

Exploit Potencial:

Un atacante podría usar el desbordamiento de pila para sobrescribir la dirección de retorno en la pila y desviar la ejecución del programa hacia un código malicioso (shellcode). Dependiendo del sistema y su configuración, este exploit podría permitir la ejecución de código arbitrario, llevando a escenarios como una escalada de privilegios si el programa se ejecuta con permisos elevados.

Conclusión

En el marco de una era digital, los desbordamientos de pila, emblemáticos de un problema endémico y raíz de innumerables brechas de seguridad, continúan siendo persistentemente subestimados en cuanto a su potencial devastador en el ámbito de la seguridad informática. El ejemplo proporcionado no es simplemente una vulnerabilidad; es un espectro en la máquina, una reiterada pesadilla .

El desbordamiento de pila es más que un simple error técnico: es un sintomático recordatorio de una cultura de desarrollo que, a menudo, elige la funcionalidad y la prisa sobre la seguridad y la robustez. Al permitir que un elemento tan fundamental de la seguridad informática quede tan crónicamente desatendido, ¿qué mensaje estamos enviando sobre la integridad de nuestros sistemas digitales?

Dentro de esta brecha técnica se esconde un agujero negro de posibilidades para los actores maliciosos, brindando la llave maestra para manipular, controlar y explotar nuestras infraestructuras digitales.

La adopción proactiva de prácticas de codificación segura, la educación continua en seguridad para desarrolladores, y una intransigente postura de «seguridad primero» no son opciones: son imperativos irrenunciables para la construcción de un futuro digital en el que podamos, con integridad y confianza, vivir, operar y prosperar.


Juan Ibero

Inmerso en la Evolución Tecnológica. Ingeniero Informático especializado en la gestión segura de entornos TI e industriales, con un profundo énfasis en seguridad, arquitectura y programación. Siempre aprendiendo, siempre explorando.

Etiquetas:

2 comentarios en «Ataques de desbordamiento de pila (Stack Overflow)»

    1. Gracias Iñigo.
      Espero que las estrategias compartidas te sean útiles contra los bugs en las cooperativas de Zimbabwe. Como en «Starship Troopers», cada pequeña batalla cuenta en nuestra guerra global contra las amenazas y errores digitales.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *