Excessive Use of Self-Modifying Code

Description

Excessive Use of Self-Modifying Code occurs when a product uses too much self-modifying code, which is code that changes its own instructions during execution. While self-modifying code has legitimate uses (JIT compilation, runtime optimization, certain embedded systems), excessive use creates code that is extremely difficult to understand, debug, test, and audit for security vulnerabilities. Self-modifying code can also circumvent security measures designed to analyze code before execution.

Risk

Excessive self-modifying code has significant security implications. Static analysis tools cannot effectively analyze code that changes at runtime. Security reviewers cannot understand program behavior from source code alone. Anti-malware tools may flag self-modifying code as suspicious. Code signing and integrity verification are undermined. Control flow integrity protections may be bypassed. Memory protection mechanisms like DEP/NX may be circumvented. Debug and audit capabilities are severely limited. Hidden functionality or backdoors are easier to conceal.

Solution

Minimize use of self-modifying code to essential cases only. Document all self-modifying code thoroughly. Use standard techniques like callbacks or polymorphism instead. If self-modifying code is necessary, isolate it to specific, auditable modules. Implement runtime verification of modified code. Use code signing to verify generated code integrity. Apply strict memory permissions (W^X - write or execute, not both). Log all code modifications for audit. Consider alternatives like interpreted scripts for dynamic behavior. Have self-modifying code reviewed by security experts.

Common Consequences

ImpactDetails
OtherScope: Other

Reduce Maintainability - Makes it more difficult to understand or maintain the product, indirectly affecting security by making vulnerabilities harder to find and fix.
OtherScope: Other

Increase Analytical Complexity - Complicates vulnerability detection and remediation while facilitating accidental security flaws.
IntegrityScope: Integrity

Bypass Security Controls - Self-modifying code can be used to evade security analysis and protection mechanisms.

Example Code

Vulnerable Code

// Vulnerable: Excessive self-modifying code

#include <sys/mman.h>
#include <string.h>

// Self-modifying code that patches itself at runtime
// This is extremely hard to audit and may bypass security measures

void self_modifying_function() {
    // Get writable/executable memory (security risk!)
    void *code = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    // Copy function to writable memory
    unsigned char *func_ptr = (unsigned char *)code;

    // Patch the code at runtime
    // Original: return x + 10;
    // Modified: return x + 20;

    // x86 instruction: add eax, 10 (83 C0 0A)
    func_ptr[0] = 0x83;
    func_ptr[1] = 0xC0;
    func_ptr[2] = 0x0A;

    // Self-modify to: add eax, 20 (83 C0 14)
    // This change is invisible to static analysis!
    if (some_condition()) {
        func_ptr[2] = 0x14;  // Modify immediate value
    }

    // Execute the modified code
    int (*patched_func)(int) = (int (*)(int))func_ptr;
    int result = patched_func(5);
}

// Another example: encryption key embedded and modified
void self_modifying_key_derivation() {
    // Key derivation that modifies itself to hide algorithm
    static unsigned char key_function[] = {
        0x55,             // push rbp
        0x48, 0x89, 0xe5, // mov rbp, rsp
        // ... more code ...
        0xc3              // ret
    };

    // Make code writable (dangerous!)
    mprotect(key_function, sizeof(key_function),
             PROT_READ | PROT_WRITE | PROT_EXEC);

    // XOR the code with a runtime value to "unlock" it
    // This hides the real algorithm from static analysis
    for (int i = 0; i < sizeof(key_function); i++) {
        key_function[i] ^= runtime_key[i % key_length];
    }

    // Execute the now-decrypted function
    void (*derive_key)(void) = (void (*)(void))key_function;
    derive_key();
}
# Vulnerable: Python with excessive dynamic code generation

import types

class SelfModifyingClass:
    """Class that modifies its own methods at runtime."""

    def __init__(self):
        # Dynamically create and modify methods
        self._modify_methods()

    def _modify_methods(self):
        # Generate code from string - hard to audit
        auth_code = """
def authenticate(self, user, password):
    # This code is generated at runtime
    # Static analysis cannot see what it does!
    if user == 'admin':
        return True  # Backdoor hidden in dynamic code!
    return self._check_password(user, password)
"""
        exec(auth_code)
        self.authenticate = types.MethodType(
            locals()['authenticate'], self
        )

    def process_request(self, request):
        # Dynamically modify processing based on input
        # Attacker could inject code through request!
        handler_code = f"""
def handler(self, data):
    return data.{request.get('method', 'strip')}()
"""
        exec(handler_code)
        handler = types.MethodType(locals()['handler'], self)

        # Use the dynamically created handler
        return handler(request.get('data', ''))


# Another pattern: modifying global functions
def create_dynamic_validator():
    """Generate validation function at runtime."""

    # Build code string based on config
    code = "def validate(data):\n"
    code += "    # Dynamically generated validation\n"

    for rule in load_rules_from_somewhere():
        # Rules could contain malicious code!
        code += f"    if not ({rule['condition']}):\n"
        code += f"        return False, '{rule['message']}'\n"

    code += "    return True, 'Valid'\n"

    # Execute the generated code
    exec(code, globals())

    return validate  # Return the dynamically created function
