Uninitialisierte Variablen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundlegende Informationen

Die Kernidee ist zu verstehen, was mit uninitialisierten Variablen passiert: sie enthalten den Wert, der bereits an der ihnen zugewiesenen Speicherstelle vorhanden war. Beispiel:

  • Function 1: initializeVariable: Wir deklarieren eine Variable x und weisen ihr einen Wert zu, sagen wir 0x1234. Diese Aktion ist vergleichbar damit, einen Platz im Speicher zu reservieren und einen bestimmten Wert dort abzulegen.
  • Function 2: useUninitializedVariable: Hier deklarieren wir eine weitere Variable y, weisen ihr aber keinen Wert zu. In C werden uninitialisierte Variablen nicht automatisch auf Null gesetzt. Stattdessen behalten sie den Wert, der zuletzt an ihrer Speicheradresse gespeichert war.

Wenn wir diese beiden Funktionen nacheinander ausführen:

  1. In initializeVariable wird x ein Wert (0x1234) zugewiesen, der eine bestimmte Speicheradresse belegt.
  2. In useUninitializedVariable wird y deklariert, aber nicht initialisiert, sodass es den direkt nach x folgenden Speicherplatz einnimmt. Da y nicht initialisiert wurde, “erbt” es schließlich den Wert aus derselben Speicherstelle, die von x verwendet wurde, weil das der zuletzt dort abgelegte Wert ist.

Dieses Verhalten veranschaulicht ein zentrales Konzept in der Low-Level-Programmierung: Speichermanagement ist entscheidend, und uninitialisierte Variablen können zu nicht vorhersehbarem Verhalten oder Sicherheitslücken führen, da sie unbeabsichtigt sensible Daten enthalten können, die im Speicher zurückgelassen wurden.

Uninitialisierte Stack-Variablen können mehrere Sicherheitsrisiken darstellen, wie zum Beispiel:

  • Data Leakage: Sensible Informationen wie Passwörter, Verschlüsselungskeys oder persönliche Daten können offengelegt werden, wenn sie in uninitialisierten Variablen gespeichert sind, was Angreifern möglicherweise das Lesen dieser Daten erlaubt.
  • Information Disclosure: Der Inhalt uninitialisierter Variablen kann Details über das Speicherlayout des Programms oder seine internen Abläufe offenbaren und Angreifern beim Entwickeln gezielter Exploits helfen.
  • Crashes and Instability: Operationen mit uninitialisierten Variablen können zu undefiniertem Verhalten führen, was Programmabstürze oder unvorhersehbare Ergebnisse zur Folge haben kann.
  • Arbitrary Code Execution: In bestimmten Szenarien könnten Angreifer diese Schwachstellen ausnutzen, um den Programmablauf zu verändern und beliebigen Code auszuführen, was auch remote code execution-Bedrohungen umfassen kann.

Beispiel

#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;
}

Wie das funktioniert:

  • initializeAndPrint Function: Diese Funktion deklariert eine Ganzzahlvariable initializedVar, weist ihr den Wert 100 zu und gibt dann sowohl die Speicheradresse als auch den Wert der Variable aus. Dieser Schritt ist unkompliziert und zeigt, wie sich eine initialisierte Variable verhält.
  • demonstrateUninitializedVar Function: In dieser Funktion deklarieren wir eine Ganzzahlvariable uninitializedVar ohne sie zu initialisieren. Wenn wir versuchen, ihren Wert auszugeben, kann die Ausgabe eine zufällige Zahl zeigen. Diese Zahl repräsentiert die Daten, die zuvor an dieser Speicherstelle vorhanden waren. Abhängig von der Umgebung und dem Compiler kann die tatsächliche Ausgabe variieren, und manchmal initialisieren einige Compiler aus Sicherheitsgründen Variablen automatisch auf null, obwohl man sich darauf nicht verlassen sollte.
  • main Function: Die main-Funktion ruft beide oben genannten Funktionen nacheinander auf und demonstriert den Kontrast zwischen einer initialisierten Variable und einer uninitialisierten.

Praktische Exploit-Muster (2024–2025)

