/** * @fileoverview Malware Score Calculator * @module rules/malware/scoring * * Dynamic scoring system for malware detection that calculates risk scores * based on multiple factors including pattern matches, obfuscation level, * network indicators, and rule correlation. */ import { MalwareRule, MalwareScore, MalwareScoreBreakdown, MalwareSeverity, PatternMatch, AnalysisContext, MalwareFinding, IScoreCalculator, ConfidenceLevel } from '../types'; import { SCORE_THRESHOLDS, RISK_LEVELS, DEFAULT_SCORING_WEIGHTS, LIMITS } from '../constants'; import { calculateEntropy, detectObfuscationLevel, detectEnvironmentChecks } from '../utils'; // ============================================================================ // SCORE CALCULATOR IMPLEMENTATION // ============================================================================ /** * Malware Score Calculator * Implements dynamic scoring based on multiple risk factors */ export class MalwareScoreCalculator implements IScoreCalculator { private weights: typeof DEFAULT_SCORING_WEIGHTS; constructor(customWeights?: Partial) { this.weights = { ...DEFAULT_SCORING_WEIGHTS, ...customWeights }; } /** * Calculate malware score for a finding */ calculateScore( rule: MalwareRule, matches: PatternMatch[], context: AnalysisContext ): MalwareScore { const breakdown = this.calculateBreakdown(rule, matches, context); const totalScore = this.calculateTotalScore(breakdown); const severity = this.scoreToSeverity(totalScore); const riskLevel = this.scoreToRiskLevel(totalScore); return { score: Math.round(totalScore), breakdown, calculatedSeverity: severity, riskLevel, explanation: this.generateExplanation(breakdown, matches.length) }; } /** * Calculate score breakdown */ private calculateBreakdown( rule: MalwareRule, matches: PatternMatch[], context: AnalysisContext ): MalwareScoreBreakdown { // Base score from rule definition const baseScore = rule.baseScore ?? this.getDefaultBaseScore(rule.severity); // Pattern match score - more matches = higher confidence const patternScore = this.calculatePatternScore(matches, rule); // Obfuscation score const obfuscationScore = this.calculateObfuscationScore(context); // Network indicator score const networkScore = this.calculateNetworkScore(context.content, matches); // Execution indicator score const executionScore = this.calculateExecutionScore(context.content, matches); // Persistence indicator score const persistenceScore = this.calculatePersistenceScore(context.content, matches); // Correlation score (boost from related findings) const correlationScore = this.calculateCorrelationScore( rule, context.previousFindings || [], context.relatedFindings || [] ); // False positive penalty const falsePositivePenalty = this.calculateFalsePositivePenalty( rule, context ); return { baseScore, patternScore, obfuscationScore, networkScore, executionScore, persistenceScore, correlationScore, falsePositivePenalty, totalScore: 0 // Will be calculated }; } /** * Calculate total score from breakdown */ private calculateTotalScore(breakdown: MalwareScoreBreakdown): number { const weighted = ( breakdown.baseScore * 0.3 + breakdown.patternScore * this.weights.patternCount + breakdown.obfuscationScore * this.weights.obfuscation + breakdown.networkScore * this.weights.networkAccess + breakdown.executionScore * this.weights.commandExecution + breakdown.persistenceScore * this.weights.persistence + breakdown.correlationScore * 0.1 ); const penalized = weighted - breakdown.falsePositivePenalty; const total = Math.max(0, Math.min(100, penalized)); // Update breakdown with total breakdown.totalScore = total; return total; } /** * Get default base score from severity */ private getDefaultBaseScore(severity: MalwareSeverity): number { switch (severity) { case MalwareSeverity.CRITICAL: return 80; case MalwareSeverity.HIGH: return 60; case MalwareSeverity.MEDIUM: return 40; case MalwareSeverity.LOW: return 20; case MalwareSeverity.INFO: return 10; default: return 30; } } /** * Calculate score from pattern matches */ private calculatePatternScore( matches: PatternMatch[], rule: MalwareRule ): number { if (matches.length === 0) return 0; // Base score for having any match let score = 30; // Bonus for multiple matches (diminishing returns) const matchBonus = Math.min(matches.length * 5, 30); score += matchBonus; // Bonus for matches from different patterns const uniquePatterns = new Set( matches.map(m => m.pattern.patternId || JSON.stringify(m.pattern)) ); const diversityBonus = Math.min(uniquePatterns.size * 10, 20); score += diversityBonus; // Weight by pattern weights if defined const weightedSum = matches.reduce((sum, match) => { return sum + (match.pattern.weight ?? 1); }, 0); const avgWeight = weightedSum / matches.length; score *= avgWeight; return Math.min(score, 100); } /** * Calculate obfuscation score */ private calculateObfuscationScore(context: AnalysisContext): number { const obfLevel = detectObfuscationLevel(context.content, context.language); const entropy = calculateEntropy(context.content); // Combine obfuscation level and entropy let score = obfLevel * 60; // High entropy bonus if (entropy > 6.5) { score += 25; } else if (entropy > 5.5) { score += 15; } // Environment check detection const envChecks = detectEnvironmentChecks(context.content); if (envChecks.detected) { score += envChecks.checks.length * 5; } return Math.min(score, 100); } /** * Calculate network indicator score */ private calculateNetworkScore(content: string, matches: PatternMatch[]): number { let score = 0; // Check for network-related patterns in matches const networkPatterns = [ /fetch|axios|request|http|socket/i, /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, /https?:\/\//i, /websocket|ws:\/\//i ]; for (const pattern of networkPatterns) { if (pattern.test(content)) { score += 15; } } // Check for suspicious destinations const suspiciousPatterns = [ /pastebin|hastebin|ghostbin/i, /ngrok|serveo|localtunnel/i, /\.onion|\.bit|\.i2p/i ]; for (const pattern of suspiciousPatterns) { if (pattern.test(content)) { score += 25; } } return Math.min(score, 100); } /** * Calculate execution indicator score */ private calculateExecutionScore(content: string, matches: PatternMatch[]): number { let score = 0; // Direct execution patterns const execPatterns = [ { pattern: /eval\s*\(/i, weight: 30 }, { pattern: /Function\s*\(/i, weight: 25 }, { pattern: /exec\s*\(/i, weight: 30 }, { pattern: /system\s*\(/i, weight: 35 }, { pattern: /shell_exec/i, weight: 35 }, { pattern: /subprocess\./i, weight: 25 }, { pattern: /child_process/i, weight: 30 }, { pattern: /Process\.Start/i, weight: 30 }, { pattern: /Runtime\.exec/i, weight: 30 } ]; for (const { pattern, weight } of execPatterns) { if (pattern.test(content)) { score += weight; } } return Math.min(score, 100); } /** * Calculate persistence indicator score */ private calculatePersistenceScore(content: string, matches: PatternMatch[]): number { let score = 0; // Persistence patterns const persistencePatterns = [ { pattern: /crontab|systemd|launchd/i, weight: 25 }, { pattern: /registry|HKEY_/i, weight: 25 }, { pattern: /startup|autorun/i, weight: 20 }, { pattern: /\.bashrc|\.profile|\.zshrc/i, weight: 20 }, { pattern: /scheduled.*task|schtasks/i, weight: 25 }, { pattern: /service.*install|daemon/i, weight: 20 } ]; for (const { pattern, weight } of persistencePatterns) { if (pattern.test(content)) { score += weight; } } return Math.min(score, 100); } /** * Calculate correlation score from related findings */ private calculateCorrelationScore( rule: MalwareRule, previousFindings: MalwareFinding[], relatedFindings: MalwareFinding[] ): number { let score = 0; if (!rule.correlation) { return score; } const allFindings = [...previousFindings, ...relatedFindings]; const findingRuleIds = new Set(allFindings.map(f => f.ruleId)); // Check for amplifying rules if (rule.correlation.amplifyWith) { for (const amplifyId of rule.correlation.amplifyWith) { if (findingRuleIds.has(amplifyId)) { score += rule.correlation.severityBoost ?? 15; } } } return Math.min(score, 50); } /** * Calculate false positive penalty */ private calculateFalsePositivePenalty( rule: MalwareRule, context: AnalysisContext ): number { let penalty = 0; // Vendor code penalty if (context.isVendorCode) { penalty += 20; } // Test file penalty if (context.isTestFile) { penalty += 30; } // Check for false positive patterns if (rule.falsePositivePatterns) { for (const fpPattern of rule.falsePositivePatterns) { if (fpPattern.type === 'regex') { const regex = new RegExp(fpPattern.pattern, fpPattern.flags || 'gi'); if (regex.test(context.content)) { penalty += 15; } } } } return Math.min(penalty, 50); } /** * Convert score to severity */ private scoreToSeverity(score: number): MalwareSeverity { if (score >= SCORE_THRESHOLDS.CRITICAL) return MalwareSeverity.CRITICAL; if (score >= SCORE_THRESHOLDS.HIGH) return MalwareSeverity.HIGH; if (score >= SCORE_THRESHOLDS.MEDIUM) return MalwareSeverity.MEDIUM; if (score >= SCORE_THRESHOLDS.LOW) return MalwareSeverity.LOW; return MalwareSeverity.INFO; } /** * Convert score to risk level */ private scoreToRiskLevel(score: number): 'critical' | 'high' | 'medium' | 'low' | 'minimal' { if (score >= RISK_LEVELS.CRITICAL.min) return RISK_LEVELS.CRITICAL.label; if (score >= RISK_LEVELS.HIGH.min) return RISK_LEVELS.HIGH.label; if (score >= RISK_LEVELS.MEDIUM.min) return RISK_LEVELS.MEDIUM.label; if (score >= RISK_LEVELS.LOW.min) return RISK_LEVELS.LOW.label; return RISK_LEVELS.MINIMAL.label; } /** * Generate human-readable explanation */ private generateExplanation( breakdown: MalwareScoreBreakdown, matchCount: number ): string { const factors: string[] = []; if (breakdown.patternScore > 50) { factors.push(`Strong pattern match (${matchCount} matches)`); } else if (breakdown.patternScore > 20) { factors.push(`Pattern detected (${matchCount} matches)`); } if (breakdown.obfuscationScore > 50) { factors.push('High code obfuscation detected'); } else if (breakdown.obfuscationScore > 25) { factors.push('Moderate obfuscation present'); } if (breakdown.networkScore > 50) { factors.push('Suspicious network activity indicators'); } if (breakdown.executionScore > 50) { factors.push('Dangerous code execution patterns'); } if (breakdown.persistenceScore > 25) { factors.push('Persistence mechanism indicators'); } if (breakdown.correlationScore > 0) { factors.push('Correlated with other malware indicators'); } if (breakdown.falsePositivePenalty > 0) { factors.push('Possible false positive indicators present'); } if (factors.length === 0) { return 'Low-confidence detection based on basic pattern matching'; } return factors.join('. ') + '.'; } /** * Calculate combined score for multiple findings */ calculateCombinedScore(findings: MalwareFinding[]): MalwareScore { if (findings.length === 0) { return { score: 0, breakdown: { baseScore: 0, patternScore: 0, obfuscationScore: 0, networkScore: 0, executionScore: 0, persistenceScore: 0, correlationScore: 0, falsePositivePenalty: 0, totalScore: 0 }, calculatedSeverity: MalwareSeverity.INFO, riskLevel: 'minimal', explanation: 'No findings detected' }; } // Aggregate breakdowns const aggregated: MalwareScoreBreakdown = { baseScore: 0, patternScore: 0, obfuscationScore: 0, networkScore: 0, executionScore: 0, persistenceScore: 0, correlationScore: 0, falsePositivePenalty: 0, totalScore: 0 }; for (const finding of findings) { const b = finding.malwareScore.breakdown; aggregated.baseScore = Math.max(aggregated.baseScore, b.baseScore); aggregated.patternScore += b.patternScore * 0.5; aggregated.obfuscationScore = Math.max(aggregated.obfuscationScore, b.obfuscationScore); aggregated.networkScore = Math.max(aggregated.networkScore, b.networkScore); aggregated.executionScore = Math.max(aggregated.executionScore, b.executionScore); aggregated.persistenceScore = Math.max(aggregated.persistenceScore, b.persistenceScore); aggregated.correlationScore += 5; // Bonus for multiple findings } // Cap values aggregated.patternScore = Math.min(aggregated.patternScore, 100); aggregated.correlationScore = Math.min(aggregated.correlationScore, 50); const totalScore = this.calculateTotalScore(aggregated); const severity = this.scoreToSeverity(totalScore); const riskLevel = this.scoreToRiskLevel(totalScore); return { score: Math.round(totalScore), breakdown: aggregated, calculatedSeverity: severity, riskLevel, explanation: `Combined analysis of ${findings.length} findings. ${this.generateExplanation(aggregated, findings.length)}` }; } } // ============================================================================ // CONFIDENCE CALCULATOR // ============================================================================ /** * Calculate confidence level based on evidence */ export function calculateConfidence( matchCount: number, patternDiversity: number, hasContextualEvidence: boolean, hasCorrelation: boolean ): ConfidenceLevel { let score = 0; // Match count contribution if (matchCount >= 5) score += 30; else if (matchCount >= 3) score += 20; else if (matchCount >= 1) score += 10; // Pattern diversity contribution if (patternDiversity >= 3) score += 25; else if (patternDiversity >= 2) score += 15; else score += 5; // Contextual evidence contribution if (hasContextualEvidence) score += 25; // Correlation contribution if (hasCorrelation) score += 20; // Map to confidence level if (score >= 80) return ConfidenceLevel.CONFIRMED; if (score >= 60) return ConfidenceLevel.HIGH; if (score >= 40) return ConfidenceLevel.MEDIUM; if (score >= 20) return ConfidenceLevel.LOW; return ConfidenceLevel.TENTATIVE; } // ============================================================================ // EXPORTS // ============================================================================ export const scoreCalculator = new MalwareScoreCalculator();