<?php
/**
 * 
 * 
 * High sensitivity detection + binary scanning for sophisticated malware
 * with obfuscation + scan images/binaries
 * 
 * 
 */

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

// Include centralized exclusion manager
require_once plugin_dir_path(__FILE__) . 'class-scan-exclusion-manager.php';

class VMPFence_Sensitivity_Scanner {
    
    private $sensitivity_mode;
    private $obfuscation_patterns;
    private $context_analysis_enabled;
    private $base64_detection_enabled;
    
    /**
     * Centralized exclusion manager for consistent exclusion logic
     */
    private $exclusion_manager;
    private $entropy_threshold;
    private $scan_statistics;
    
    public function __construct() {
        $this->sensitivity_mode = 'medium'; // Default: Low/Medium/High
        $this->context_analysis_enabled = false;
        $this->base64_detection_enabled = false;
        $this->entropy_threshold = 4.5; // Shannon entropy threshold for obfuscation
        $this->scan_statistics = array();
        
        // Initialize centralized exclusion manager
        $this->exclusion_manager = VMPFence_Scan_Exclusion_Manager::getInstance();
        
        $this->load_obfuscation_patterns();
    }
    
    /**
     * Enable high sensitivity mode
     */
    public function enable_high_sensitivity() {
        $this->sensitivity_mode = 'high';
        $this->context_analysis_enabled = true;
        $this->base64_detection_enabled = true;
        $this->entropy_threshold = 3.8; // Lower threshold = more sensitive
        
        return array(
            'mode' => $this->sensitivity_mode,
            'context_analysis' => $this->context_analysis_enabled,
            'base64_detection' => $this->base64_detection_enabled,
            'entropy_threshold' => $this->entropy_threshold
        );
    }
    
    /**
     * Scan with context analysis
     */
    public function scan_with_context($file_content, $file_path = '') {
        // Check exclusion manager first
        if (!empty($file_path) && $this->exclusion_manager->should_exclude_file($file_path)) {
            return array(
                'file_path' => $file_path,
                'excluded' => true,
                'context_issues' => array(),
                'obfuscation_detected' => false,
                'base64_issues' => array(),
                'entropy_analysis' => array(),
                'suspicious_patterns' => array()
            );
        }
        
        $results = array(
            'file_path' => $file_path,
            'context_issues' => array(),
            'obfuscation_detected' => false,
            'base64_issues' => array(),
            'entropy_analysis' => array(),
            'suspicious_patterns' => array()
        );
        
        if (!$this->context_analysis_enabled) {
            return $results;
        }
        
        // Context analysis: Look for code patterns with before/after analysis
        $context_issues = $this->analyze_code_context($file_content);
        if (!empty($context_issues)) {
            $results['context_issues'] = $context_issues;
        }
        
        // Obfuscation analysis
        $obfuscation_result = $this->analyze_obfuscation($file_content);
        if ($obfuscation_result['detected']) {
            $results['obfuscation_detected'] = true;
            $results['obfuscation_details'] = $obfuscation_result;
        }
        
        // Base64 malware detection
        if ($this->base64_detection_enabled) {
            $base64_issues = $this->detect_base64_malware($file_content);
            if (!empty($base64_issues)) {
                $results['base64_issues'] = $base64_issues;
            }
        }
        
        // Entropy analysis for packed/obfuscated code
        $entropy_analysis = $this->calculate_entropy_analysis($file_content);
        if ($entropy_analysis['suspicious']) {
            $results['entropy_analysis'] = $entropy_analysis;
        }
        
        return $results;
    }
    
    /**
     * Analyze obfuscation patterns
     */
    public function analyze_obfuscation($content) {
        $result = array(
            'detected' => false,
            'obfuscation_types' => array(),
            'confidence_score' => 0,
            'entropy_score' => 0,
            'suspicious_functions' => array()
        );
        
        // Calculate Shannon entropy
        $entropy = $this->calculate_shannon_entropy($content);
        $result['entropy_score'] = $entropy;
        
        if ($entropy > $this->entropy_threshold) {
            $result['detected'] = true;
            $result['obfuscation_types'][] = 'high_entropy';
            $result['confidence_score'] += 30;
        }
        
        // Check for common obfuscation patterns
        foreach ($this->obfuscation_patterns as $pattern_name => $pattern) {
            if (preg_match($pattern['regex'], $content, $matches)) {
                $result['detected'] = true;
                $result['obfuscation_types'][] = $pattern_name;
                $result['confidence_score'] += $pattern['weight'];
                
                if (isset($matches[0])) {
                    $result['suspicious_functions'][] = array(
                        'type' => $pattern_name,
                        'match' => substr($matches[0], 0, 100) // Limit match length
                    );
                }
            }
        }
        
        // Check for variable function calls
        if (preg_match_all('/\$[a-zA-Z_][a-zA-Z0-9_]*\s*\(/i', $content, $var_functions)) {
            if (count($var_functions[0]) > 5) {
                $result['detected'] = true;
                $result['obfuscation_types'][] = 'variable_functions';
                $result['confidence_score'] += 20;
            }
        }
        
        // Check for excessive string concatenation (obfuscation technique)
        if (preg_match_all('/["\'][^"\']*["\'][.\s]*["\'][^"\']*["\']/', $content, $concat_matches)) {
            if (count($concat_matches[0]) > 10) {
                $result['detected'] = true;
                $result['obfuscation_types'][] = 'string_concatenation';
                $result['confidence_score'] += 15;
            }
        }
        
        $result['confidence_score'] = min($result['confidence_score'], 100);
        
        return $result;
    }
    
