Variables no inicializadas
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Información básica
La idea central aquí es entender qué ocurre con las variables no inicializadas, ya que tendrán el valor que ya estaba en la memoria asignada a ellas. Ejemplo:
- Function 1:
initializeVariable: Declaramos una variablexy le asignamos un valor, digamos0x1234. Esta acción es equivalente a reservar un espacio en memoria y poner un valor específico en él. - Function 2:
useUninitializedVariable: Aquí declaramos otra variableypero no le asignamos ningún valor. En C, las variables no inicializadas no se ponen automáticamente a cero. En su lugar, conservan el valor que se almacenó por última vez en su dirección de memoria.
Cuando ejecutamos estas dos funciones de manera secuencial:
- En
initializeVariable, axse le asigna un valor (0x1234), que ocupa una dirección de memoria específica. - En
useUninitializedVariable,yse declara pero no se le asigna un valor, por lo que ocupa el espacio de memoria inmediatamente después dex. Al no inicializary, termina “heredando” el valor de la misma ubicación de memoria usada porx, porque ese es el último valor que había allí.
Este comportamiento ilustra un concepto clave en programación de bajo nivel: la gestión de memoria es crucial, y las variables no inicializadas pueden llevar a comportamientos impredecibles o vulnerabilidades de seguridad, ya que pueden contener sin querer datos sensibles dejados en memoria.
Las variables de pila no inicializadas podrían plantear varios riesgos de seguridad como:
- Data Leakage: Información sensible, como contraseñas, claves de cifrado o datos personales, puede exponerse si se almacena en variables no inicializadas, permitiendo a atacantes leer potencialmente esos datos.
- Information Disclosure: El contenido de variables no inicializadas podría revelar detalles sobre el layout de memoria del programa u operaciones internas, ayudando a atacantes a desarrollar exploits dirigidos.
- Crashes and Instability: Operaciones que involucran variables no inicializadas pueden resultar en comportamiento indefinido, provocando fallos del programa o resultados impredecibles.
- Arbitrary Code Execution: 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:
initializeAndPrintFunction: Esta función declara una variable enterainitializedVar, le asigna el valor100, y luego imprime tanto la dirección de memoria como el valor de la variable. Este paso es directo y muestra cómo se comporta una variable inicializada.demonstrateUninitializedVarFunction: En esta función declaramos una variable enterauninitializedVarsin inicializarla. Cuando intentamos imprimir su valor, la salida puede mostrar un número aleatorio. Este número representa los datos que previamente estaban en esa ubicación de memoria. Dependiendo del entorno y del compilador, la salida real puede variar, y a veces, por seguridad, algunos compiladores podrían inicializar automáticamente las variables a cero, aunque no se debe confiar en ello.mainFunction: La funciónmainllama a ambas funciones anteriores en secuencia, demostrando el contraste entre una variable inicializada y una no inicializada.
Patrones prácticos de explotación (2024–2025)
El clásico bug “read-before-write” sigue siendo relevante porque las mitigaciones modernas (ASLR, canaries) a menudo dependen del secreto. Superficies de ataque típicas:
- Partially initialized structs copied to userland: El padding y los campos sin usar pueden leak stack canary halves, saved frame pointers o kernel pointers. Si la struct contiene un function pointer, dejarlo sin inicializar también puede permitir un controlled overwrite cuando se reutilice más tarde.
- Uninitialized stack buffers reused as indexes/lengths: Un
size_t len;sin inicializar usado para acotarread(fd, buf, len)puede dar a los atacantes lecturas/escrituras fuera de los límites o permitir eludir comprobaciones de tamaño cuando la ranura de la pila todavía contiene un valor grande de una llamada previa. - Compiler-added padding: Incluso cuando miembros individuales están inicializados, los bytes de padding implícito entre ellos no lo están. Copiar toda la struct a userland leaks padding que a menudo contiene contenido previo de la pila (canaries, pointers).
- ROP/Canary disclosure: Si una función copia una struct local a stdout para depuración, el padding sin inicializar puede revelar el stack canary, permitiendo una explotación de stack overflow subsiguiente sin brute-force.
Minimal PoC pattern to detect such issues during review:
struct msg {
char data[0x20];
uint32_t len;
};
ssize_t handler(int fd) {
struct msg m; // never fully initialized
m.len = read(fd, m.data, sizeof(m.data));
// later debug helper
write(1, &m, sizeof(m)); // leaks padding + stale stack
return m.len;
}
Mitigaciones y opciones del compilador (ten en cuenta al evadir)
- Clang/GCC auto-init: Toolchains recientes exponen
-ftrivial-auto-var-init=zeroo-ftrivial-auto-var-init=pattern, rellenando todas las variables automáticas (stack) al entrar en la función con ceros o un patrón de poison (0xAA / 0xFE). Esto cierra la mayoría de los uninitialized-stack info leaks y hace la explotación más difícil al convertir secretos en valores conocidos. - Linux kernel hardening: Kernels compilados con
CONFIG_INIT_STACK_ALLo el más nuevoCONFIG_INIT_STACK_ALL_PATTERNinicializan a cero/por patrón cada slot del stack al entrar en la función, borrando canaries/pointers que de otro modo leakearían. Busca distros que distribuyan kernels compilados con Clang con estas opciones activadas (común en configuraciones de hardening 6.8+). - Opt-out attributes: Clang ahora permite
__attribute__((uninitialized))en locales/structs específicos para mantener zonas críticas para rendimiento sin inicializar incluso cuando el auto-init global está activado. Revisa dichas anotaciones cuidadosamente—con frecuencia marcan una attack surface deliberada para side channels.
Desde la perspectiva de un atacante, saber si el binario fue compilado con estas flags determina si stack-leak primitives son viables o si debes pivotar a heap/data-section disclosures.
Finding uninitialized-stack bugs quickly
- Compiler diagnostics: Compila con
-Wall -Wextra -Wuninitialized(GCC/Clang). Para código C++,clang-tidy -checks=cppcoreguidelines-init-variablesauto-arreglará muchos casos para zero-init y es útil para detectar locales omitidos durante una auditoría. - Dynamic tools:
-fsanitize=memory(MSan) en Clang o Valgrind’s--track-origins=yesmarcan de forma fiable lecturas de bytes de stack no inicializados durante fuzzing. Instrumenta harnesses de prueba con estas herramientas para sacar a la luz sutiles padding leaks. - Grepping patterns: En revisiones, busca llamadas a
copy_to_user/writeque envíen structs completos, omemcpy/sendde datos del stack donde solo parte del struct está inicializado. Presta especial atención a las rutas de error donde la inicialización se salta.
ARM64 Example
Esto no cambia en absoluto en ARM64 ya que las variables locales también se gestionan en el stack, puedes check this example were this is shown.
References
- CONFIG_INIT_STACK_ALL_PATTERN documentation
- GHSL-2024-197: GStreamer uninitialized stack variable leading to function pointer overwrite
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


