using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using MemScanEDR.Models; namespace MemScanEDR.Services; /// /// AI 分析器 v2.0 —— 重构版 /// 核心变化: /// - 默认使用本地 AI 管线(DetectionPipeline:基线+分类+规则三级联动) /// - LLM 大模型仅用于辅助研判(可选,需手动开启) /// - 启发式规则引擎作为降级方案保留 /// public class AIAnalyzer { private readonly HttpClient _http = new(); private AIConfig _config; private readonly DetectionPipeline _pipeline; private readonly List _signatures; public AIAnalyzer(AIConfig config, List signatures, ScanMode scanMode = ScanMode.Balanced) { _config = config; _signatures = signatures; _http.Timeout = TimeSpan.FromSeconds(config.Timeout); _pipeline = new DetectionPipeline(scanMode); } public void UpdateConfig(AIConfig config) { _config = config; _http.Timeout = TimeSpan.FromSeconds(config.Timeout); } /// /// 分析进程 —— v2.0 核心入口 /// public async Task AnalyzeProcessAsync(ProcessInfo proc, List? matches = null, List? suspiciousRegions = null) { // v2.0: 优先使用本地 AI 管线 if (_config.EnableLocalAI) { try { var result = await _pipeline.AnalyzeAsync(proc, _signatures); // 如果启用了 LLM 辅助研判且本地 AI 判定为可疑/高危 if (_config.EnableLLMAssist && result.RiskLevel != RiskLevel.Safe && _config.Provider != AIProvider.Heuristic) { var llmResult = await LlmAssistedAnalysis(proc, result); if (llmResult != null) { // LLM 结果作为补充参考,不覆盖本地 AI 的核心判定 result.Reasoning += $" | LLM研判: {llmResult.Reasoning}"; result.AIProvider += " + LLM"; } } return result; } catch (Exception ex) { // 管线异常时回退 return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new(), $"管线异常回退: {ex.Message}"); } } // 未启用本地AI时的传统模式 if (_config.Provider == AIProvider.Heuristic) { return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new()); } // 传统 LLM 模式(兼容 v1.0) return await LegacyApiAnalyze(proc, matches ?? new(), suspiciousRegions ?? new()); } /// /// LLM 辅助研判 —— 仅对本地 AI 已标记为可疑的进程做二次确认 /// private async Task LlmAssistedAnalysis(ProcessInfo proc, ScanResult pipelineResult) { try { var prompt = BuildLlmAssistPrompt(proc, pipelineResult); var response = await CallAIAsync(prompt); return ParseLlmResponse(proc, response); } catch { return null; } } private static string BuildLlmAssistPrompt(ProcessInfo proc, ScanResult pipelineResult) { var sb = new StringBuilder(); sb.AppendLine("=== 本地AI已标记为可疑的进程 ==="); sb.AppendLine($"PID: {proc.Pid}, 进程名: {proc.Name}, 路径: {proc.ExePath}"); sb.AppendLine($"风险等级: {pipelineResult.RiskLevel}, 评分: {pipelineResult.RiskScore:F2}"); sb.AppendLine($"指标: {string.Join(", ", pipelineResult.Indicators)}"); sb.AppendLine($"检测层: {pipelineResult.DetectionLayer}"); sb.AppendLine($"本地AI推理: {pipelineResult.Reasoning}"); sb.AppendLine("\n=== 请结合以上信息做最终研判 ==="); sb.AppendLine("请判断此进程是否为真正的威胁,并给出简短理由。"); sb.AppendLine("注意:.NET/Java/Node.js/Chrome等JIT运行时可能有合法RWX内存,安全软件可能有注入行为。"); return sb.ToString(); } private ScanResult ParseLlmResponse(ProcessInfo proc, string content) { return new ScanResult { Pid = proc.Pid, ProcessName = proc.Name, RiskLevel = RiskLevel.Safe, RiskScore = 0, Reasoning = content.Trim().Length > 200 ? content.Trim()[..200] : content.Trim(), AIProvider = $"llm-assist/{_config.Model}" }; } /// /// 传统 LLM API 分析(兼容 v1.0 模式) /// private async Task LegacyApiAnalyze(ProcessInfo proc, List matches, List suspiciousRegions) { var preScore = QuickPreScore(proc, matches, suspiciousRegions); if (preScore < 0.05) { return new ScanResult { Pid = proc.Pid, ProcessName = proc.Name, RiskLevel = RiskLevel.Safe, RiskScore = 0.0, Indicators = new List(), Reasoning = $"PID {proc.Pid} ({proc.Name}) | v2.0 预过滤跳过", AIProvider = "heuristic/pre-filter-v2", DetectionLayer = "PreFilter", ScanTime = DateTime.Now }; } try { var prompt = BuildLegacyPrompt(proc, matches, suspiciousRegions); var response = await CallAIAsync(prompt); return ParseLegacyResponse(proc, response); } catch (Exception ex) { return HeuristicAnalyze(proc, matches, suspiciousRegions, $"API异常回退: {ex.Message}"); } } private static double QuickPreScore(ProcessInfo proc, List matches, List suspiciousRegions) { double s = 0; if (!proc.IsSigned && proc.IsTempPath) s += 0.30; if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath) s += 0.25; var suspPorts = new HashSet { 4444, 5555, 6666, 7777, 1337, 31337, 9999, 12345, 27015 }; if (proc.Connections.Any(c => suspPorts.Contains(c.RemotePort))) s += 0.20; if (matches.Count > 0) s += 0.20; if (suspiciousRegions.Any(r => r.Protect.Contains("EXECUTE_READWRITE"))) s += 0.15; return Math.Min(s, 1.0); } // ─── 启发式规则(保留作为降级方案)─── public ScanResult HeuristicAnalyze(ProcessInfo proc, List matches, List suspiciousRegions, string? errorNote = null) { double score = 0; var indicators = new List(); var reasons = new List(); // Rule 1: unsigned + temp path if (!proc.IsSigned && proc.IsTempPath) { score += 0.30; indicators.Add("unsigned_temp_path"); reasons.Add("unsigned executable in temp directory"); } // Rule 2: process masquerading if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath) { score += 0.25; indicators.Add("process_masquerading"); reasons.Add("system process name in non-system path"); } // Rule 3: suspicious ports var suspPorts = new HashSet { 4444, 5555, 6666, 7777, 1337, 31337 }; foreach (var conn in proc.Connections) { if (suspPorts.Contains(conn.RemotePort)) { score += 0.20; indicators.Add("suspicious_port"); reasons.Add($"suspicious remote port {conn.RemotePort}"); break; } } // Rule 4: signature matches if (matches.Count > 0) { var high = matches.Count(m => m.Severity == "high"); var med = matches.Count(m => m.Severity == "medium"); score += Math.Min(high * 0.20, 0.4); score += Math.Min(med * 0.08, 0.2); if (high > 0) { indicators.Add("high_severity_match"); reasons.Add($"{high} high-severity signatures"); } if (matches.Any(m => m.Category == "shellcode")) { score += 0.15; indicators.Add("shellcode_detected"); } if (matches.Any(m => m.Category == "c2")) { score += 0.20; indicators.Add("c2_pattern"); } } // Rule 5: suspicious memory regions (v2.0 improved: distinguish JIT) var rwxCount = suspiciousRegions.Count(r => r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE"); // v2.0 improvement: don't flag JIT runtimes var isRuntime = proc.Name.ToLowerInvariant() switch { var n when n.Contains("dotnet") || n.Contains("java") || n.Contains("node") || n.Contains("python") || n.Contains("chrome") || n.Contains("msedge") || n.Contains("firefox") => true, _ => false }; if (rwxCount > 0 && !isRuntime) { score += Math.Min(rwxCount * 0.12, 0.25); indicators.Add("rwx_private_memory"); } score = Math.Min(score, 1.0); var level = score >= 0.75 ? RiskLevel.High : score >= 0.50 ? RiskLevel.Suspicious : RiskLevel.Safe; var reasoning = $"PID {proc.Pid} ({proc.Name}) | {string.Join("; ", reasons)} | score={score:F2}"; if (errorNote != null) reasoning += $" | {errorNote}"; return new ScanResult { Pid = proc.Pid, ProcessName = proc.Name, RiskLevel = level, RiskScore = score, Indicators = indicators, Reasoning = reasoning, AIProvider = "heuristic/rule-engine-v2", DetectionLayer = "Heuristic", ScanTime = DateTime.Now }; } // ─── LLM API 调用 ─── private async Task CallAIAsync(string userMessage) { if (_config.Provider == AIProvider.Claude) return await CallClaudeAsync(userMessage); var url = _config.Provider == AIProvider.Local ? $"{_config.BaseUrl}/api/chat" : $"{_config.BaseUrl.TrimEnd('/')}/chat/completions"; object requestBody = _config.Provider == AIProvider.Local ? new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, stream = false } : new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, max_tokens = _config.MaxTokens, temperature = _config.Temperature }; var json = JsonSerializer.Serialize(requestBody); var request = new HttpRequestMessage(HttpMethod.Post, url); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); if (!string.IsNullOrEmpty(_config.ApiKey)) request.Headers.Add("Authorization", $"Bearer {_config.ApiKey}"); var response = await _http.SendAsync(request); response.EnsureSuccessStatusCode(); var responseJson = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseJson); if (_config.Provider == AIProvider.Local) return doc.RootElement.GetProperty("message").GetProperty("content").GetString() ?? ""; return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString() ?? ""; } private async Task CallClaudeAsync(string userMessage) { var url = $"{_config.BaseUrl.TrimEnd('/')}/v1/messages"; var requestBody = new { model = _config.Model, max_tokens = _config.MaxTokens, system = V2SystemPrompt, messages = new[] { new { role = "user", content = userMessage } } }; var json = JsonSerializer.Serialize(requestBody); var request = new HttpRequestMessage(HttpMethod.Post, url); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); request.Headers.Add("x-api-key", _config.ApiKey); request.Headers.Add("anthropic-version", "2023-06-01"); var response = await _http.SendAsync(request); response.EnsureSuccessStatusCode(); var responseJson = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseJson); return doc.RootElement.GetProperty("content")[0].GetProperty("text").GetString() ?? ""; } private string BuildLegacyPrompt(ProcessInfo proc, List matches, List suspiciousRegions) { var sb = new StringBuilder(); sb.AppendLine("=== PROCESS DATA ==="); sb.AppendLine($"PID: {proc.Pid}"); sb.AppendLine($"Name: {proc.Name}"); sb.AppendLine($"Path: {(string.IsNullOrEmpty(proc.ExePath) ? "unknown" : proc.ExePath)}"); sb.AppendLine($"Parent: {proc.ParentName} (PPID: {proc.Ppid})"); sb.AppendLine($"CommandLine: {proc.CmdLine}"); sb.AppendLine($"Signed: {proc.IsSigned}"); sb.AppendLine($"Signer: {(string.IsNullOrEmpty(proc.Signer) ? "-" : proc.Signer)}"); sb.AppendLine($"SystemPath: {proc.IsSystemPath}"); sb.AppendLine($"TempPath: {proc.IsTempPath}"); if (proc.Connections.Count > 0) { sb.AppendLine($"\nNetworkConnections ({proc.Connections.Count}):"); foreach (var c in proc.Connections.Take(8)) sb.AppendLine($" {c.Protocol} {c.LocalAddr} -> {c.RemoteAddr} [{c.State}]"); } if (matches.Count > 0) { sb.AppendLine($"\nMemorySignatures ({matches.Count}):"); foreach (var m in matches.Take(10)) sb.AppendLine($" [{m.Severity}] {m.SigId} @ {m.Address}: {m.Description}"); } if (suspiciousRegions.Count > 0) { sb.AppendLine($"\nSuspiciousMemoryRegions ({suspiciousRegions.Count}):"); foreach (var r in suspiciousRegions.Take(5)) sb.AppendLine($" 0x{r.BaseAddress:X} size={r.Size} protect={r.Protect} type={r.Type}"); } sb.AppendLine("\n=== ANALYZE THIS PROCESS ==="); return sb.ToString(); } private static ScanResult ParseLegacyResponse(ProcessInfo proc, string content) { content = content.Trim(); if (content.StartsWith("```")) content = string.Join('\n', content.Split('\n')[1..^1]); try { using var doc = JsonDocument.Parse(content); var root = doc.RootElement; var riskLevel = root.GetProperty("risk_level").GetString() switch { "high" => RiskLevel.High, "suspicious" => RiskLevel.Suspicious, _ => RiskLevel.Safe }; var indicators = new List(); if (root.TryGetProperty("indicators", out var indArr)) foreach (var ind in indArr.EnumerateArray()) indicators.Add(ind.GetString() ?? ""); return new ScanResult { Pid = proc.Pid, ProcessName = proc.Name, RiskLevel = riskLevel, RiskScore = root.GetProperty("risk_score").GetDouble(), Indicators = indicators, Reasoning = root.GetProperty("reasoning").GetString() ?? "", AIProvider = "llm-api-v2", DetectionLayer = "LLM", ScanTime = DateTime.Now }; } catch { return new ScanResult { Pid = proc.Pid, ProcessName = proc.Name, RiskLevel = RiskLevel.Safe, RiskScore = 0, Reasoning = content, AIProvider = "llm-api-v2", DetectionLayer = "LLM", ScanTime = DateTime.Now }; } } public async Task<(bool ok, string message)> TestConnectionAsync() { if (_config.Provider == AIProvider.Heuristic) { return _config.EnableLocalAI ? (true, "AI 管线模式 (Pipeline v2.0, 无需 API)") : (true, "启发式规则模式 (Heuristic v2.0, 无需 API)"); } try { await CallAIAsync("ping"); return (true, $"OK - {_config.Provider}/{_config.Model}"); } catch (Exception ex) { return (false, ex.Message); } } private const string V2SystemPrompt = @"You are a Windows memory security expert (v2.0) analyzing process threat levels. === CORE PRINCIPLE === The VAST majority (>95%) of processes are SAFE. Default to ""safe"" unless you see CLEAR threat evidence. === CRITICAL FALSE POSITIVE PREVENTION === 1. .NET/Java/Node.js/Chrome/V8 processes LEGITIMATELY use RWX memory for JIT compilation. This is NOT suspicious. 2. Security/EDR products legitimately inject/hook processes. Their RWX + injection API usage is NORMAL. 3. Development/debugging tools legitimately use Process inspection APIs. 4. A single indicator ALONE is never enough for ""high"" risk. === RISK LEVELS === ""safe"": Normal benign process, even with minor deviations (< 0.30) ""suspicious"": Multiple weak signals or single strong signal (0.30-0.70) ""high"": Clear evidence with multiple concurring signals (> 0.70) === JSON RESPONSE === {""risk_level"":""safe"",""risk_score"":0.0,""indicators"":[],""reasoning"":""""}"; }