Memory Safety - Speichersicherheits-Schwachstellen
Memory-Safety-Schwachstellen entstehen in Sprachen ohne automatisches Speichermanagement (C/C++) durch fehlerhafte manuelle Speicherverwaltung. Hauptklassen: Buffer Overflow (Stack/Heap), Use-After-Free, Double-Free, Integer Overflow, Format String. Diese Klasse verantwortet historisch ~70% aller schwerwiegenden CVEs in grossen Softwareprojekten (Microsoft, Google). Schutz: Memory-Safe-Languages (Rust, Go), AddressSanitizer, Stack Canaries, ASLR, DEP/NX.
Memory-Safety-Schwachstellen sind eine Klasse von Bugs in systemnaher Software (C, C++, Assembly) die entstehen wenn ein Programm auf Speicher zugreift der nicht für es bestimmt ist. Microsoft berichtete dass ~70% aller CVEs in Windows-Produkten auf Memory-Safety-Fehler zurückgehen. Google Chrome: ähnliche Zahlen. Diese Schwachstellen sind besonders gefährlich weil sie oft zu Remote Code Execution (RCE) führen.
Schwachstellen-Klassen
Buffer Overflow (Stack-basiert)
// Klassisches Beispiel:
void unsafe_copy(char* user_input) {
char buffer[64];
strcpy(buffer, user_input); // KEINE Längenprüfung!
}
// Bei Input > 64 Bytes: überschreibt Return-Adresse!
// Angreifer kontrolliert wohin Funktion zurückspringt → Code-Execution!
Exploitierung:
- Buffer mit Angreifer-Payload füllen: Shellcode oder ROP-Chain
- Return-Adresse auf Shellcode zeigen
- Funktion kehrt zurück → springt in Angreifer-Code → RCE
Moderne Gegenmaßnahmen:
- Stack Canaries: zufälliger Wert zwischen Buffer und Return-Adresse - wenn überschrieben, bricht das Programm ab
- ASLR: Adressen werden zufällig vergeben, Angreifer kennt Shellcode-Adresse nicht
- NX/DEP: Stack ist nicht ausführbar, Shellcode kann nicht ausgeführt werden → ROP erforderlich
Heap Buffer Overflow
// Speicher auf dem Heap überschreiben:
char* buf = malloc(64);
memcpy(buf, user_data, user_len); // user_len > 64 → Overflow!
// → Überschreibt Heap-Metadaten → Allocator kompromittiert → Exploit
Use-After-Free (UAF)
// Speicher freigeben, dann noch nutzen:
char* ptr = malloc(100);
free(ptr);
ptr[0] = 'A'; // UNDEFINED BEHAVIOR! ptr ist dangling pointer!
Exploitierung:
- Objekt A wird allokiert (ptr_a) → Free von A
- Angreifer sorgt dafür dass Objekt B den ptr_a-Speicher erhält
- Alt-Code nutzt ptr_a noch → liest/schreibt in B
- Type Confusion → Angreifer kontrolliert B’s Inhalt → Exploit
UAF ist eine der häufigsten Browser-Exploit-Klassen. In Chrome sind ~70% der High-Severity CVEs UAF-basiert.
Double-Free
// Speicher zweimal freigeben:
char* ptr = malloc(100);
free(ptr);
// ... code ...
free(ptr); // Double-Free! Heap-Korruption!
// → Modifiziert Heap-Allocator-Strukturen
// → Kann zu UAF oder Arbitrary Write führen
Integer Overflow
// Überlauf bei Integer-Arithmetik:
int size = user_provided_size * 2; // Wenn size = MAX_INT/2: Overflow → 0!
char* buf = malloc(size);
memcpy(buf, user_data, user_provided_size * 2); // zu wenig Buffer!
// → Buffer-Overflow trotz malloc!
// Reales Beispiel: libpng Längen-Berechnungen
uint16_t width, height; // 16-Bit
int total = width * height * 4; // Overflow wenn > 65535x65535!
Format String Schwachstelle
printf(user_input); // NIEMALS! Nur printf("%s", user_input)!
// → Bei user_input = "%x %x %x %x": Stack-Werte ausgegeben!
// → Bei user_input = "%n": schreibt in Speicher!
// → Beliebiger Speicher-Lese- und Schreibzugriff!
// RICHTIG:
printf("%s", user_input); // %s als Formatstring, Input als Argument
Schutzmaßnahmen auf Compiler/OS-Ebene
Stack Canaries (GCC/Clang)
gcc -fstack-protector-strong app.c
Zufälliger Wert zwischen lokalen Variablen und Return-Adresse. Beim Funktionsrückgabe wird der Canary geprüft - wenn überschrieben, bricht das Programm ab (Stack-Smashing-Protection).
ASLR (Address Space Layout Randomization)
Das OS randomisiert Stack, Heap, Libraries und PIE-Binary-Base. Angreifer kennt Adressen nicht, Sprünge scheitern.
# Linux:
sysctl kernel.randomize_va_space=2 # volle Randomisierung
# Windows: ASLR im Security Center aktiviert (Standard)
ASLR-Bypass erfordert: Information Leak + ROP-Chain.
NX/DEP (No-Execute / Data Execution Prevention)
Speicherbereiche werden als nicht-ausführbar markiert. Stack und Heap sind Data-Bereiche - Shellcode kann nicht ausgeführt werden.
gcc -Wl,-z,noexecstack
Umgehung: ROP (Return-Oriented Programming).
PIE (Position Independent Executable)
gcc -fPIE -pie app.c
Die Binary-Base wird ebenfalls randomisiert (nicht nur Libraries). Vollständiges ASLR ist erst mit PIE möglich.
RELRO (RELocation Read-Only)
gcc -Wl,-z,relro,-z,now app.c
Die GOT (Global Offset Table) wird nach dem Laden read-only gesetzt. GOT-Overwrite-Angriffe werden dadurch verhindert.
AddressSanitizer (Entwicklungs-/Testphase)
gcc -fsanitize=address,undefined app.c
Instrumentiert alle Speicher-Zugriffe. Buffer Overflow, UAF, Double-Free führen zu sofortigem Absturz mit Fehlermeldung. Performance-Overhead: ~2x - nur für Testing/Fuzzing, nicht für Produktion.
# MemorySanitizer (für uninitialisierte Reads):
clang -fsanitize=memory app.c
Control Flow Integrity (CFI)
clang -fsanitize=cfi app.c
Stellt sicher dass indirekte Sprünge nur zu gültigen Zielen führen. Erschwert ROP-Angriffe erheblich. Aktiviert in Chrome, Windows (Microsoft CET).
Memory-Safe-Languages
Rust - Memory-Safety ohne Garbage Collector
Das Ownership-System des Compilers verhindert UAF, Double-Free und Buffer-Overflow. Zero-Cost-Abstractions liefern die gleiche Performance wie C/C++. NSA, CISA und das Weiße Haus empfehlen Memory-Safe-Languages (2023/2024).
fn safe_access(v: &Vec<i32>, idx: usize) -> Option<&i32> {
v.get(idx) // Bounds-Check! Gibt None zurück wenn out-of-bounds
}
// KEIN Buffer-Overflow möglich durch safe Rust-Code!
// unsafe{}-Block: nötig für FFI/System-Code, explizit markiert
Adoption von Rust:
- Linux-Kernel: Rust als zweite Sprache seit Kernel 6.1
- Android: Rust für neue Kernel-Komponenten (UAF drastisch reduziert)
- Windows: Rust für neue sicherheitskritische Komponenten
- Google: 70% weniger Memory-Safety-Bugs bei Android-Rust-Code
Go - Sicher, aber mit Garbage Collector
- Garbage Collector verhindert UAF und Double-Free
- Bounds-Checks automatisch
- Performance geringer als C, aber deutlich höher als Python
- Trade-off: GC-Pausen, aber sichere Speicherverwaltung
Python / Java / JavaScript - vollständig GC-verwaltet
- Speicherverwaltung vom Runtime übernommen
- Native Extensions (C-Erweiterungen) können unsicher sein
- Python-C-Extensions (numpy, scipy) sind potenziell memory-unsafe
Empfehlung
- Neuprojekte mit Performance-Anforderungen: Rust bevorzugen
- Bestehende C/C++-Codebasis: schrittweiser Rust-Rewrite für kritische Teile
- Alle C/C++-Projekte: ASAN/Fuzzing in CI/CD ist Pflicht