    /**
     * Scan image files for malware
     */
    public function scan_image_files($file_path) {
        $result = array(
            'file_path' => $file_path,
            'file_type' => '',
            'threats_found' => array(),
            'metadata_issues' => array(),
            'scan_complete' => true
        );
        
        if (!file_exists($file_path)) {
            return $result;
        }
        
        $file_info = pathinfo($file_path);
        $extension = strtolower($file_info['extension'] ?? '');
        $result['file_type'] = $extension;
        
        // Check if file is actually an image
        $image_extensions = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg');
        
        if (!in_array($extension, $image_extensions)) {
            return $result;
        }
        
        // Read file content
        $content = file_get_contents($file_path);
        if ($content === false) {
            return $result;
        }
        
        // SVG files can contain JavaScript - scan as code
        if ($extension === 'svg') {
            $svg_analysis = $this->scan_svg_malware($content);
            if (!empty($svg_analysis)) {
                foreach ($svg_analysis as $threat) {
                    $result['threats_found'][] = array(
                        'type' => 'svg_malware',
                        'risk_level' => 'high',
                        'details' => $threat['description']
                    );
                }
            }
        }
        
        // Check for embedded PHP code in image files
        $php_patterns = array(
            '/(<\?php|<\?=|\?>)/' => 'PHP tags in image file',
            '/eval\s*\(/i' => 'Eval function in image',
            '/base64_decode\s*\(/i' => 'Base64 decode in image',
            '/system\s*\(/i' => 'System command in image',
            '/exec\s*\(/i' => 'Exec function in image',
            '/shell_exec\s*\(/i' => 'Shell exec in image'
        );
        
        foreach ($php_patterns as $pattern => $description) {
            if (preg_match($pattern, $content, $matches)) {
                $result['threats_found'][] = array(
                    'type' => 'embedded_php',
                    'risk_level' => 'high',
                    'details' => $description . ': ' . substr($matches[0], 0, 50)
                );
            }
        }
        
        // Check for suspicious strings in image metadata/content
        $suspicious_strings = array(
            'eval(' => 'Eval function call',
            'base64_decode(' => 'Base64 decode call',
            'gzinflate(' => 'Gzinflate function call',
            'str_rot13(' => 'ROT13 obfuscation',
            'create_function(' => 'Dynamic function creation',
            'call_user_func' => 'Dynamic function execution',
            '$_GET' => 'User input handling',
            '$_POST' => 'User input handling',
            '$_REQUEST' => 'User input handling',
            '$_COOKIE' => 'Cookie input handling'
        );
        
        foreach ($suspicious_strings as $suspicious => $description) {
            if (stripos($content, $suspicious) !== false) {
                $result['threats_found'][] = array(
                    'type' => 'suspicious_string',
                    'risk_level' => 'medium',
                    'details' => $description
                );
            }
        }
        
        // EXIF metadata analysis (for JPEG files)
        if (in_array($extension, array('jpg', 'jpeg')) && function_exists('exif_read_data')) {
            $exif_analysis = $this->analyze_image_metadata($file_path);
            if (!empty($exif_analysis)) {
                $result['metadata_issues'] = $exif_analysis;
                if (!empty($exif_analysis['malicious'])) {
                    $result['malware_detected'] = true;
                }
            }
        }
        
        return $result;
    }
    
    /**
     * Detect binary malware
     */
    public function detect_binary_malware($file_path) {
        $result = array(
            'file_path' => $file_path,
            'binary_signatures' => array(),
            'executable_headers' => array(),
            'malware_detected' => false,
            'file_analysis' => array()
        );
        
        if (!file_exists($file_path)) {
            return $result;
        }
        
        // Read first few bytes for header analysis
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Binary file analysis requires direct file operations for security scanning
        $handle = fopen($file_path, 'rb');
        if (!$handle) {
            return $result;
        }
        
        $header = fread($handle, 1024); // Read first 1KB
        fclose($handle);
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        
        if ($header === false || strlen($header) < 4) {
            return $result;
        }
        
        // Check for executable file headers
        $executable_signatures = array(
            'MZ' => 'PE/DOS Executable',
            'PK' => 'ZIP Archive/JAR',
            '%PDF' => 'PDF Document',
            "\x7fELF" => 'ELF Executable',
            "\xCA\xFE\xBA\xBE" => 'Java Class File',
            "\xFF\xD8\xFF" => 'JPEG Image',
            "\x89PNG" => 'PNG Image'
        );
        
        foreach ($executable_signatures as $signature => $description) {
            if (strpos($header, $signature) === 0) {
                $result['executable_headers'][] = array(
                    'signature' => bin2hex($signature),
                    'type' => $description,
                    'position' => 0
                );
                
                // Flag certain types as potentially suspicious
                if (in_array($signature, array('MZ', "\x7fELF", "\xCA\xFE\xBA\xBE"))) {
                    $result['malware_detected'] = true;
                }
            }
        }
        
        // Check for suspicious binary patterns
        $suspicious_patterns = array(
            '/cmd\.exe/i',
            '/powershell/i',
            '/eval\(/i',
            '/base64_decode/i'
        );
        
        foreach ($suspicious_patterns as $pattern) {
            if (preg_match($pattern, $header)) {
                $result['binary_signatures'][] = array(
                    'pattern' => $pattern,
                    'description' => 'Suspicious command found in binary data'
                );
                $result['malware_detected'] = true;
            }
        }
        
        return $result;
    }
    
    /**
     * Load obfuscation detection patterns
     */
    private function load_obfuscation_patterns() {
        $this->obfuscation_patterns = array(
            'eval_base64' => array(
                'regex' => '/eval\s*\(\s*base64_decode\s*\(/i',
                'weight' => 40,
                'description' => 'Base64 encoded eval execution'
            ),
            'gzinflate_base64' => array(
                'regex' => '/gzinflate\s*\(\s*base64_decode\s*\(/i',
                'weight' => 35,
                'description' => 'Compressed and encoded data execution'
            ),
            'str_rot13' => array(
                'regex' => '/str_rot13\s*\(\s*["\'][^"\']*["\']\s*\)/i',
                'weight' => 25,
                'description' => 'ROT13 string obfuscation'
            ),
            'create_function' => array(
                'regex' => '/create_function\s*\(\s*["\'][^"\']*["\']\s*,/i',
                'weight' => 30,
                'description' => 'Dynamic function creation'
            ),
            'preg_replace_eval' => array(
                'regex' => '/preg_replace\s*\(\s*["\'][^"\']*["\'].*["\'].*e["\'].*\)/i',
                'weight' => 45,
                'description' => 'Regex with eval modifier (deprecated but dangerous)'
            ),
            'hex_decode' => array(
                'regex' => '/pack\s*\(\s*["\']H\*["\'].*\)/i',
                'weight' => 20,
                'description' => 'Hex-encoded string unpacking'
            ),
            'variable_variables' => array(
                'regex' => '/\$\$[a-zA-Z_][a-zA-Z0-9_]*/i',
                'weight' => 15,
                'description' => 'Variable variables (potential obfuscation)'
            )
        );
    }
    
    /**
     * Analyze code context (before/after patterns)
     */
    private function analyze_code_context($content) {
        $context_issues = array();
        
        // Split content into lines for context analysis
        $lines = explode("\n", $content);
        $total_lines = count($lines);
        
        // Look for suspicious patterns with context
        $suspicious_functions = array('eval', 'exec', 'system', 'shell_exec', 'passthru', 'file_get_contents');
        
        foreach ($suspicious_functions as $func) {
            $pattern = '/\b' . preg_quote($func, '/') . '\s*\(/i';
            
            for ($i = 0; $i < $total_lines; $i++) {
                if (preg_match($pattern, $lines[$i], $matches, PREG_OFFSET_CAPTURE)) {
                    // Analyze context (3 lines before and after)
                    $context_start = max(0, $i - 3);
                    $context_end = min($total_lines - 1, $i + 3);
                    
                    $before_context = array();
                    $after_context = array();
                    
                    for ($j = $context_start; $j < $i; $j++) {
                        $before_context[] = trim($lines[$j]);
                    }
                    
                    for ($j = $i + 1; $j <= $context_end; $j++) {
                        $after_context[] = trim($lines[$j]);
                    }
                    
                    $context_issues[] = array(
                        'function' => $func,
                        'line' => $i + 1,
                        'match' => trim($lines[$i]),
                        'before_context' => $before_context,
                        'after_context' => $after_context,
                        'risk_level' => $this->assess_context_risk($before_context, $after_context, $func)
                    );
                }
            }
        }
        
        return $context_issues;
    }
    
