Uninitialized Variables

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Βασικές Πληροφορίες

Η βασική ιδέα εδώ είναι να κατανοήσουμε τι συμβαίνει με uninitialized variables καθώς θα έχουν την τιμή που ήδη βρισκόταν στη μνήμη που τους έχει ανατεθεί. Παράδειγμα:

  • Function 1: initializeVariable: Δηλώνουμε μια μεταβλητή x και της αναθέτουμε μια τιμή, ας πούμε 0x1234. Αυτή η ενέργεια είναι παρόμοια με την κράτηση ενός χώρου στη μνήμη και την τοποθέτηση μιας συγκεκριμένης τιμής σε αυτόν.
  • Function 2: useUninitializedVariable: Εδώ δηλώνουμε μια άλλη μεταβλητή y αλλά δεν της αναθέτουμε καμία τιμή. Στη C, οι uninitialized variables δεν μηδενίζονται αυτόματα. Αντίθετα, διατηρούν όποια τιμή είχε αποθηκευτεί τελευταία στη διεύθυνση μνήμης τους.

Όταν εκτελούμε αυτές τις δύο συναρτήσεις διαδοχικά:

  1. Στη initializeVariable, στη μεταβλητή x ανατίθεται μια τιμή (0x1234), η οποία καταλαμβάνει μια συγκεκριμένη διεύθυνση μνήμης.
  2. Στη useUninitializedVariable, η y δηλώνεται αλλά δεν της ανατίθεται τιμή, οπότε καταλαμβάνει τη θέση μνήμης αμέσως μετά την x. Επειδή δεν αρχικοποιείται η y, καταλήγει να «κληρονομεί» την τιμή από την ίδια διεύθυνση μνήμης που χρησιμοποίησε η x, επειδή αυτή ήταν η τελευταία τιμή που υπήρχε εκεί.

Αυτή η συμπεριφορά καταδεικνύει ένα βασικό σημείο στον προγραμματισμό χαμηλού επιπέδου: Η διαχείριση μνήμης είναι κρίσιμη, και οι uninitialized variables μπορούν να οδηγήσουν σε απρόβλεπτη συμπεριφορά ή σε ευπάθειες ασφαλείας, καθώς μπορεί να κρατούν ακούσια ευαίσθητα δεδομένα που έχουν μείνει στη μνήμη.

Οι Uninitialized stack variables μπορούν να προκαλέσουν αρκετούς κινδύνους ασφάλειας όπως:

  • Data Leakage: Ευαίσθητες πληροφορίες όπως κωδικοί πρόσβασης, κλειδιά κρυπτογράφησης ή προσωπικά δεδομένα μπορούν να εκτεθούν αν αποθηκευτούν σε uninitialized variables, επιτρέποντας σε επιτιθέμενους να διαβάσουν αυτά τα δεδομένα.
  • Information Disclosure: Το περιεχόμενο των uninitialized variables μπορεί να αποκαλύψει λεπτομέρειες για τη διάταξη μνήμης του προγράμματος ή τις εσωτερικές λειτουργίες του, βοηθώντας τους επιτιθέμενους στην ανάπτυξη στοχευμένων exploits.
  • Crashes and Instability: Ενέργειες που αφορούν uninitialized variables μπορεί να οδηγήσουν σε μη καθορισμένη συμπεριφορά, προκαλώντας καταρρεύσεις του προγράμματος ή απρόβλεπτα αποτελέσματα.
  • Arbitrary Code Execution: Σε ορισμένα σενάρια, επιτιθέμενοι μπορούν να εκμεταλλευτούν αυτές τις ευπάθειες για να αλλάξουν τη ροή εκτέλεσης του προγράμματος, επιτρέποντάς τους να εκτελέσουν arbitrary code, το οποίο μπορεί να περιλαμβάνει απειλές για remote code execution.

Παράδειγμα

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

Πώς λειτουργεί αυτό:

  • initializeAndPrint Συνάρτηση: Η συνάρτηση αυτή δηλώνει μια ακέραια μεταβλητή initializedVar, της αναθέτει την τιμή 100 και στη συνέχεια εκτυπώνει τόσο τη διεύθυνση μνήμης όσο και την τιμή της μεταβλητής. Αυτό το βήμα είναι απλό και δείχνει πώς συμπεριφέρεται μια αρχικοποιημένη μεταβλητή.
  • demonstrateUninitializedVar Συνάρτηση: Σε αυτή τη συνάρτηση δηλώνουμε μια ακέραια μεταβλητή uninitializedVar χωρίς να την αρχικοποιήσουμε. Όταν προσπαθήσουμε να εκτυπώσουμε την τιμή της, η έξοδος μπορεί να δείξει έναν τυχαίο αριθμό. Αυτός ο αριθμός αντιπροσωπεύει όποια δεδομένα υπήρχαν προηγουμένως σε εκείνη τη θέση μνήμης. Ανάλογα με το περιβάλλον και τον compiler, η πραγματική έξοδος μπορεί να διαφέρει, και μερικές φορές, για λόγους ασφάλειας, κάποιοι compilers μπορεί να αρχικοποιούν αυτόματα τις μεταβλητές στο μηδέν — ωστόσο αυτό δεν πρέπει να θεωρείται δεδομένο.
  • main Συνάρτηση: Η main συνάρτηση καλεί και τις δύο παραπάνω συναρτήσεις με τη σειρά, δείχνοντας την αντίθεση μεταξύ μιας αρχικοποιημένης μεταβλητής και μιας μη αρχικοποιημένης.

