UNPKG

pury

Version:

🛡️ AI-powered security scanner with advanced threat detection, dual reporting system (detailed & summary), and comprehensive code analysis

304 lines 12.6 kB
import { FindingType, Severity } from '../types/index.js'; import { logger } from '../utils/logger.js'; import { extractLineContext } from '../utils/file-utils.js'; export class MalwareAnalyzer { patterns; sensitivity; constructor(sensitivity = 'medium') { this.sensitivity = sensitivity; this.patterns = this.loadPatterns(); } async analyze(files, onProgress) { const startTime = Date.now(); const findings = []; for (let i = 0; i < files.length; i++) { const file = files[i]; onProgress?.(i + 1, files.length, file.path); try { const fileFindings = await this.analyzeFile(file); findings.push(...fileFindings); } catch (error) { logger.warn(`Failed to analyze file ${file.path}: ${error.message}`); } } const processingTime = Date.now() - startTime; logger.debug(`Malware analysis completed in ${processingTime}ms`); return { findings, processingTime, filesAnalyzed: files.length }; } async analyzeFile(file) { const findings = []; const lines = file.content.split('\n'); // Check each pattern against the file content for (const pattern of this.patterns) { if (!this.shouldApplyPattern(pattern)) { continue; } const matches = this.findPatternMatches(file.content, lines, pattern); findings.push(...matches.map(match => this.createFinding(match, file, pattern))); } // Additional heuristic analysis const heuristicFindings = this.performHeuristicAnalysis(file, lines); findings.push(...heuristicFindings); return findings; } shouldApplyPattern(pattern) { switch (this.sensitivity) { case 'low': return pattern.severity === Severity.CRITICAL || pattern.severity === Severity.HIGH; case 'medium': return pattern.severity !== Severity.LOW; case 'high': return true; default: return true; } } findPatternMatches(content, lines, pattern) { const matches = []; if (pattern.pattern instanceof RegExp) { // Line-by-line regex matching for better line number tracking lines.forEach((line, index) => { const match = line.match(pattern.pattern); if (match) { matches.push({ line: index + 1, match: match[0], context: extractLineContext(content, index + 1, 2) }); } }); } else { // String pattern matching const searchString = pattern.pattern; lines.forEach((line, index) => { if (line.toLowerCase().includes(searchString.toLowerCase())) { matches.push({ line: index + 1, match: line.trim(), context: extractLineContext(content, index + 1, 2) }); } }); } return matches; } createFinding(match, file, pattern) { return { id: `malware-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, type: FindingType.MALWARE, severity: pattern.severity, title: pattern.name, description: pattern.description, file: file.path, line: match.line, evidence: match.match, suggestion: this.getSuggestion(pattern), references: [`Pattern ID: ${pattern.id}`, `Category: ${pattern.category}`] }; } performHeuristicAnalysis(file, lines) { const findings = []; // Entropy analysis for obfuscated code const highEntropyLines = this.findHighEntropyLines(lines); highEntropyLines.forEach(({ line, entropy, content }) => { if (entropy > 4.5) { // Threshold for suspicious entropy findings.push({ id: `entropy-${Date.now()}-${line}`, type: FindingType.MALWARE, severity: entropy > 5.5 ? Severity.HIGH : Severity.MEDIUM, title: 'High Entropy Content Detected', description: `Line contains high entropy content (${entropy.toFixed(2)}) which may indicate obfuscated or encoded data`, file: file.path, line, evidence: content.substring(0, 100) + (content.length > 100 ? '...' : ''), suggestion: 'Review this content for potential obfuscation or encoding. Ensure it serves a legitimate purpose.' }); } }); // Check for suspicious concatenation patterns const suspiciousConcatenation = this.findSuspiciousConcatenation(lines); suspiciousConcatenation.forEach(({ line, content }) => { findings.push({ id: `concat-${Date.now()}-${line}`, type: FindingType.MALWARE, severity: Severity.MEDIUM, title: 'Suspicious String Concatenation', description: 'Detected string concatenation that may be used to evade static analysis', file: file.path, line, evidence: content, suggestion: 'Review the purpose of this string concatenation. Consider using clearer string construction methods.' }); }); return findings; } findHighEntropyLines(lines) { return lines .map((content, index) => ({ line: index + 1, entropy: this.calculateEntropy(content), content })) .filter(item => item.entropy > 4.0 && item.content.length > 20); } calculateEntropy(str) { const charCounts = new Map(); for (const char of str) { charCounts.set(char, (charCounts.get(char) || 0) + 1); } let entropy = 0; const { length } = str; for (const count of charCounts.values()) { const probability = count / length; entropy -= probability * Math.log2(probability); } return entropy; } findSuspiciousConcatenation(lines) { const suspiciousPatterns = [ /['"]\s*\+\s*['"][^'"]+['"]\s*\+\s*['"][^'"]+/g, // Multiple string concatenations /String\.fromCharCode\s*\(/g, // Character code conversion /atob\s*\(/g, // Base64 decoding /eval\s*\(\s*[^)]*\+/g // Eval with concatenation ]; const results = []; lines.forEach((line, index) => { for (const pattern of suspiciousPatterns) { if (pattern.test(line)) { results.push({ line: index + 1, content: line.trim() }); break; // Only add one finding per line } } }); return results; } getSuggestion(pattern) { const suggestions = { obfuscation: 'Remove code obfuscation or provide clear documentation if obfuscation is necessary for legitimate purposes.', eval: 'Avoid using eval() or similar dynamic code execution functions. Use safer alternatives like JSON.parse() for data parsing.', base64: 'Review base64 encoded content. Ensure it contains legitimate data and is not being used to hide malicious code.', 'suspicious-import': 'Review imported modules for legitimacy. Ensure they come from trusted sources and serve necessary purposes.', network: 'Review network operations for necessity and security. Ensure proper input validation and error handling.', filesystem: 'Review file system operations for security implications. Ensure proper path validation and access controls.' }; return (suggestions[pattern.category] || 'Review this code pattern for potential security implications and ensure it serves a legitimate purpose.'); } loadPatterns() { return [ // Obfuscation patterns { id: 'obf-001', name: 'JavaScript Obfuscation', pattern: /\\x[0-9a-fA-F]{2}/g, severity: Severity.MEDIUM, description: 'Detected hexadecimal character encoding which may indicate obfuscated code', category: 'obfuscation' }, { id: 'obf-002', name: 'Unicode Escape Sequences', pattern: /\\u[0-9a-fA-F]{4}/g, severity: Severity.MEDIUM, description: 'Detected Unicode escape sequences which may be used for obfuscation', category: 'obfuscation' }, { id: 'obf-003', name: 'Eval with Dynamic Code', pattern: /eval\s*\(\s*[^)]*[\+\[\]]/g, severity: Severity.HIGH, description: 'Detected eval() with dynamic code construction which is highly suspicious', category: 'eval' }, // Base64 patterns { id: 'b64-001', name: 'Base64 Encoded Content', pattern: /[A-Za-z0-9+/]{40,}={0,2}/g, severity: Severity.MEDIUM, description: 'Detected long base64 encoded string which may contain executable code', category: 'base64' }, { id: 'b64-002', name: 'Base64 Decode Function', pattern: /atob\s*\(/g, severity: Severity.MEDIUM, description: 'Detected base64 decoding function which may be used to decode malicious content', category: 'base64' }, // Suspicious function calls { id: 'func-001', name: 'Dynamic Function Creation', pattern: /new\s+Function\s*\(/g, severity: Severity.HIGH, description: 'Detected dynamic function creation which can be used for code injection', category: 'eval' }, { id: 'func-002', name: 'String fromCharCode', pattern: /String\.fromCharCode\s*\(/g, severity: Severity.MEDIUM, description: 'Detected String.fromCharCode which may be used to construct strings dynamically', category: 'obfuscation' }, // Network operations { id: 'net-001', name: 'XMLHttpRequest Creation', pattern: /new\s+XMLHttpRequest\s*\(/g, severity: Severity.LOW, description: 'Detected XMLHttpRequest which may be used for data exfiltration', category: 'network' }, { id: 'net-002', name: 'Fetch API Usage', pattern: /fetch\s*\(\s*['"`][^'"`]*['"`]/g, severity: Severity.LOW, description: 'Detected fetch API usage with external URLs', category: 'network' }, // File system operations (Node.js) { id: 'fs-001', name: 'File System Write', pattern: /fs\.writeFile|fs\.writeFileSync/g, severity: Severity.MEDIUM, description: 'Detected file system write operations', category: 'filesystem' }, { id: 'fs-002', name: 'Child Process Execution', pattern: /child_process\.exec|spawn|fork/g, severity: Severity.HIGH, description: 'Detected child process execution which can be used for malicious commands', category: 'eval' } ]; } addCustomPattern(pattern) { this.patterns.push(pattern); } getPatternCount() { return this.patterns.length; } setSensitivity(level) { this.sensitivity = level; } } //# sourceMappingURL=malware-analyzer.js.map