Übermäßige Verwendung von selbstmodifizierendem Code
Beschreibung
Übermäßige Verwendung von selbstmodifizierendem Code tritt auf, wenn ein Produkt zu viel selbstmodifizierenden Code verwendet, also Code, der seine eigenen Anweisungen während der Ausführung ändert. Obwohl selbstmodifizierender Code legitime Einsatzzwecke hat (JIT-Kompilierung, Laufzeitoptimierung, bestimmte eingebettete Systeme), erzeugt übermäßige Verwendung Code, der extrem schwer zu verstehen, zu debuggen, zu testen und auf Sicherheitslücken zu prüfen ist. Selbstmodifizierender Code kann auch Sicherheitsmaßnahmen umgehen, die darauf ausgelegt sind, Code vor der Ausführung zu analysieren.
Risiko
Übermäßiger selbstmodifizierender Code hat erhebliche Sicherheitsauswirkungen. Statische Analysetools können Code, der sich zur Laufzeit ändert, nicht effektiv analysieren. Sicherheitsprüfer können das Programmverhalten nicht allein aus dem Quellcode verstehen. Anti-Malware-Tools können selbstmodifizierenden Code als verdächtig markieren. Code-Signierung und Integritätsprüfung werden untergraben. Kontrollflussintegritätsschutz kann umgangen werden. Speicherschutzmechanismen wie DEP/NX können umgangen werden. Debug- und Audit-Fähigkeiten sind stark eingeschränkt. Versteckte Funktionalität oder Backdoors lassen sich leichter verbergen.
Lösung
Minimieren Sie die Verwendung von selbstmodifizierendem Code auf wesentliche Fälle. Dokumentieren Sie allen selbstmodifizierenden Code gründlich. Verwenden Sie Standardtechniken wie Callbacks oder Polymorphismus stattdessen. Falls selbstmodifizierender Code notwendig ist, isolieren Sie ihn in spezifische, überprüfbare Module. Implementieren Sie Laufzeitverifikation des modifizierten Codes. Verwenden Sie Code-Signierung zur Überprüfung der Integrität generierten Codes. Wenden Sie strikte Speicherberechtigungen an (W^X – Schreiben oder Ausführen, nicht beides). Protokollieren Sie alle Codeänderungen für die Prüfung. Erwägen Sie Alternativen wie interpretierte Skripte für dynamisches Verhalten. Lassen Sie selbstmodifizierenden Code von Sicherheitsexperten überprüfen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Sonstiges | Bereich: Sonstiges Reduzierte Wartbarkeit – Erschwert das Verstehen oder Warten des Produkts und beeinträchtigt indirekt die Sicherheit, indem Schwachstellen schwerer zu finden und zu beheben sind. |
| Sonstiges | Bereich: Sonstiges Erhöhte analytische Komplexität – Erschwert die Erkennung und Behebung von Schwachstellen und begünstigt versehentliche Sicherheitslücken. |
| Integrität | Bereich: Integrität Umgehung von Sicherheitskontrollen – Selbstmodifizierender Code kann verwendet werden, um Sicherheitsanalysen und Schutzmechanismen zu umgehen. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: Übermäßiger selbstmodifizierender Code
#include <sys/mman.h>
#include <string.h>
// Selbstmodifizierender Code, der sich zur Laufzeit patcht
// Extrem schwer zu prüfen und kann Sicherheitsmaßnahmen umgehen
void self_modifying_function() {
// Beschreibbaren/ausführbaren Speicher erhalten (Sicherheitsrisiko!)
void *code = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// Funktion in beschreibbaren Speicher kopieren
unsigned char *func_ptr = (unsigned char *)code;
// Code zur Laufzeit patchen
// Original: return x + 10;
// Modifiziert: return x + 20;
// x86-Instruktion: add eax, 10 (83 C0 0A)
func_ptr[0] = 0x83;
func_ptr[1] = 0xC0;
func_ptr[2] = 0x0A;
// Selbstmodifikation zu: add eax, 20 (83 C0 14)
// Diese Änderung ist für statische Analyse unsichtbar!
if (some_condition()) {
func_ptr[2] = 0x14; // Sofortwert modifizieren
}
// Den modifizierten Code ausführen
int (*patched_func)(int) = (int (*)(int))func_ptr;
int result = patched_func(5);
}
// Weiteres Beispiel: Eingebetteter und modifizierter Verschlüsselungsschlüssel
void self_modifying_key_derivation() {
// Schlüsselableitung, die sich selbst modifiziert, um den Algorithmus zu verbergen
static unsigned char key_function[] = {
0x55, // push rbp
0x48, 0x89, 0xe5, // mov rbp, rsp
// ... weiterer Code ...
0xc3 // ret
};
// Code beschreibbar machen (gefährlich!)
mprotect(key_function, sizeof(key_function),
PROT_READ | PROT_WRITE | PROT_EXEC);
// Code mit einem Laufzeitwert XOR-verknüpfen, um ihn zu "entsperren"
// Verbirgt den echten Algorithmus vor statischer Analyse
for (int i = 0; i < sizeof(key_function); i++) {
key_function[i] ^= runtime_key[i % key_length];
}
// Die nun entschlüsselte Funktion ausführen
void (*derive_key)(void) = (void (*)(void))key_function;
derive_key();
}
# VERWUNDBAR: Python mit übermäßiger dynamischer Codegenerierung
import types
class SelfModifyingClass:
"""Klasse, die ihre eigenen Methoden zur Laufzeit modifiziert."""
def __init__(self):
# Methoden dynamisch erstellen und modifizieren
self._modify_methods()
def _modify_methods(self):
# Code aus String generieren – schwer zu prüfen
auth_code = """
def authenticate(self, user, password):
# Dieser Code wird zur Laufzeit generiert
# Statische Analyse kann nicht sehen, was er tut!
if user == 'admin':
return True # Backdoor in dynamischem Code versteckt!
return self._check_password(user, password)
"""
exec(auth_code)
self.authenticate = types.MethodType(
locals()['authenticate'], self
)
def process_request(self, request):
# Verarbeitung dynamisch basierend auf Eingabe modifizieren
# Angreifer könnte Code über Request injizieren!
handler_code = f"""
def handler(self, data):
return data.{request.get('method', 'strip')}()
"""
exec(handler_code)
handler = types.MethodType(locals()['handler'], self)
# Den dynamisch erstellten Handler verwenden
return handler(request.get('data', ''))
# Weiteres Muster: Globale Funktionen modifizieren
def create_dynamic_validator():
"""Validierungsfunktion zur Laufzeit generieren."""
# Code-String basierend auf Konfiguration erstellen
code = "def validate(data):\n"
code += " # Dynamisch generierte Validierung\n"
for rule in load_rules_from_somewhere():
# Regeln könnten bösartigen Code enthalten!
code += f" if not ({rule['condition']}):\n"
code += f" return False, '{rule['message']}'\n"
code += " return True, 'Valid'\n"
# Den generierten Code ausführen
exec(code, globals())
return validate # Die dynamisch erstellte Funktion zurückgeben
// VERWUNDBAR: JavaScript mit übermäßigem eval und dynamischem Code
class SelfModifyingAPI {
constructor() {
this.methods = {};
this._generateMethods();
}
_generateMethods() {
// API-Methoden aus String-Vorlagen generieren
// Extrem schwer zu prüfen!
const methodTemplate = `
return async function(params) {
${this._getSecurityCheck()}
${this._getBusinessLogic()}
return result;
}
`;
// Funktion aus String erstellen
this.methods.processPayment = eval(`(${methodTemplate})`);
// Noch schlimmer: Function-Prototype modifizieren
Function.prototype.securityBypass = function() {
// Versteckte Backdoor in Prototype-Modifikation!
return this.apply(null, arguments);
};
}
_getSecurityCheck() {
// Sicherheitslogik aus Strings gebaut – kann nicht statisch analysiert werden
return `
if (params.skipAuth) {
// Versteckte Umgehung!
} else {
await this.authenticate(params.token);
}
`;
}
// Funktionen dynamisch basierend auf Benutzereingabe erstellen
createHandler(userDefinedLogic) {
// GEFÄHRLICH: Erstellt Funktion aus Benutzereingabe!
const handler = new Function('data', userDefinedLogic);
this.methods.customHandler = handler;
}
}
// Selbstmodifizierender Code durch Property-Deskriptoren
Object.defineProperty(target, 'secretMethod', {
get: function() {
// Methode, die sich beim Zugriff selbst modifiziert
const originalImpl = this._secretImpl;
// Bei erstem Zugriff neu definieren (One-Time-Pad-Stil)
Object.defineProperty(this, 'secretMethod', {
value: function() {
// Neue Implementierung – anders als das Original!
return originalImpl.apply(this, arguments);
}
});
return this.secretMethod;
}
});
Sichere Lösung
// SICHER: Selbstmodifizierenden Code vermeiden, stattdessen Funktionszeiger verwenden
#include <stdbool.h>
// Operationstypen statisch definieren
typedef int (*Operation)(int);
int add_ten(int x) {
return x + 10;
}
int add_twenty(int x) {
return x + 20;
}
// Funktionszeiger anstelle von selbstmodifizierendem Code verwenden
int perform_operation(int x, bool use_larger_value) {
// Funktion zur Laufzeit auswählen, ohne Code zu modifizieren
Operation op = use_larger_value ? add_twenty : add_ten;
return op(x);
}
// Für dynamisches Verhalten Konfiguration und Dispatch-Tabellen verwenden
typedef struct {
const char *name;
Operation handler;
bool requires_auth;
} OperationEntry;
static const OperationEntry operations[] = {
{"add_small", add_ten, false},
{"add_large", add_twenty, true},
{NULL, NULL, false}
};
int dispatch_operation(const char *op_name, int x, bool is_authenticated) {
for (int i = 0; operations[i].name != NULL; i++) {
if (strcmp(operations[i].name, op_name) == 0) {
// Authentifizierung prüfen, falls erforderlich
if (operations[i].requires_auth && !is_authenticated) {
return -1; // Fehler: nicht autorisiert
}
return operations[i].handler(x);
}
}
return -1; // Fehler: unbekannte Operation
}
// Für Schlüsselableitung Standard-Kryptobibliotheken verwenden
#include <openssl/kdf.h>
int derive_key(const unsigned char *password, size_t pass_len,
const unsigned char *salt, size_t salt_len,
unsigned char *key, size_t key_len) {
// Standard-konforme, prüfbare Kryptofunktion verwenden
return PKCS5_PBKDF2_HMAC(
(const char *)password, pass_len,
salt, salt_len,
100000, // Iterationen
EVP_sha256(),
key_len, key
);
}
# SICHER: Korrekte Entwurfsmuster anstelle von selbstmodifizierendem Code verwenden
from abc import ABC, abstractmethod
from typing import Dict, Callable, Any
class Authenticator(ABC):
"""Abstrakter Authenticator – Verhalten durch Unterklasse definiert, nicht durch Laufzeitmodifikation."""
@abstractmethod
def authenticate(self, user: str, password: str) -> bool:
pass
class StandardAuthenticator(Authenticator):
"""Standard-Authentifizierungsimplementierung – vollständig prüfbar."""
def __init__(self, user_store):
self.user_store = user_store
def authenticate(self, user: str, password: str) -> bool:
stored_hash = self.user_store.get_password_hash(user)
if not stored_hash:
return False
return verify_password(password, stored_hash)
class RequestProcessor:
"""Anfragen mit konfigurierten Handlern anstelle von dynamischem Code verarbeiten."""
def __init__(self):
# Erlaubte Operationen statisch definieren
self._handlers: Dict[str, Callable] = {
'strip': self._handle_strip,
'lower': self._handle_lower,
'upper': self._handle_upper,
'normalize': self._handle_normalize,
}
def process_request(self, request: dict) -> str:
"""Anfrage mit registriertem Handler verarbeiten."""
method = request.get('method', 'strip')
data = request.get('data', '')
# Nur registrierte, prüfbare Handler verwenden
handler = self._handlers.get(method)
if handler is None:
raise ValueError(f"Unbekannte Methode: {method}")
return handler(data)
def _handle_strip(self, data: str) -> str:
return data.strip()
def _handle_lower(self, data: str) -> str:
return data.lower()
def _handle_upper(self, data: str) -> str:
return data.upper()
def _handle_normalize(self, data: str) -> str:
import unicodedata
return unicodedata.normalize('NFC', data)
class ValidationRuleEngine:
"""Validierung mit konfigurierten Regeln anstelle von generiertem Code."""
def __init__(self):
self._validators: Dict[str, Callable[[Any], bool]] = {
'not_empty': lambda x: bool(x),
'is_positive': lambda x: isinstance(x, (int, float)) and x > 0,
'is_email': self._validate_email,
'max_length_100': lambda x: len(str(x)) <= 100,
}
def validate(self, data: Any, rules: list) -> tuple:
"""Daten gegen eine Liste von Regelnamen validieren."""
for rule_name in rules:
validator = self._validators.get(rule_name)
if validator is None:
return False, f"Unbekannte Regel: {rule_name}"
if not validator(data):
return False, f"Validierung fehlgeschlagen: {rule_name}"
return True, "Gültig"
def _validate_email(self, value: Any) -> bool:
"""E-Mail-Format validieren."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, str(value)))
# Strategy-Pattern für austauschbares Verhalten
class PaymentStrategy(ABC):
@abstractmethod
def process(self, amount: float, details: dict) -> dict:
pass
class CreditCardPayment(PaymentStrategy):
def process(self, amount: float, details: dict) -> dict:
# Prüfbare, statische Implementierung
return {"method": "credit_card", "amount": amount, "status": "processed"}
class PaymentProcessor:
"""Zahlungsverarbeiter mit Strategy-Pattern."""
def __init__(self):
self._strategies: Dict[str, PaymentStrategy] = {
'credit_card': CreditCardPayment(),
# Weitere Strategien nach Bedarf hinzufügen
}
def process_payment(self, method: str, amount: float, details: dict) -> dict:
strategy = self._strategies.get(method)
if not strategy:
raise ValueError(f"Nicht unterstützte Zahlungsmethode: {method}")
return strategy.process(amount, details)
CVE-Beispiele
Dieses CWE ist für die direkte CVE-Zuordnung als VERBOTEN markiert, da es ein Codequalitätsproblem und keine direkte Sicherheitslücke darstellt.
Verwandte CWEs
- CWE-1120: Excessive Code Complexity (übergeordnet)
- CWE-1226: Complexity Issues (Kategoriemitglied)
- CWE-94: Improper Control of Generation of Code ('Code Injection') (verwandt)
Referenzen
- MITRE Corporation. "CWE-1123: Excessive Use of Self-Modifying Code." https://cwe.mitre.org/data/definitions/1123.html
- OWASP – Code Injection
- Richtlinien zu Speicherschutz und DEP/NX