// Vulnerable: JavaScript with excessive eval and dynamic code

class SelfModifyingAPI {
    constructor() {
        this.methods = {};
        this._generateMethods();
    }

    _generateMethods() {
        // Generate API methods from string templates
        // This is extremely hard to audit!

        const methodTemplate = `
            return async function(params) {
                ${this._getSecurityCheck()}
                ${this._getBusinessLogic()}
                return result;
            }
        `;

        // Create function from string
        this.methods.processPayment = eval(`(${methodTemplate})`);

        // Even worse: modify Function prototype
        Function.prototype.securityBypass = function() {
            // Hidden backdoor in prototype modification!
            return this.apply(null, arguments);
        };
    }

    _getSecurityCheck() {
        // Security logic built from strings - can't be statically analyzed
        return `
            if (params.skipAuth) {
                // Hidden bypass!
            } else {
                await this.authenticate(params.token);
            }
        `;
    }

    // Dynamically create functions based on user input
    createHandler(userDefinedLogic) {
        // DANGEROUS: Creates function from user input!
        const handler = new Function('data', userDefinedLogic);
        this.methods.customHandler = handler;
    }
}

// Self-modifying code through property descriptors
Object.defineProperty(target, 'secretMethod', {
    get: function() {
        // Method that modifies itself when accessed
        const originalImpl = this._secretImpl;

        // Redefine on first access (one-time pad style)
        Object.defineProperty(this, 'secretMethod', {
            value: function() {
                // New implementation - different from original!
                return originalImpl.apply(this, arguments);
            }
        });

        return this.secretMethod;
    }
});

Fixed Code

// Fixed: Avoid self-modifying code, use function pointers instead

#include <stdbool.h>

// Define operation types statically
typedef int (*Operation)(int);

int add_ten(int x) {
    return x + 10;
}

int add_twenty(int x) {
    return x + 20;
}

// Use function pointer instead of self-modifying code
int perform_operation(int x, bool use_larger_value) {
    // Select function at runtime without modifying code
    Operation op = use_larger_value ? add_twenty : add_ten;
    return op(x);
}

// For dynamic behavior, use configuration and dispatch tables

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) {
            // Check authentication if required
            if (operations[i].requires_auth && !is_authenticated) {
                return -1;  // Error: not authorized
            }
            return operations[i].handler(x);
        }
    }
    return -1;  // Error: unknown operation
}

// For key derivation, use standard crypto libraries
#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) {
    // Use standard, auditable crypto function
    return PKCS5_PBKDF2_HMAC(
        (const char *)password, pass_len,
        salt, salt_len,
        100000,  // iterations
        EVP_sha256(),
        key_len, key
    );
}
# Fixed: Use proper design patterns instead of self-modifying code

from abc import ABC, abstractmethod
from typing import Dict, Callable, Any


class Authenticator(ABC):
    """Abstract authenticator - behavior defined by subclass, not runtime modification."""

    @abstractmethod
    def authenticate(self, user: str, password: str) -> bool:
        pass


class StandardAuthenticator(Authenticator):
    """Standard authentication implementation - fully auditable."""

    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:
    """Process requests using configured handlers instead of dynamic code."""

    def __init__(self):
        # Define allowed operations statically
        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:
        """Process request using registered handler."""
        method = request.get('method', 'strip')
        data = request.get('data', '')

        # Only use registered, auditable handlers
        handler = self._handlers.get(method)
        if handler is None:
            raise ValueError(f"Unknown method: {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:
    """Validation using configured rules instead of generated 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:
        """Validate data against a list of rule names."""
        for rule_name in rules:
            validator = self._validators.get(rule_name)
            if validator is None:
                return False, f"Unknown rule: {rule_name}"

            if not validator(data):
                return False, f"Validation failed: {rule_name}"

        return True, "Valid"

    def _validate_email(self, value: Any) -> bool:
        """Validate email format."""
        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 for pluggable behavior
class PaymentStrategy(ABC):
    @abstractmethod
    def process(self, amount: float, details: dict) -> dict:
        pass


class CreditCardPayment(PaymentStrategy):
    def process(self, amount: float, details: dict) -> dict:
        # Auditable, static implementation
        return {"method": "credit_card", "amount": amount, "status": "processed"}


class PaymentProcessor:
    """Payment processor using strategy pattern."""

    def __init__(self):
        self._strategies: Dict[str, PaymentStrategy] = {
            'credit_card': CreditCardPayment(),
            # Add more strategies as needed
        }

    def process_payment(self, method: str, amount: float, details: dict) -> dict:
        strategy = self._strategies.get(method)
        if not strategy:
            raise ValueError(f"Unsupported payment method: {method}")
        return strategy.process(amount, details)

CVE Examples

This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality concern rather than a direct security vulnerability.


  • CWE-1120: Excessive Code Complexity (parent)
  • CWE-1226: Complexity Issues (category member)
  • CWE-94: Improper Control of Generation of Code ('Code Injection') (related)

References

  1. MITRE Corporation. "CWE-1123: Excessive Use of Self-Modifying Code." https://cwe.mitre.org/data/definitions/1123.html
  2. OWASP - Code Injection
  3. Memory Protection and DEP/NX guidelines