Πρακτικά μοτίβα εκμετάλλευσης (2024–2025)

Το κλασικό “read-before-write” bug παραμένει σημαντικό επειδή τα σύγχρονα mitigations (ASLR, canaries) συχνά βασίζονται στην μυστικότητα. Τυπικές επιφάνειες επίθεσης:

  • Partially initialized structs copied to userland: Kernel ή drivers συχνά κάνουν memset μόνο σε ένα πεδίο μήκους και μετά copy_to_user(&u, &local_struct, sizeof(local_struct)). Padding και μη χρησιμοποιημένα πεδία leak μισά του stack canary, saved frame pointers ή kernel pointers. Εάν το struct περιέχει ένα function pointer, η μη αρχικοποίησή του μπορεί επίσης να επιτρέψει controlled overwrite όταν επαναχρησιμοποιηθεί αργότερα.
  • Uninitialized stack buffers reused as indexes/lengths: Ένα μη αρχικοποιημένο size_t len; που χρησιμοποιείται για να περιορίσει το read(fd, buf, len) μπορεί να δώσει σε επιτιθέμενους out-of-bounds reads/writes ή να επιτρέψει το bypassing των size checks όταν η θέση στο stack εξακολουθεί να περιέχει μια μεγάλη τιμή από προηγούμενη κλήση.
  • Compiler-added padding: Ακόμα και όταν μεμονωμένα μέλη αρχικοποιούνται, τα implicit padding bytes ανάμεσά τους όχι. Η αντιγραφή ολόκληρου του struct στο userland leaks padding που συχνά περιέχει προηγούμενο περιεχόμενο του stack (canaries, pointers).
  • ROP/Canary disclosure: Εάν μια συνάρτηση αντιγράψει ένα local struct στο stdout για debugging, το μη αρχικοποιημένο padding μπορεί να αποκαλύψει το stack canary, επιτρέποντας επακόλουθη εκμετάλλευση stack overflow χωρίς brute-force.

Ελάχιστο μοτίβο PoC για τον εντοπισμό τέτοιων ζητημάτων κατά την ανασκόπηση:

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

Αντιμετώπιση & compiler options (κρατήστε υπόψη όταν παρακάμπτετε)

  • Clang/GCC auto-init: Πρόσφατα toolchains εκθέτουν -ftrivial-auto-var-init=zero ή -ftrivial-auto-var-init=pattern, γεμίζοντας κάθε automatic (stack) μεταβλητή στην είσοδο της συνάρτησης με μηδενικά ή ένα poison pattern (0xAA / 0xFE). Αυτό κλείνει τα περισσότερα uninitialized-stack info leaks και κάνει την εκμετάλλευση πιο δύσκολη μετατρέποντας secrets σε γνωστές τιμές.
  • Linux kernel hardening: Πυρήνες που χτίζονται με CONFIG_INIT_STACK_ALL ή το νεότερο CONFIG_INIT_STACK_ALL_PATTERN μηδενίζουν/αρχικοποιούν με pattern κάθε θέση του stack κατά την είσοδο συνάρτησης, σβήνοντας canaries/pointers που αλλιώς θα leak. Ψάξτε για διανομές που διανέμουν Clang-built kernels με αυτές τις επιλογές ενεργοποιημένες (συνήθως σε 6.8+ hardening configs).
  • Opt-out attributes: Το Clang πλέον επιτρέπει __attribute__((uninitialized)) σε συγκεκριμένους locals/structs για να κρατά περιοχές κρίσιμες για απόδοση μη αρχικοποιημένες ακόμα και όταν η global auto-init είναι ενεργή. Ελέγξτε προσεκτικά τέτοιες annotations—συχνά σημειώνουν σκόπιμη επιφάνεια επίθεσης για side channels.

Από την πλευρά του επιτιθέμενου, το αν το binary χτίστηκε με αυτές τις flags καθορίζει αν τα stack-leak primitives είναι βιώσιμα ή αν πρέπει να μεταβείτε σε heap/data-section disclosures.

Εύρεση uninitialized-stack σφαλμάτων γρήγορα

  • Compiler diagnostics: Κάντε build με -Wall -Wextra -Wuninitialized (GCC/Clang). Για C++ κώδικα, το clang-tidy -checks=cppcoreguidelines-init-variables θα auto-fixάρει πολλές περιπτώσεις σε zero-init και είναι χρήσιμο για να εντοπίσετε missed locals κατά την audit.
  • Dynamic tools: -fsanitize=memory (MSan) στο Clang ή το Valgrind με --track-origins=yes επισημαίνουν αξιόπιστα αναγνώσεις μη αρχικοποιημένων bytes του stack κατά το fuzzing. Ενσωματώστε test harnesses με αυτά για να αναδείξετε subtle padding leaks.
  • Grepping patterns: Στις αναθεωρήσεις, ψάξτε για κλήσεις copy_to_user / write ολόκληρων structs, ή memcpy/send δεδομένων του stack όπου μόνο μέρος του struct έχει οριστεί. Δώστε ιδιαίτερη προσοχή σε error paths όπου η αρχικοποίηση παραλείπεται.

Παράδειγμα ARM64

Αυτό δεν αλλάζει καθόλου σε ARM64 καθώς οι τοπικές μεταβλητές διαχειρίζονται επίσης στο stack — μπορείτε να check this example όπου αυτό αποδεικνύεται.

References

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks