Niezainicjalizowane zmienne
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Podstawowe informacje
Główna idea tutaj to zrozumieć, co się dzieje z niezainicjalizowanymi zmiennymi, ponieważ będą one miały wartość, która już znajdowała się w przypisanej im pamięci. Przykład:
- Function 1:
initializeVariable: Deklarujemy zmiennąxi przypisujemy jej wartość, powiedzmy0x1234. Ta operacja jest podobna do zarezerwowania miejsca w pamięci i zapisania tam konkretnej wartości. - Function 2:
useUninitializedVariable: Tutaj deklarujemy kolejną zmiennąy, ale nie przypisujemy jej żadnej wartości. W C niezainicjalizowane zmienne nie są automatycznie ustawiane na zero. Zamiast tego zachowują wartość, która ostatnio była zapisana pod ich adresem pamięci.
Kiedy uruchomimy te dwie funkcje sekwencyjnie:
- W
initializeVariablexotrzymuje wartość (0x1234), która zajmuje określony adres pamięci. - W
useUninitializedVariableyjest deklarowane, ale nie otrzymuje wartości, więc zajmuje miejsce w pamięci zaraz pox. Ponieważynie zostało zainicjalizowane, kończy „dziedziczyć” wartość z tej samej lokacji pamięci użytej przezx, ponieważ to była ostatnia zapisana tam wartość.
To zachowanie ilustruje kluczową koncepcję w programowaniu niskopoziomowym: zarządzanie pamięcią jest kluczowe, a niezainicjalizowane zmienne mogą prowadzić do nieprzewidywalnego zachowania lub luk bezpieczeństwa, ponieważ mogą niezamierzenie przechowywać wrażliwe dane pozostawione w pamięci.
Niezainicjalizowane zmienne na stosie mogą stwarzać kilka zagrożeń bezpieczeństwa, takich jak:
- Data Leakage: Wrażliwe informacje, takie jak hasła, klucze szyfrowania lub dane osobowe, mogą zostać ujawnione, jeśli znajdują się w niezainicjalizowanych zmiennych, co pozwala atakującym potencjalnie odczytać te dane.
- Information Disclosure: Zawartość niezainicjalizowanych zmiennych może ujawnić szczegóły dotyczące układu pamięci programu lub jego wewnętrznych działań, pomagając atakującym w opracowaniu ukierunkowanych exploitów.
- Crashes and Instability: Operacje z użyciem niezainicjalizowanych zmiennych mogą skutkować niezdefiniowanym zachowaniem, prowadząc do awarii programu lub nieprzewidywalnych rezultatów.
- Arbitrary Code Execution: W niektórych scenariuszach atakujący mogą wykorzystać te luki do zmiany przepływu wykonania programu, umożliwiając wykonanie dowolnego kodu, co może obejmować zagrożenia związane z remote code execution.
Przykład
#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;
}
Jak to działa:
initializeAndPrintFunction: Ta funkcja deklaruje zmienną całkowitąinitializedVar, przypisuje jej wartość100, a następnie wypisuje zarówno adres pamięci, jak i wartość zmiennej. Ten krok jest prosty i pokazuje, jak zachowuje się zainicjalizowana zmienna.demonstrateUninitializedVarFunction: W tej funkcji deklarujemy zmienną całkowitąuninitializedVarbez jej inicjalizacji. Gdy próbujemy wypisać jej wartość, wynik może pokazać losową liczbę. Ta liczba reprezentuje dane, które wcześniej znajdowały się w tej lokalizacji pamięci. W zależności od środowiska i kompilatora rzeczywisty output może się różnić, a czasem, ze względów bezpieczeństwa, niektóre kompilatory mogą automatycznie zainicjalizować zmienne zerami — jednak nie należy na to polegać.mainFunction: Funkcjamainwywołuje obie powyższe funkcje jedna po drugiej, pokazując kontrast między zmienną zainicjalizowaną a niezainicjalizowaną.
Praktyczne wzorce eksploatacji (2024–2025)
Klasyczny “read-before-write” bug pozostaje istotny, ponieważ nowoczesne mechanizmy (ASLR, canaries) często opierają się na poufności. Typowe powierzchnie ataku:
- Partially initialized structs copied to userland: Kernel or drivers frequently
memsetonly a length field and thencopy_to_user(&u, &local_struct, sizeof(local_struct)). Padding and unused fields leak stack canary halves, saved frame pointers or kernel pointers. If the struct contains a function pointer, leaving it uninitialized may also allow controlled overwrite when later reused. - Uninitialized stack buffers reused as indexes/lengths: An uninitialized
size_t len;used to boundread(fd, buf, len)may give attackers out-of-bounds reads/writes or allow bypassing size checks when the stack slot still contains a large value from a prior call. - Compiler-added padding: Even when individual members are initialized, implicit padding bytes between them are not. Copying the whole struct to userland leaks padding that often contains prior stack content (canaries, pointers).
- ROP/Canary disclosure: If a function copies a local struct to stdout for debugging, uninitialized padding can reveal the stack canary enabling subsequent stack overflow exploitation without 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;
}
Środki zaradcze i opcje kompilatora (pamiętaj przy omijaniu)
- Clang/GCC auto-init: Nowsze toolchainy udostępniają
-ftrivial-auto-var-init=zerolub-ftrivial-auto-var-init=pattern, wypełniając każdą automatyczną (stack) zmienną przy wejściu do funkcji zerami lub wzorcem poison (0xAA / 0xFE). To zamyka większość uninitialized-stack info leaks i utrudnia exploitację przez przekształcenie sekretów w znane wartości. - Linux kernel hardening: Jądra zbudowane z
CONFIG_INIT_STACK_ALLlub nowszymCONFIG_INIT_STACK_ALL_PATTERNzerują/wzorcują każdą pozycję stosu przy wejściu do funkcji, wymazując canaries/wskaźniki które w przeciwnym razie leak. Szukaj dystrybucji dostarczających jądra zbudowane Clangiem z tymi opcjami włączonymi (częste w konfiguracjach hardening 6.8+). - Opt-out attributes: Clang pozwala teraz na
__attribute__((uninitialized))na określonych lokalnych/structs, aby utrzymać obszary krytyczne wydajnościowo nieinicjalizowane nawet gdy globalne auto-init jest włączone. Przejrzyj takie adnotacje uważnie — często oznaczają celowy attack surface dla side channels.
Z perspektywy atakującego, wiedza czy binarka została zbudowana z tymi flagami decyduje, czy stack-leak primitives są wykonalne, czy musisz pivotować do heap/data-section disclosures.
Szybkie znajdowanie błędów uninitialized-stack
- Compiler diagnostics: Kompiluj z
-Wall -Wextra -Wuninitialized(GCC/Clang). Dla kodu C++clang-tidy -checks=cppcoreguidelines-init-variablesautomatycznie naprawi wiele przypadków do zero-init i jest przydatny do wykrywania pominiętych lokalnych zmiennych podczas audytu. - Dynamic tools:
-fsanitize=memory(MSan) w Clang lub Valgrind z--track-origins=yesniezawodnie oznaczają odczyty nieinicjalizowanych bajtów stosu podczas fuzzingu. Instrumentuj test harnessy tymi narzędziami, aby ujawnić subtelne padding leaks. - Grepping patterns: W przeglądach szukaj wywołań
copy_to_user/writecałych struktur, lubmemcpy/senddanych ze stosu, gdzie tylko część struktury jest ustawiona. Zwróć szczególną uwagę na ścieżki błędów, gdzie inicjalizacja jest pomijana.
ARM64 Przykład
To w ARM64 w ogóle się nie zmienia, ponieważ zmienne lokalne też są zarządzane na stosie — możesz sprawdzić ten przykład, gdzie to jest pokazane.
Referencje
- CONFIG_INIT_STACK_ALL_PATTERN documentation
- GHSL-2024-197: GStreamer uninitialized stack variable leading to function pointer overwrite
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


