Saltar al contenido

Ataques de desbordamiento de pila (Stack Overflow)

Compartir en:

La memoria de un ordenador no es simplemente un vasto 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.

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. Y, al igual que un librero que se llena de libros, 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».

Pero no todo es tan sencillo. Las direcciones de retorno de nuestras funciones también encuentran su hogar en la pila. Si no somos cautelosos, y una variable ocupa más espacio del que se le ha asignado, puede «desbordar» y sobrescribir estas direcciones. Esto es particularmente problemático, ya que un usuario con intenciones maliciosas podría explotar este desbordamiento para hacer que nuestro programa ejecute comandos que no debería.

No es ninguna sorpresa que los ataques de «buffer overflow», incluyendo el desbordamiento de pila y de heap, sean tácticas favoritas para los hackers. 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 en el fascinante mundo de la arquitectura x86, es fundamental comprender cómo los registros juegan un papel crucial en la gestión del sistema y de los programas. 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 notoria en el uso de ciertos registros e instrucciones entre los compiladores más prevalentes.

Hagamos una pausa aquí y profundicemos en tres registros que brillan con luz propia 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 esta 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 en el edificio de nuestros sistemas digitales.

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.

Estos olvidados desbordamientos de pila no son solo errores, son portales abiertos a mundos donde la seguridad y la privacidad son sacrificadas en el altar del descuido y la negligencia programática. Esta no es una hipérbole de la condición de la seguridad en TI, sino un espejo que refleja los rostros de todos los individuos cuya información ha sido saqueada, manipulada y explotada debido a una simple falla que hemos conocido durante décadas.

Esta devastación potencial encarna una crítica feroz hacia los arquitectos de nuestro mundo digital, forzándonos a preguntar: ¿Hasta cuándo permitiremos que la familiaridad engendre complacencia? La seguridad no es un estado; es un proceso. Y mientras naveguemos por este mar proceloso de la era digital, debemos erradicar estos antiguos demonios de fallos de seguridad conocidos (como el desbordamiento de pila), para que no sigan hundiendo los barcos que construimos con tanto esfuerzo.

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.

Compartir en:
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 *