Zuweisung einer festen Adresse an einen Pointer
Beschreibung
Zuweisung einer festen Adresse an einen Pointer tritt auf, wenn Code eine hardcodierte Speicheradresse einer Zeigervariablen zuweist und dann diesen Pointer verwendet, um auf Speicher zuzugreifen. Speicheradressen sind nur in spezifischen Kontexten bedeutsam - sie ändern sich zwischen Ausführungen, Systemen und Konfigurationen. Code, der sich auf feste Adressen verlässt, funktioniert typischerweise nur zufällig und bricht unvorhersehbar. Dieses Muster ist besonders gefährlich in modernen Systemen mit Address Space Layout Randomization (ASLR) und Speicherschutz.
Risiko
Feste Adresszuweisungen verursachen Abstürze, wenn der erwartete Speicher nicht gemappt oder zugänglich ist. Auf Systemen ohne ASLR können Angreifer vorhersehbare Adressen für Angriffe ausnutzen. Der Code ist nicht portabel - Adressen, die auf einem System gültig sind, schlagen auf anderen fehl. In eingebetteten Systemen können feste Adressen Hardware-Register referenzieren, aber dies erfordert sorgfältige Berücksichtigung des Speicher-Mappings. Die explizite Verwendung von Adresse 0 erzeugt NULL-Pointer-Dereferenzierungen. Sicherheitsmaßnahmen wie ASLR werden umgangen, wenn Code feste Adressen annimmt.
Lösung
Hardcodieren Sie niemals Speicheradressen außer in plattformspezifischem eingebettetem Code mit dokumentierten Hardware-Speicher-Maps. Verwenden Sie ordnungsgemäße Speicherallokation (malloc, new) statt fester Adressen. Für Hardware-Zugriff in eingebetteten Systemen verwenden Sie dokumentierte memory-mapped I/O mit ordnungsgemäßen Abstraktionen. Wenn feste Adressen unvermeidlich sind, kapseln Sie sie mit klarer Dokumentation und Plattformprüfungen. Verwenden Sie symbolische Konstanten, die von der Plattform für spezielle Adressen definiert sind. Aktivieren Sie ASLR und schreiben Sie keinen Code, der von Adressvorhersehbarkeit abhängt.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Absturz Zugriff auf nicht gemappten oder geschützten Speicher verursacht Segmentation Faults. |
| Sicherheit | Bereich: Umgangene Maßnahmen Feste Adressen umgehen ASLR und andere Sicherheitsfeatures. |
| Portabilität | Bereich: Nicht portabler Code Code schlägt auf verschiedenen Systemen, Konfigurationen oder Ausführungen fehl. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Hardcodierte Speicheradresse
void access_memory_vulnerable() {
int* ptr = (int*)0x12345678; // Feste Adresse!
*ptr = 42; // Absturz wenn Adresse nicht gemappt!
}
// VERWUNDBAR: Explizite NULL-Zuweisung dann Dereferenzierung
void null_access_vulnerable() {
int* ptr = (int*)0x0; // NULL-Pointer!
*ptr = 42; // Garantierter Absturz!
}
// VERWUNDBAR: Annehmen von Stack-Adressen
void stack_assumption_vulnerable() {
// Annahme, dass Stack an bestimmter Stelle beginnt
char* stack_base = (char*)0x7FFF0000;
// Dies funktioniert nicht mit ASLR
read_stack(stack_base);
}
// VERWUNDBAR: Hardcodierte Buffer-Adresse
void buffer_vulnerable() {
char* buffer = (char*)0x08048000; // Annahme festes Code-Segment
strcpy(buffer, "data"); // Wahrscheinlich Absturz oder Sicherheitsproblem
}
// VERWUNDBAR: Nimmt vorhersehbare Heap-Position an
void heap_assumption_vulnerable() {
// Falsch! Heap-Adressen sind mit ASLR randomisiert
int* heap_data = (int*)0x602000;
*heap_data = 100;
}
// VERWUNDBAR: Type-Punning über feste Adresse
void type_pun_vulnerable() {
// Versuch, auf bestimmten Speicher als anderen Typ zuzugreifen
float* f = (float*)0xDEADBEEF;
printf("%f\n", *f); // Absturz!
}
// VERWUNDBAR: Eingebetteter Code ohne ordnungsgemäße Abstraktion
void embedded_dangerous() {
// Magische Zahl für Hardware-Register
volatile int* reg = (volatile int*)0x40001000;
*reg = 0x01; // Kann auf spezifischer Hardware funktionieren, scheitert anderswo
}
// VERWUNDBAR: Array an fester Adresse
void array_vulnerable() {
int (*array)[100] = (int(*)[100])0xB0000000;
(*array)[0] = 1; // Adresse möglicherweise nicht gültig
}
// VERWUNDBAR: C++ mit festen Adressen
class VulnerableHardware {
public:
void writeRegister(int value) {
// Hardcodierte Hardware-Adresse
volatile int* reg = reinterpret_cast<volatile int*>(0x40001234);
*reg = value;
}
int readMemory() {
// Beliebiges Speicherlesen
int* ptr = reinterpret_cast<int*>(0x08000000);
return *ptr; // Absturz auf den meisten Systemen!
}
};
// VERWUNDBAR: Placement new an fester Adresse
void placementNew_vulnerable() {
void* addr = reinterpret_cast<void*>(0xC0000000);
// Annahme, dass diese Adresse gültiger beschreibbarer Speicher ist
new (addr) MyClass(); // Sehr wahrscheinlich Absturz
}
// VERWUNDBAR: Feste Adresse im Template
template<uintptr_t Address>
class FixedMemory_vulnerable {
public:
void write(int value) {
*reinterpret_cast<int*>(Address) = value;
}
};
// Verwendung
FixedMemory_vulnerable<0x12345678> mem; // Hardcodierte Adresse im Typ
// VERWUNDBAR: Sicherheits-Exploit-Muster
void exploit_pattern_vulnerable() {
// Angreiferfreundlicher Code der vorhersehbare Adressen verwendet
void (*func_ptr)() = (void (*)())0x08048000; // Feste Code-Adresse
func_ptr(); // Sprung zu "vorhersehbarem" Ort
// Dieses Muster wird in Exploits gegen Non-ASLR-Systeme verwendet
}
// VERWUNDBAR: Annehmen physisches Speicherlayout
void physical_memory_vulnerable() {
// Versuch direkt auf Videospeicher zuzugreifen
char* video = (char*)0xB8000; // Legacy VGA Textmodus-Adresse
video[0] = 'A'; // Kann ohne ordnungsgemäße Einrichtung abstürzen
}
Lösungscode
// SICHER: Ordnungsgemäße Speicherallokation verwenden
void access_memory_safe() {
int* ptr = malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 42;
free(ptr);
}
}
// SICHER: Ordnungsgemäße NULL-Behandlung
void null_handling_safe(int* ptr) {
if (ptr == NULL) {
handle_null_error();
return;
}
*ptr = 42;
}
// SICHER: Standard-Stack-Allokation verwenden
void stack_safe() {
char buffer[1024]; // Stack-allokiert, Adresse zur Laufzeit bestimmt
process(buffer, sizeof(buffer));
}
// SICHER: Dynamische Buffer-Allokation
void buffer_safe() {
char* buffer = malloc(256);
if (buffer) {
strcpy(buffer, "data");
process(buffer);
free(buffer);
}
}
// SICHER: Ordnungsgemäße Heap-Allokation
void heap_safe() {
int* heap_data = malloc(sizeof(int));
if (heap_data) {
*heap_data = 100;
free(heap_data);
}
}
// SICHER: Eingebetteter Code mit ordnungsgemäßer Abstraktion
// hardware.h
#ifndef HARDWARE_H
#define HARDWARE_H
#ifdef PLATFORM_STM32
#define GPIO_BASE_ADDR 0x40010800
#define USART_BASE_ADDR 0x40013800
#elif defined(PLATFORM_AVR)
#define GPIO_BASE_ADDR 0x20
#define USART_BASE_ADDR 0xC0
#else
#error "Plattform nicht unterstützt"
#endif
typedef struct {
volatile uint32_t CR1;
volatile uint32_t CR2;
volatile uint32_t SR;
volatile uint32_t DR;
} USART_TypeDef;
// Plattformspezifischer Accessor
static inline USART_TypeDef* get_usart1(void) {
return (USART_TypeDef*)USART_BASE_ADDR;
}
#endif
// Verwendung in eingebettetem Code:
void embedded_safe() {
#ifdef PLATFORM_STM32
USART_TypeDef* usart = get_usart1();
usart->DR = 'A';
#else
// Plattform-angemessenen Code verwenden
#endif
}
// SICHER: Memory-mapped I/O mit ordnungsgemäßen Prüfungen
int safe_mmio_read(uintptr_t phys_addr, size_t length) {
#ifdef __linux__
int fd = open("/dev/mem", O_RDONLY);
if (fd < 0) {
return -1;
}
void* mapped = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, phys_addr);
if (mapped == MAP_FAILED) {
close(fd);
return -1;
}
// Gemappten Speicher sicher verwenden
int value = *(volatile int*)mapped;
munmap(mapped, length);
close(fd);
return value;
#else
return -1; // Auf dieser Plattform nicht unterstützt
#endif
}
// SICHER: C++ mit ordnungsgemäßer Speicherbehandlung
class SafeHardware {
private:
volatile uint32_t* registerBase;
bool initialized;
public:
SafeHardware() : registerBase(nullptr), initialized(false) {}
bool initialize(uintptr_t baseAddress) {
#ifdef EMBEDDED_PLATFORM
// Auf eingebettet verifizieren, dass Adresse im gültigen Hardware-Bereich
if (isValidHardwareAddress(baseAddress)) {
registerBase = reinterpret_cast<volatile uint32_t*>(baseAddress);
initialized = true;
return true;
}
#endif
return false;
}
bool writeRegister(size_t offset, uint32_t value) {
if (!initialized || registerBase == nullptr) {
return false;
}
registerBase[offset] = value;
return true;
}
};
// SICHER: Smart Pointers verwenden
void safe_allocation() {
auto ptr = std::make_unique<int>(42);
// Speicher ordnungsgemäß verwaltet
}
// SICHER: Speicherpool mit verifizierten Adressen
class SafeMemoryPool {
std::vector<uint8_t> pool;
uint8_t* base;
public:
SafeMemoryPool(size_t size) : pool(size) {
base = pool.data(); // Adresse zur Laufzeit bestimmt
}
template<typename T>
T* allocate(size_t offset) {
if (offset + sizeof(T) > pool.size()) {
return nullptr; // Grenzprüfung
}
return reinterpret_cast<T*>(base + offset);
}
};
// SICHER: RAII für memory-mapped Regionen
class MemoryMappedRegion {
void* addr;
size_t length;
public:
MemoryMappedRegion(size_t len) : addr(nullptr), length(len) {
addr = mmap(nullptr, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
addr = nullptr;
}
}
~MemoryMappedRegion() {
if (addr) {
munmap(addr, length);
}
}
void* get() { return addr; }
bool valid() { return addr != nullptr; }
};
// Verwendung
void use_mapped_safe() {
MemoryMappedRegion region(4096);
if (region.valid()) {
// region.get() sicher verwenden
}
}
Ausgenutzt in der Praxis
Return-to-libc ohne ASLR
Vor ASLR verwendeten Angreifer feste Adressen in libc für Return-Oriented-Programming-Angriffe.
Eingebettete Geräte-Exploits
Eingebettete Geräte mit festen Speicherlayouts wurden unter Verwendung bekannter Adressen ausgenutzt.
Kernel-Exploits
Kernel-Schwachstellen wurden unter Verwendung bekannter Adressen ausgenutzt, bevor Kernel-ASLR (KASLR) weit verbreitet war.
Tools zum Testen/Ausnutzen
- Valgrind — erkennt ungültige Speicherzugriffe
- Statische Analysatoren — markieren hardcodierte Adressen
- ASLR-Verifikationstools — prüfen ob ASLR effektiv ist
- Speicher-Debugger — erkennen Zugriff auf nicht gemappten Speicher
CVE-Beispiele
- Exploits gegen Non-ASLR-Systeme mit vorhersehbaren Adressen
- Kernel-Exploits mit festen Kernel-Adressen
- Eingebettete System-Exploits, die feste Speicher-Maps ausnutzen
Referenzen
- MITRE. "CWE-587: Assignment of a Fixed Address to a Pointer." https://cwe.mitre.org/data/definitions/587.html
- CERT C. "INT36-C: Converting a pointer to integer or integer to pointer." https://wiki.sei.cmu.edu/confluence/display/c/