Uninitialized Variables

Nauka hakowania AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Podstawowe informacje

Podstawowym pomysłem tutaj jest zrozumienie, co dzieje się z niezainicjowanymi zmiennymi, ponieważ będą one miały wartość, która była już przypisana do pamięci. Przykład:

  • Funkcja 1: initializeVariable: Deklarujemy zmienną x i przypisujemy jej wartość, powiedzmy 0x1234. Ta czynność jest podobna do zarezerwowania miejsca w pamięci i umieszczenia w nim określonej wartości.

  • Funkcja 2: useUninitializedVariable: Tutaj deklarujemy inną zmienną y, ale nie przypisujemy jej żadnej wartości. W języku C niezainicjowane zmienne nie są automatycznie ustawiane na zero. Zamiast tego zachowują wartość, która była ostatnio przechowywana pod ich adresem pamięci.

Gdy uruchamiamy te dwie funkcje sekwencyjnie:

  1. W initializeVariable, x otrzymuje wartość (0x1234), która zajmuje określony adres pamięci.

  2. W useUninitializedVariable, y jest deklarowane, ale nie otrzymuje wartości, więc zajmuje miejsce w pamięci bezpośrednio po x. Ze względu na brak inicjalizacji y, kończy się "dziedziczeniem" wartości z tego samego miejsca w pamięci używanego przez x, ponieważ jest to ostatnia wartość, która tam była.

To zachowanie ilustruje kluczowe pojęcie w programowaniu niskopoziomowym: Zarządzanie pamięcią jest kluczowe, a niezainicjowane zmienne mogą prowadzić do nieprzewidywalnego zachowania lub podatności na zagrożenia bezpieczeństwa, ponieważ mogą nieumyślnie przechowywać wrażliwe dane pozostawione w pamięci.

Niezainicjowane zmienne stosu mogą stwarzać kilka zagrożeń dla bezpieczeństwa, takich jak:

  • Ujawnienie danych: Wrażliwe informacje, takie jak hasła, klucze szyfrowania lub dane osobowe, mogą być ujawnione, jeśli przechowywane są w niezainicjowanych zmiennych, umożliwiając potencjalnie atakującym odczytanie tych danych.

  • Ujawnienie informacji: Zawartość niezainicjowanych zmiennych może ujawnić szczegóły dotyczące układu pamięci programu lub operacji wewnętrznych, pomagając atakującym w opracowywaniu ukierunkowanych exploitów.

  • Awarie i niestabilność: Operacje związane z niezainicjowanymi zmiennymi mogą prowadzić do niezdefiniowanego zachowania, co może skutkować awariami programu lub nieprzewidywalnymi wynikami.

  • Wykonywanie arbitralnego kodu: W określonych scenariuszach atakujący mogą wykorzystać te podatności, aby zmienić przebieg wykonania programu, umożliwiając im wykonanie arbitralnego kodu, który może obejmować zagrożenia związane z wykonaniem kodu zdalnego.

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:

  • Funkcja initializeAndPrint: Ta funkcja deklaruje zmienną całkowitą initializedVar, przypisuje jej wartość 100, a następnie drukuje zarówno adres pamięci, jak i wartość zmiennej. Ten krok jest prosty i pokazuje, jak zachowuje się zmienna zainicjowana.

  • Funkcja demonstrateUninitializedVar: W tej funkcji deklarujemy zmienną całkowitą uninitializedVar bez inicjalizacji. Gdy próbujemy wydrukować jej wartość, wynik może pokazać losową liczbę. Ta liczba reprezentuje dane, które wcześniej znajdowały się pod tym adresem pamięci. W zależności od środowiska i kompilatora, rzeczywisty wynik może się różnić, a czasami, dla bezpieczeństwa, niektóre kompilatory mogą automatycznie inicjować zmienne na zero, chociaż nie należy polegać na tym.

  • Funkcja main: Funkcja main wywołuje obie powyższe funkcje sekwencyjnie, demonstrując różnicę między zmienną zainicjowaną i niezainicjowaną.

Przykład ARM64

To nie zmienia się w ARM64, ponieważ zmienne lokalne są również zarządzane na stosie, możesz sprawdzić ten przykład, gdzie to jest pokazane.

Last updated