    /**
     * Assess risk level based on context
     */
    private function assess_context_risk($before, $after, $function) {
        $risk_score = 0;
        
        // Check for encoding/decoding in context
        $encoding_patterns = array('base64_decode', 'gzinflate', 'str_rot13', 'hex2bin');
        $context_text = implode(' ', array_merge($before, $after));
        
        foreach ($encoding_patterns as $encoding) {
            if (stripos($context_text, $encoding) !== false) {
                $risk_score += 25;
            }
        }
        
        // Check for user input handling
        $input_patterns = array('$_GET', '$_POST', '$_REQUEST', '$_COOKIE');
        foreach ($input_patterns as $input) {
            if (stripos($context_text, $input) !== false) {
                $risk_score += 30;
            }
        }
        
        // Check for file operations with user input
        $file_patterns = array('file_get_contents', 'fopen', 'include', 'require');
        foreach ($file_patterns as $file_op) {
            if (stripos($context_text, $file_op) !== false) {
                // File operations with user input are medium risk
                if ($function === 'file_get_contents' && $risk_score < 25) {
                    $risk_score += 25; // Ensure file_get_contents gets at least medium
                } else {
                    $risk_score += 15;
                }
            }
        }
        
        // Determine risk level
        if ($risk_score >= 50) {
            return 'high';
        } elseif ($risk_score >= 25) {
            return 'medium';
        } else {
            return 'low';
        }
    }
    
    /**
     * Detect base64 encoded malware
     */
    private function detect_base64_malware($content) {
        $base64_issues = array();
        
        // Find base64_decode calls
        if (preg_match_all('/base64_decode\s*\(\s*["\']([A-Za-z0-9+\/=]+)["\']/', $content, $matches, PREG_OFFSET_CAPTURE)) {
            foreach ($matches[1] as $index => $match) {
                $encoded_data = $match[0];
                $position = $match[1];
                
                // Decode and analyze
                $decoded = base64_decode($encoded_data, true);
                if ($decoded !== false) {
                    $analysis = $this->analyze_decoded_content($decoded);
                    if ($analysis['suspicious']) {
                        $base64_issues[] = array(
                            'position' => $position,
                            'encoded_length' => strlen($encoded_data),
                            'decoded_length' => strlen($decoded),
                            'analysis' => $analysis,
                            'encoded_preview' => substr($encoded_data, 0, 50) . '...'
                        );
                    }
                }
            }
        }
        
        return $base64_issues;
    }
    
    /**
     * Analyze decoded base64 content
     */
    private function analyze_decoded_content($decoded) {
        $analysis = array(
            'suspicious' => false,
            'indicators' => array(),
            'entropy' => 0
        );
        
        // Calculate entropy
        $entropy = $this->calculate_shannon_entropy($decoded);
        $analysis['entropy'] = $entropy;
        
        if ($entropy > 6.0) {
            $analysis['suspicious'] = true;
            $analysis['indicators'][] = 'high_entropy';
        }
        
        // Check for suspicious patterns
        $suspicious_patterns = array(
            '/eval\s*\(/i' => 'eval_function',
            '/exec\s*\(/i' => 'exec_function', 
            '/system\s*\(/i' => 'system_function',
            '/shell_exec\s*\(/i' => 'shell_exec_function',
            '/file_get_contents\s*\(/i' => 'file_access',
            '/curl_exec\s*\(/i' => 'network_access',
            '/\$_GET|\$_POST|\$_REQUEST/i' => 'user_input',
            '/<\?php/i' => 'embedded_php'
        );
        
        foreach ($suspicious_patterns as $pattern => $indicator) {
            if (preg_match($pattern, $decoded)) {
                $analysis['suspicious'] = true;
                $analysis['indicators'][] = $indicator;
            }
        }
        
        return $analysis;
    }
    
    /**
     * Calculate Shannon entropy
     */
    private function calculate_shannon_entropy($data) {
        $frequency = array();
        $length = strlen($data);
        
        if ($length == 0) {
            return 0;
        }
        
        // Count character frequencies
        for ($i = 0; $i < $length; $i++) {
            $char = $data[$i];
            if (!isset($frequency[$char])) {
                $frequency[$char] = 0;
            }
            $frequency[$char]++;
        }
        
        // Calculate entropy
        $entropy = 0;
        foreach ($frequency as $count) {
            $probability = $count / $length;
            if ($probability > 0) {
                $entropy -= $probability * log($probability, 2);
            }
        }
        
        return $entropy;
    }
    