Der klassische “read-before-write” Bug bleibt relevant, weil moderne Mitigations (ASLR, canaries) oft auf Geheimhaltung beruhen. Typische Angriffsflächen:

  • Partially initialized structs copied to userland: Kernel oder Treiber führen häufig nur ein memset für ein Längenfeld aus und dann copy_to_user(&u, &local_struct, sizeof(local_struct)). Padding und ungenutzte Felder leak stack canary halves, saved frame pointers or kernel pointers. Wenn die Struct einen function pointer enthält, kann das Nicht-Initialisieren später auch eine controlled overwrite ermöglichen, wenn sie wiederverwendet wird.
  • Uninitialized stack buffers reused as indexes/lengths: Ein uninitialisiertes size_t len;, das verwendet wird, um read(fd, buf, len) zu begrenzen, kann Angreifern out-of-bounds reads/writes erlauben oder das Umgehen von Größenprüfungen ermöglichen, wenn der Stack-Slot noch einen großen Wert aus einem vorherigen Aufruf enthält.
  • Compiler-added padding: Selbst wenn einzelne Members initialisiert sind, werden implizite Padding-Bytes dazwischen nicht initialisiert. Das Kopieren der gesamten Struct in userland leak padding, das häufig vorherigen Stack-Content (canaries, pointers) enthält.
  • ROP/Canary disclosure: Wenn eine Funktion eine lokale Struct zum Debugging auf stdout kopiert, kann uninitialisiertes Padding den stack canary offenbaren und damit eine nachfolgende stack overflow exploitation ohne Brute-Force ermöglichen.

Minimaler PoC-Pattern, um solche Probleme während der Review zu erkennen:

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;
}

Gegenmaßnahmen & Compiler-Optionen (bei Umgehung beachten)

  • Clang/GCC auto-init: Neuere Toolchains bieten -ftrivial-auto-var-init=zero oder -ftrivial-auto-var-init=pattern, die jede automatische (Stack-)Variable beim Funktionsbeginn mit Nullen oder einem Poison-Pattern (0xAA / 0xFE) füllen. Das schließt die meisten uninitialized-stack info leaks und erschwert Exploits, indem Secrets in bekannte Werte umgewandelt werden.
  • Linux kernel hardening: Kernel, die mit CONFIG_INIT_STACK_ALL oder dem neueren CONFIG_INIT_STACK_ALL_PATTERN gebaut werden, null-/pattern-initialisieren beim Funktionsbeginn jedes Stack-Slot und löschen damit Canaries/Pointer, die sonst leaken würden. Achte auf distros, die Clang-gebuildete Kernel mit diesen Optionen ausliefern (häufig in 6.8+ Hardening-Konfigurationen).
  • Opt-out-Attribute: Clang erlaubt jetzt __attribute__((uninitialized)) auf bestimmten Locals/Structs, um performance-kritische Bereiche uninitialisiert zu lassen, auch wenn globales auto-init aktiviert ist. Überprüfe solche Annotationen sorgfältig — sie markieren oft eine beabsichtigte Angriffsfläche für Side-Channels.

Aus Sicht eines Angreifers bestimmt die Kenntnis, ob das Binary mit diesen Flags gebaut wurde, ob stack-leak primitives praktikabel sind oder ob man auf heap/data-section disclosures ausweichen muss.

Schnelles Auffinden von uninitialized-stack Bugs

  • Compiler-Diagnostics: Baue mit -Wall -Wextra -Wuninitialized (GCC/Clang). Für C++-Code hilft clang-tidy -checks=cppcoreguidelines-init-variables, das viele Fälle automatisch auf zero-init setzt und praktisch ist, um übersehene Locals während eines Audits zu finden.
  • Dynamische Tools: -fsanitize=memory (MSan) in Clang oder Valgrind’s --track-origins=yes melden zuverlässig Reads von uninitialized Stack-Bytes während Fuzzing. Instrumentiere Test-Harnesses damit, um subtile Padding-leaks aufzudecken.
  • Grepping-Muster: Suche in Reviews nach copy_to_user / write-Aufrufen ganzer Structs oder memcpy/send von Stack-Daten, wenn nur ein Teil des Structs gesetzt ist. Achte besonders auf Error-Pfade, in denen die Initialisierung übersprungen wird.

ARM64 Example

Dies ändert sich nicht bei ARM64, da lokale Variablen ebenfalls im Stack verwaltet werden — du kannst check this example sehen, wo dies gezeigt wird.

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks