<?php
/**
 * VMPFence Badstrings Detection System
 * 
 *
 * Detects suspicious string combinations that indicate obfuscated malware
 * 
 * 
 * @package VMPFence
 * @since 1.0.0
 */

if (!defined('ABSPATH')) {
    exit;
}

class VMPFence_Badstrings_Detector {
    
    /**
     * Badstrings database - suspicious string combinations
     */
    private $badstrings_database = array();
    
    /**
     * Common encoding/obfuscation functions
     */
    private $encoding_functions = array();
    
    /**
     * Evaluation patterns that when combined with badstrings indicate malware
     */
    private $eval_patterns = array();
    
    /**
     * Badstrings detection performance metrics
     */
    private $performance_stats = array(
        'files_analyzed' => 0,
        'badstring_combinations_found' => 0,
        'eval_encodings_detected' => 0,
        'suspicious_patterns_matched' => 0,
        'false_positives_filtered' => 0
    );
    
    /**
     * Constructor - Initialize badstrings detection system
     */
    public function __construct() {
        $this->load_badstrings_database();
        $this->initialize_encoding_functions();
        $this->initialize_eval_patterns();
    }
    
    /**
     * Load badstrings database with suspicious string combinations
     * REFINED: Only ultra-specific malware combinations to avoid false positives
     */
    private function load_badstrings_database() {
        // ULTRA-SPECIFIC suspicious string combinations that indicate obfuscated malware
        // Only combinations that are rarely used in legitimate WordPress code
        $this->badstrings_database = array(
            // Category: Obfuscation indicators - VERY SPECIFIC ONLY
            'obfuscation' => array(
                'chr(', 'ord(', 'hex2bin(', 'dechex(', 'octdec(',
                'str_rot13(', 'strrev('  // Keep only these specific patterns
            ),
            
            // Category: Encoding functions - ONLY SUSPICIOUS COMBINATIONS
            'encoding' => array(
                'base64_decode(', 'rawurldecode(', 'convert_uudecode(',
                'gzinflate(', 'gzuncompress('
            ),
            
            // Category: Dynamic execution - DANGEROUS ONLY
            'execution' => array(
                'eval(', 'assert(', 'create_function(',
                'system(', 'exec(', 'shell_exec(', 'passthru('
            ),
        );
    }
    
    /**
     * Initialize encoding function patterns
     */
    private function initialize_encoding_functions() {
        $this->encoding_functions = array(
            'base64' => array(
                'decode' => 'base64_decode',
                'pattern' => '/base64_decode\s*\(\s*["\']([A-Za-z0-9+\/=]+)["\']\s*\)/',
                'min_length' => 20, // Minimum suspicious base64 length
                'max_length' => 10000 // Maximum to process (performance limit)
            ),
            'hex' => array(
                'decode' => 'hex2bin',
                'pattern' => '/hex2bin\s*\(\s*["\']([a-fA-F0-9]+)["\']\s*\)/',
                'min_length' => 10,
                'max_length' => 5000
            ),
            'rot13' => array(
                'decode' => 'str_rot13',
                'pattern' => '/str_rot13\s*\(\s*["\']([^"\']+)["\']\s*\)/',
                'min_length' => 5,
                'max_length' => 2000
            ),
            'url' => array(
                'decode' => 'urldecode',
                'pattern' => '/urldecode\s*\(\s*["\']([^"\']+)["\']\s*\)/',
                'min_length' => 10,
                'max_length' => 3000
            )
        );
    }
    
    /**
     * Initialize eval pattern detection
     */
    private function initialize_eval_patterns() {
        $this->eval_patterns = array(
            // Direct eval usage
            'direct_eval' => '/eval\s*\(\s*(.+?)\s*\)/s',
            
            // Assert as eval alternative
            'assert_eval' => '/assert\s*\(\s*(.+?)\s*\)/s',
            
            // Create function for dynamic code
            'create_function' => '/create_function\s*\(\s*["\'][^"\']*["\']\s*,\s*(.+?)\s*\)/s',
            
            // Preg replace /e modifier (deprecated but still dangerous)
            'preg_replace_e' => '/preg_replace\s*\(\s*["\'][^"\']*\/[^"\']*e[^"\']*["\']/s',
            
            // Call user func with dynamic function names
            'dynamic_call' => '/call_user_func(?:_array)?\s*\(\s*(.+?)\s*,/s',
            
            // Variable functions
            'variable_func' => '/\$[a-zA-Z_][a-zA-Z0-9_]*\s*\(\s*(.+?)\s*\)/s'
        );
    }
    
    /**
     * Main badstrings detection function
     * Analyze file content for suspicious string combinations
     */
    public function detect_eval_badstring_combinations($file_content, $file_path = '') {
        $this->performance_stats['files_analyzed']++;
        $detections = array();
        
        // Direct pattern matching for common malware signatures
        $patterns = array(
            'eval_base64' => array(
                'pattern' => '/eval\s*\(\s*base64_decode\s*\(/i',
                'encoding_type' => 'base64'
            ),
            'eval_hex2bin' => array(
                'pattern' => '/eval\s*\(\s*hex2bin\s*\(/i',
                'encoding_type' => 'hex'
            ),
            'eval_gzinflate' => array(
                'pattern' => '/eval\s*\(\s*gzinflate\s*\(/i',
                'encoding_type' => 'gzinflate'
            ),
            'eval_str_rot13' => array(
                'pattern' => '/eval\s*\(\s*str_rot13\s*\(/i',
                'encoding_type' => 'rot13'
            ),
            'assert_base64' => array(
                'pattern' => '/assert\s*\(\s*base64_decode\s*\(/i',
                'encoding_type' => 'base64'
            ),
            'eval_rawurldecode' => array(
                'pattern' => '/eval\s*\(\s*rawurldecode\s*\(/i',
                'encoding_type' => 'url'
            ),
            'eval_urldecode' => array(
                'pattern' => '/eval\s*\(\s*urldecode\s*\(/i',
                'encoding_type' => 'url'
            )
        );
        
        foreach ($patterns as $pattern_name => $pattern_info) {
            if (preg_match_all($pattern_info['pattern'], $file_content, $matches, PREG_OFFSET_CAPTURE)) {
                foreach ($matches[0] as $match) {
                    $detections[] = array(
                        'type' => 'eval_encoding_combination',
                        'pattern' => $pattern_name,
                        'encoding_type' => $pattern_info['encoding_type'],
                        'position' => $match[1],
                        'context' => substr($file_content, max(0, $match[1] - 30), 100),
                        'risk_level' => 'high',
                        'description' => 'Suspicious eval + encoding combination detected'
                    );
                    $this->performance_stats['eval_encodings_detected']++;
                }
            }
        }
        
        // Only filter obvious false positives from WordPress core
        $filtered_detections = array();
        foreach ($detections as $detection) {
            // Skip filtering for now - let's see if basic detection works first
            $filtered_detections[] = $detection;
        }
        
        return $filtered_detections;
    }
    