    /**
     * Calculate entropy analysis with recommendations
     */
    private function calculate_entropy_analysis($content) {
        $entropy = $this->calculate_shannon_entropy($content);
        
        $analysis = array(
            'entropy' => $entropy,
            'suspicious' => false,
            'recommendation' => '',
            'confidence' => 0
        );
        
        if ($entropy > 7.0) {
            $analysis['suspicious'] = true;
            $analysis['recommendation'] = 'Very high entropy - likely packed or heavily obfuscated';
            $analysis['confidence'] = 90;
        } elseif ($entropy > 6.0) {
            $analysis['suspicious'] = true;
            $analysis['recommendation'] = 'High entropy - possibly obfuscated or compressed';
            $analysis['confidence'] = 70;
        } elseif ($entropy > $this->entropy_threshold) {
            $analysis['suspicious'] = true;
            $analysis['recommendation'] = 'Moderate entropy - potential obfuscation detected';
            $analysis['confidence'] = 50;
        } else {
            $analysis['recommendation'] = 'Normal entropy levels';
            $analysis['confidence'] = 10;
        }
        
        return $analysis;
    }
    
    /**
     * Scan SVG files for JavaScript malware
     */
    private function scan_svg_malware($svg_content) {
        $issues = array();
        
        // SVG-specific malware patterns
        $svg_patterns = array(
            '/<script[^>]*>/i' => 'Embedded JavaScript in SVG',
            '/on\w+\s*=/i' => 'JavaScript event handler',
            '/javascript:/i' => 'JavaScript protocol usage',
            '/document\.cookie/i' => 'Cookie access attempt',
            '/window\.location/i' => 'Location manipulation',
            '/eval\s*\(/i' => 'Eval function execution',
            '/XMLHttpRequest/i' => 'AJAX request capability'
        );
        
        foreach ($svg_patterns as $pattern => $description) {
            if (preg_match($pattern, $svg_content, $matches)) {
                $issues[] = array(
                    'type' => 'svg_script',
                    'pattern' => $pattern,
                    'match' => substr($matches[0], 0, 100),
                    'description' => $description,
                    'severity' => 'high'
                );
            }
        }
        
        return $issues;
    }
    
    /**
     * Analyze image metadata for malicious content
     */
    private function analyze_image_metadata($file_path) {
        $metadata_analysis = array(
            'malicious' => array(),
            'suspicious' => array(),
            'metadata' => array()
        );
        
        if (!function_exists('exif_read_data')) {
            return $metadata_analysis;
        }
        
        try {
            $exif_data = @exif_read_data($file_path);
            
            if ($exif_data === false) {
                return $metadata_analysis;
            }
            
            $metadata_analysis['metadata'] = $exif_data;
            
            // Check for suspicious strings in EXIF data
            $suspicious_patterns = array(
                '/eval\(/i',
                '/base64_decode/i',
                '/exec\(/i',
                '/system\(/i',
                '/<\?php/i',
                '/\$_GET|\$_POST/i'
            );
            
            foreach ($exif_data as $key => $value) {
                if (is_string($value)) {
                    foreach ($suspicious_patterns as $pattern) {
                        if (preg_match($pattern, $value)) {
                            $metadata_analysis['malicious'][] = array(
                                'field' => $key,
                                'pattern' => $pattern,
                                'value_preview' => substr($value, 0, 50)
                            );
                        }
                    }
                    
                    // Check for unusually long metadata fields (potential payload)
                    if (strlen($value) > 1000) {
                        $metadata_analysis['suspicious'][] = array(
                            'field' => $key,
                            'issue' => 'unusually_long_metadata',
                            'length' => strlen($value)
                        );
                    }
                }
            }
        } catch (Exception $e) {
            // Handle EXIF parsing errors silently
        }
        
        return $metadata_analysis;
    }
    
    /**
     * Get current sensitivity settings
     */
    public function get_sensitivity_settings() {
        return array(
            'mode' => $this->sensitivity_mode,
            'context_analysis' => $this->context_analysis_enabled,
            'base64_detection' => $this->base64_detection_enabled,
            'entropy_threshold' => $this->entropy_threshold,
            'obfuscation_patterns_count' => count($this->obfuscation_patterns)
        );
    }
    
    /**
     * Get scan statistics
     */
    public function get_scan_statistics() {
        return $this->scan_statistics;
    }
}
