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
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Makes it more difficult to understand or maintain the product, indirectly affecting security by making vulnerabilities harder to find and fix. |
| Other | Scope: Other Increase Analytical Complexity - Complicates vulnerability detection and remediation while facilitating accidental security flaws. |
| Integrity | Scope: 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.
Related CWEs
- CWE-1120: Excessive Code Complexity (parent)
- CWE-1226: Complexity Issues (category member)
- CWE-94: Improper Control of Generation of Code ('Code Injection') (related)
References
- MITRE Corporation. "CWE-1123: Excessive Use of Self-Modifying Code." https://cwe.mitre.org/data/definitions/1123.html
- OWASP - Code Injection
- Memory Protection and DEP/NX guidelines