    /**
     * Analyze eval + encoding combinations (primary malware indicator)
     */
    private function analyze_eval_encoding_combinations($file_content) {
        $combinations = array();
        
        foreach ($this->eval_patterns as $eval_type => $eval_pattern) {
            if (preg_match_all($eval_pattern, $file_content, $eval_matches, PREG_OFFSET_CAPTURE)) {
                // Use full matches if capture group doesn't exist, otherwise use capture group
                $matches_to_check = isset($eval_matches[1]) ? $eval_matches[1] : $eval_matches[0];
                foreach ($matches_to_check as $match) {
                    $eval_content = $match[0];
                    $eval_position = $match[1];
                    
                    // Check if eval content contains encoding functions
                    foreach ($this->encoding_functions as $encoding_type => $encoding_info) {
                        if (strpos($eval_content, $encoding_info['decode']) !== false) {
                            // Extract encoded content
                            if (preg_match($encoding_info['pattern'], $eval_content, $encoded_match)) {
                                $encoded_data = $encoded_match[1];
                                
                                // Validate encoded data length
                                if (strlen($encoded_data) >= $encoding_info['min_length'] && 
                                    strlen($encoded_data) <= $encoding_info['max_length']) {
                                    
                                    $combinations[] = array(
                                        'type' => 'eval_encoding_combination',
                                        'eval_type' => $eval_type,
                                        'encoding_type' => $encoding_type,
                                        'encoded_data' => substr($encoded_data, 0, 200), // Limit for safety
                                        'position' => $eval_position,
                                        'risk_level' => 'high',
                                        'context' => substr($file_content, max(0, $eval_position - 50), 150)
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }
        
        return $combinations;
    }
    
    /**
     * Analyze suspicious string patterns
     */
    public function analyze_string_patterns($file_content) {
        $patterns = array();
        
        // Pattern 1: Multiple encoding functions in close proximity
        $encoding_functions = implode('|', array_column($this->encoding_functions, 'decode'));
        $multi_encoding_pattern = '/(' . $encoding_functions . ')\s*\(\s*(' . $encoding_functions . ')/';
        
        if (preg_match_all($multi_encoding_pattern, $file_content, $matches, PREG_OFFSET_CAPTURE)) {
            foreach ($matches[0] as $i => $match) {
                $patterns[] = array(
                    'type' => 'multiple_encoding_layers',
                    'functions' => array($matches[1][$i][0], $matches[2][$i][0]),
                    'position' => $match[1],
                    'risk_level' => 'medium',
                    'context' => substr($file_content, max(0, $match[1] - 30), 100)
                );
            }
        }
        
        // Pattern 2: Obfuscated variable names with eval
        $obfuscated_var_pattern = '/\$[O0l1I]{3,}\s*=.*?eval/i';
        if (preg_match_all($obfuscated_var_pattern, $file_content, $matches, PREG_OFFSET_CAPTURE)) {
            foreach ($matches[0] as $match) {
                $patterns[] = array(
                    'type' => 'obfuscated_variable_eval',
                    'pattern' => substr($match[0], 0, 100),
                    'position' => $match[1],
                    'risk_level' => 'high',
                    'context' => substr($file_content, max(0, $match[1] - 20), 80)
                );
            }
        }
        
        // Pattern 3: Concatenated function names
        $concat_func_pattern = '/["\']([a-z_]+)["\']\s*\.\s*["\']([a-z_]+)["\']\s*\(/';
        if (preg_match_all($concat_func_pattern, $file_content, $matches, PREG_OFFSET_CAPTURE)) {
            foreach ($matches[0] as $i => $match) {
                $combined_function = $matches[1][$i][0] . $matches[2][$i][0];
                
                // Check if combined function is suspicious
                if (in_array($combined_function, $this->badstrings_database['execution'])) {
                    $patterns[] = array(
                        'type' => 'concatenated_function_name',
                        'function' => $combined_function,
                        'parts' => array($matches[1][$i][0], $matches[2][$i][0]),
                        'position' => $match[1],
                        'risk_level' => 'medium',
                        'context' => substr($file_content, max(0, $match[1] - 25), 75)
                    );
                }
            }
        }
        
        return $patterns;
    }
    
    /**
     * Detect badstring function combinations
     * STRICT: Requires combinations across DIFFERENT categories for higher accuracy
     */
    private function detect_badstring_function_combinations($file_content) {
        $combinations = array();
        $category_hits = array();
        
        // Check for suspicious function presence across all categories
        foreach ($this->badstrings_database as $category => $functions) {
            $found_functions = array();
            
            foreach ($functions as $function) {
                if (preg_match_all('/\b' . preg_quote($function, '/') . '\s*\(/i', $file_content, $matches, PREG_OFFSET_CAPTURE)) {
                    $found_functions[$function] = count($matches[0]);
                }
            }
            
            if (!empty($found_functions)) {
                $category_hits[$category] = $found_functions;
            }
        }
        
        // STRICT REQUIREMENT: Must have functions from at least 2 different categories
        // AND at least one execution function (eval, assert, etc.)
        if (count($category_hits) >= 2 && isset($category_hits['execution'])) {
            $total_suspicious_functions = 0;
            foreach ($category_hits as $functions) {
                $total_suspicious_functions += count($functions);
            }
            
            // EVEN STRICTER: Require at least 4 total suspicious functions across categories
            if ($total_suspicious_functions >= 4) {
                $combinations[] = array(
                    'type' => 'multi_category_badstring_cluster',
                    'categories' => array_keys($category_hits),
                    'category_functions' => $category_hits,
                    'total_functions' => $total_suspicious_functions,
                    'risk_level' => 'high' // Always high risk when multiple categories present
                );
            }
        }
        
        return $combinations;
    }
    
    /**
     * Filter false positives based on context and file type
     */
    private function filter_false_positives($suspicious_combinations, $file_path) {
        $filtered_results = array();
        
        // Get file context for better filtering
        $file_extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        $is_core_file = (strpos($file_path, '/wp-admin/') !== false || 
                        strpos($file_path, '/wp-includes/') !== false);
        
        foreach ($suspicious_combinations as $category => $combinations) {
            foreach ($combinations as $combination) {
                $is_false_positive = false;
                
                // Skip certain patterns in core WordPress files
                if ($is_core_file) {
                    if ($combination['type'] === 'multiple_encoding_layers' && 
                        count($combination['functions']) === 2) {
                        $is_false_positive = true; // Common in WP core
                    }
                }
                
                // Skip legitimate plugin patterns
                if (strpos($file_path, '/plugins/') !== false) {
                    // Allow base64 usage in legitimate plugins (but still flag complex patterns)
                    if ($combination['type'] === 'eval_encoding_combination' && 
                        $combination['encoding_type'] === 'base64' &&
                        strlen($combination['encoded_data']) < 50) {
                        $is_false_positive = true;
                    }
                }
                
                // Skip common theme functions
                if (strpos($file_path, '/themes/') !== false) {
                    if ($combination['type'] === 'badstring_function_cluster' &&
                        $combination['category'] === 'file_ops' &&
                        count($combination['functions']) < 5) {
                        $is_false_positive = true; // Common in themes
                    }
                }
                
                if (!$is_false_positive) {
                    $filtered_results[] = $combination;
                } else {
                    $this->performance_stats['false_positives_filtered']++;
                }
            }
        }
        
        return $filtered_results;
    }
    
    /**
     * Get badstrings detection performance statistics
     */
    public function get_performance_stats() {
        return $this->performance_stats;
    }
    
    /**
     * Reset performance statistics
     */
    public function reset_performance_stats() {
        $this->performance_stats = array(
            'files_analyzed' => 0,
            'badstring_combinations_found' => 0,
            'eval_encodings_detected' => 0,
            'suspicious_patterns_matched' => 0,
            'false_positives_filtered' => 0
        );
    }
    
    /**
     * Validate and test badstrings detection
     */
    public function validate_badstrings_system() {
        $validation_results = array(
            'badstrings_categories' => count($this->badstrings_database),
            'total_badstrings' => array_sum(array_map('count', $this->badstrings_database)),
            'encoding_functions' => count($this->encoding_functions),
            'eval_patterns' => count($this->eval_patterns),
            'system_ready' => true
        );
        
        // Validate each category has minimum required badstrings
        foreach ($this->badstrings_database as $category => $functions) {
            if (count($functions) < 3) {
                $validation_results['warnings'][] = "Category '{$category}' has only " . count($functions) . " badstrings";
            }
        }
        
        return $validation_results;
    }
}
