Uninitialized Variables

Support HackTricks

Información Básica

La idea principal aquí es entender qué sucede con las variables no inicializadas, ya que tendrán el valor que ya estaba en la memoria asignada a ellas. Ejemplo:

  • Función 1: initializeVariable: Declaramos una variable x y le asignamos un valor, digamos 0x1234. Esta acción es similar a reservar un espacio en la memoria y poner un valor específico en él.

  • Función 2: useUninitializedVariable: Aquí, declaramos otra variable y pero no le asignamos ningún valor. En C, las variables no inicializadas no se establecen automáticamente en cero. En cambio, retienen el último valor que se almacenó en su ubicación de memoria.

Cuando ejecutamos estas dos funciones secuencialmente:

  1. En initializeVariable, x se le asigna un valor (0x1234), que ocupa una dirección de memoria específica.

  2. En useUninitializedVariable, y se declara pero no se le asigna un valor, por lo que toma el espacio de memoria justo después de x. Debido a que no se inicializa y, termina "heredando" el valor de la misma ubicación de memoria utilizada por x, porque ese es el último valor que estuvo allí.

Este comportamiento ilustra un concepto clave en la programación de bajo nivel: La gestión de memoria es crucial, y las variables no inicializadas pueden llevar a un comportamiento impredecible o vulnerabilidades de seguridad, ya que pueden contener involuntariamente datos sensibles que quedan en la memoria.

Las variables de pila no inicializadas podrían presentar varios riesgos de seguridad como:

  • Filtración de Datos: Información sensible como contraseñas, claves de cifrado o detalles personales pueden ser expuestos si se almacenan en variables no inicializadas, permitiendo a los atacantes potencialmente leer estos datos.

  • Divulgación de Información: El contenido de las variables no inicializadas podría revelar detalles sobre la disposición de la memoria del programa o las operaciones internas, ayudando a los atacantes a desarrollar exploits dirigidos.

  • Caídas e Inestabilidad: Las operaciones que involucran variables no inicializadas pueden resultar en un comportamiento indefinido, llevando a caídas del programa o resultados impredecibles.

  • Ejecución de Código Arbitrario: En ciertos escenarios, los atacantes podrían explotar estas vulnerabilidades para alterar el flujo de ejecución del programa, permitiéndoles ejecutar código arbitrario, lo que podría incluir amenazas de ejecución remota de código.

Ejemplo

#include <stdio.h>

// Function to initialize and print a variable
void initializeAndPrint() {
int initializedVar = 100; // Initialize the variable
printf("Initialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&initializedVar, initializedVar);
}

// Function to demonstrate the behavior of an uninitialized variable
void demonstrateUninitializedVar() {
int uninitializedVar; // Declare but do not initialize
printf("Uninitialized Variable:\n");
printf("Address: %p, Value: %d\n\n", (void*)&uninitializedVar, uninitializedVar);
}

int main() {
printf("Demonstrating Initialized vs. Uninitialized Variables in C\n\n");

// First, call the function that initializes its variable
initializeAndPrint();

// Then, call the function that has an uninitialized variable
demonstrateUninitializedVar();

return 0;
}

Cómo Funciona Esto:

  • Función initializeAndPrint: Esta función declara una variable entera initializedVar, le asigna el valor 100 y luego imprime tanto la dirección de memoria como el valor de la variable. Este paso es sencillo y muestra cómo se comporta una variable inicializada.

  • Función demonstrateUninitializedVar: En esta función, declaramos una variable entera uninitializedVar sin inicializarla. Cuando intentamos imprimir su valor, la salida puede mostrar un número aleatorio. Este número representa cualquier dato que estaba previamente en esa ubicación de memoria. Dependiendo del entorno y del compilador, la salida real puede variar, y a veces, por seguridad, algunos compiladores pueden inicializar automáticamente las variables a cero, aunque esto no debe ser confiable.

  • Función main: La función main llama a ambas funciones anteriores en secuencia, demostrando el contraste entre una variable inicializada y una no inicializada.

Ejemplo ARM64

Esto no cambia en absoluto en ARM64 ya que las variables locales también se gestionan en la pila, puedes ver este ejemplo donde se muestra esto.

Support HackTricks

Last updated