using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using Microsoft.Win32; using MemScanEDR.Models; using MemScanEDR.Services; namespace MemScanEDR; // ─── Value Converter: List → comma-separated string ─── public class ListToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is List list && list.Count > 0 ? string.Join(", ", list) : "—"; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException(); } // ─── Simple language helper ────────────────────────────────── public static class Lang { private static bool _isChinese = true; public static bool IsChinese => _isChinese; public static void SetChinese(bool zh) => _isChinese = zh; public static string T(string zh, string en) => _isChinese ? zh : en; } // ═══════════════════════════════════════════════════════════════ // MAIN WINDOW // ═══════════════════════════════════════════════════════════════ public partial class MainWindow : Window { // ── Services ─────────────────────────────────────────────── private readonly ProcessCollector _processCollector = new(); private readonly MemoryScanner _memoryScanner = new(); private readonly CeMcpManager _ceMcp = new(); private AIAnalyzer? _aiAnalyzer; // ── Application state ────────────────────────────────────── private AppSettings _settings = null!; private readonly ObservableCollection _results = new(); private readonly ObservableCollection _scanLog = new(); private List _signatures = new(); private CancellationTokenSource? _cts; private bool _isScanning; // ═══════════════════════════════════════════════════════════ // CONSTRUCTOR // ═══════════════════════════════════════════════════════════ public MainWindow() { try { InitializeComponent(); // Bind observable collections to DataGrids / ListBox DgAlerts.ItemsSource = _results; DgProcesses.ItemsSource = _results; LstScanLog.ItemsSource = _scanLog; // Load settings & initialize AI analyzer _settings = AppSettings.Load(); _aiAnalyzer = new AIAnalyzer(_settings.AI); // Load signature database _signatures = _memoryScanner.LoadSignatures(_settings.RulesDir); // Apply saved settings to the Settings view LoadSettingsToUI(); ApplyLanguage(); AddLog(Lang.T("[init] MemScan-EDR 控制台已加载", "[init] MemScan-EDR console loaded")); // Start CE-MCP server at launch (background) Task.Run(() => { if (_settings.CeMcpPath != "disabled") { Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] 正在启动 CE-MCP 服务器...", "[mcp] Starting CE-MCP server..."))); var ok = _ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath); if (ok) { Thread.Sleep(1500); Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器已启动", "[mcp] CE-MCP server started"))); } else { Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器未找到,将跳过", "[mcp] CE-MCP server not found — skipped"))); } } }); } catch (Exception ex) { System.IO.File.AppendAllText( System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "crash.log"), $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] MainWindow init error\n{ex}\n\n"); System.Windows.MessageBox.Show($"Init error (see crash.log):\n\n{ex.Message}", "MemScan-EDR", MessageBoxButton.OK, MessageBoxImage.Error); } } // ═══════════════════════════════════════════════════════════ // LANGUAGE TOGGLE // ═══════════════════════════════════════════════════════════ private void BtnLangToggle_Click(object sender, RoutedEventArgs e) { Lang.SetChinese(!Lang.IsChinese); ApplyLanguage(); } private void ApplyLanguage() { // Title bar BtnLang.Content = Lang.T("EN", "中"); // Sidebar TbMonHeader.Text = Lang.T("监控", "MONITORING"); TbSysHeader.Text = Lang.T("系统", "SYSTEM"); TbNavDashboard.Text = Lang.T("仪表盘", "Dashboard"); TbNavProcess.Text = Lang.T("进程列表", "Processes"); TbNavScan.Text = Lang.T("扫描进度", "Scan Progress"); TbNavSettings.Text = Lang.T("设置", "Settings"); TbQuickScan.Text = Lang.T("快速扫描", "Quick Scan"); TbQuickScanDesc.Text = Lang.T("立即扫描所有进程", "Scan all processes now"); BtnStartScan.Content = Lang.T("开始扫描", "Start Scan"); // Dashboard TbDashboardTitle.Text = Lang.T("安全仪表盘", "Security Dashboard"); TbStatTotal.Text = Lang.T("已扫描", "TOTAL SCANNED"); TbStatTotalDesc.Text = Lang.T("进程", "Processes"); TbStatHigh.Text = Lang.T("高风险", "HIGH RISK"); TbStatHighDesc.Text = Lang.T("严重告警", "Critical Alerts"); TbStatSusp.Text = Lang.T("可疑", "SUSPICIOUS"); TbStatSuspDesc.Text = Lang.T("警告", "Warnings"); TbStatSafe.Text = Lang.T("安全", "SAFE"); TbStatSafeDesc.Text = Lang.T("正常", "Clean"); TbRecentAlerts.Text = Lang.T("最近告警", "Recent Alerts"); // Scan view TbScanTitle.Text = Lang.T("扫描进度", "Scan Progress"); BtnStopScan.Content = Lang.T("停止扫描", "Stop Scan"); TbScanLog.Text = Lang.T("扫描日志", "Scan Log"); // Process view TbProcTitle.Text = Lang.T("扫描进程", "Scanned Processes"); // Settings TbSettingsTitle.Text = Lang.T("设置", "Settings"); TbAiConfig.Text = Lang.T("AI 配置", "AI Configuration"); TbAiProvider.Text = Lang.T("AI 提供商", "AI Provider"); TbBaseUrl.Text = Lang.T("API 地址", "API Base URL"); TbApiKey.Text = Lang.T("API 密钥", "API Key"); TbModel.Text = Lang.T("模型名称", "Model Name"); TbPathConfig.Text = Lang.T("路径配置", "Path Configuration"); TbCeMcpPath.Text = Lang.T("CE-MCP 服务器路径", "CE-MCP Server Path"); TbRulesDir.Text = Lang.T("扫描规则目录", "Scan Rules Directory"); TbLogDir.Text = Lang.T("日志目录", "Log Directory"); TbScanProfile.Text = Lang.T("扫描配置", "Scan Profile"); BtnSaveSettings.Content = Lang.T("保存设置", "Save Settings"); // ComboBox items CmbAIProvider.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("启发式规则 (无需 API)", "Heuristic (rule-based, no API)"), Tag = "Heuristic" }; CmbAIProvider.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("OpenAI 兼容 (OpenAI / DeepSeek / 通义等)", "OpenAI-Compatible (OpenAI / DeepSeek / etc.)"), Tag = "OpenAICompatible" }; CmbAIProvider.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("Claude (Anthropic API)", "Claude (Anthropic API)"), Tag = "Claude" }; CmbAIProvider.Items[3] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("本地模型 (Ollama / LM Studio 等)", "Local (Ollama / LM Studio / etc.)"), Tag = "Local" }; CmbScanProfile.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("快速 - 简单扫描", "fast - Quick scan"), Tag = "fast" }; CmbScanProfile.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("平衡 - 默认扫描深度", "balanced - Default scan depth"), Tag = "balanced" }; CmbScanProfile.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("深度 - 完整内存 + AI 分析", "deep - Full memory + AI analysis"), Tag = "deep" }; // Restore selected index LoadSettingsToUI(); } // ═══════════════════════════════════════════════════════════ // SIDEBAR NAVIGATION // ═══════════════════════════════════════════════════════════ private void NavChanged(object sender, RoutedEventArgs e) { if (DashboardView == null || ProcessView == null || ScanView == null || SettingsView == null) return; DashboardView.Visibility = Visibility.Collapsed; ProcessView.Visibility = Visibility.Collapsed; ScanView.Visibility = Visibility.Collapsed; SettingsView.Visibility = Visibility.Collapsed; if (sender == NavDashboard) DashboardView.Visibility = Visibility.Visible; else if (sender == NavProcess) ProcessView.Visibility = Visibility.Visible; else if (sender == NavScan) ScanView.Visibility = Visibility.Visible; else if (sender == NavSettings) SettingsView.Visibility = Visibility.Visible; } // ═══════════════════════════════════════════════════════════ // WINDOW CHROME BUTTONS // ═══════════════════════════════════════════════════════════ private void TitleBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (e.ClickCount == 2) BtnMaximize(sender, e); else DragMove(); } private void BtnMinimize(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized; private void BtnMaximize(object sender, RoutedEventArgs e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; private void BtnClose(object sender, RoutedEventArgs e) { _cts?.Cancel(); _ceMcp.Stop(); Close(); } // ═══════════════════════════════════════════════════════════ // SCAN PROGRESS REPORTING MODEL // ═══════════════════════════════════════════════════════════ private class ScanProgress { public string ProcessName { get; init; } = ""; public string Message { get; init; } = ""; public double Progress { get; init; } public ScanResult? Result { get; init; } public bool IsComplete { get; init; } } // ═══════════════════════════════════════════════════════════ // START SCAN // ═══════════════════════════════════════════════════════════ private async void BtnStartScan_Click(object sender, RoutedEventArgs e) { if (_isScanning) return; _isScanning = true; _cts = new CancellationTokenSource(); var ct = _cts.Token; BtnStartScan.IsEnabled = false; BtnStopScan.IsEnabled = true; _scanLog.Clear(); _results.Clear(); UpdateStats(); ScanProgressBar.Value = 0; LblCurrentProcess.Text = Lang.T("初始化中...", "Initializing..."); NavScan.IsChecked = true; SetScanStatus(Lang.T("扫描中", "SCANNING"), true); AddLog(Lang.T($"[scan] 扫描开始 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan started — {DateTime.Now:yyyy-MM-dd HH:mm:ss}")); var progress = new Progress(OnScanProgress); try { await Task.Run(() => RunScanAsync(progress, ct), ct); } catch (OperationCanceledException) { AddLog(Lang.T("[scan] 扫描已取消", "[scan] Scan cancelled by user")); } catch (Exception ex) { AddLog($"[error] {ex.Message}"); } finally { _isScanning = false; BtnStartScan.IsEnabled = true; BtnStopScan.IsEnabled = false; SetScanStatus(Lang.T("空闲", "IDLE"), false); AddLog(Lang.T($"[scan] 扫描完成 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan completed — {DateTime.Now:yyyy-MM-dd HH:mm:ss}")); } } private void BtnStopScan_Click(object sender, RoutedEventArgs e) { _cts?.Cancel(); AddLog(Lang.T("[scan] 正在停止...", "[scan] Stop requested...")); } // ═══════════════════════════════════════════════════════════ // CORE SCAN LOGIC (background) // ═══════════════════════════════════════════════════════════ private async Task RunScanAsync(IProgress progress, CancellationToken ct) { // CE-MCP already started at launch — just check if it's running if (_settings.CeMcpPath != "disabled" && !_ceMcp.IsRunning) { progress.Report(new ScanProgress { Message = Lang.T("[mcp] CE-MCP 未运行,尝试启动...", "[mcp] CE-MCP not running, attempting start...") }); _ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath); await Task.Delay(1000, ct); } // Enumerate processes progress.Report(new ScanProgress { Message = Lang.T("[proc] 正在枚举进程...", "[proc] Enumerating processes...") }); var processes = _processCollector.CollectAll(); progress.Report(new ScanProgress { Message = Lang.T($"[proc] 发现 {processes.Count} 个进程", $"[proc] Found {processes.Count} processes") }); var total = processes.Count; var scanned = 0; foreach (var proc in processes) { ct.ThrowIfCancellationRequested(); scanned++; var pct = (double)scanned / total * 100.0; progress.Report(new ScanProgress { ProcessName = $"{proc.Name} (PID {proc.Pid})", Message = Lang.T($"[{scanned}/{total}] 扫描 {proc.Name} (PID {proc.Pid})...", $"[{scanned}/{total}] Scanning {proc.Name} (PID {proc.Pid})..."), Progress = pct }); try { var matches = _memoryScanner.ScanProcess(proc.Pid, _signatures); var suspiciousRegions = _memoryScanner.GetSuspiciousRegions(proc.Pid); var result = await _aiAnalyzer!.AnalyzeProcessAsync(proc, matches, suspiciousRegions); var riskTag = result.RiskLevel switch { RiskLevel.High => Lang.T("高危", "HIGH"), RiskLevel.Suspicious => Lang.T("可疑", "SUSP"), _ => Lang.T("安全", "SAFE") }; progress.Report(new ScanProgress { Message = $"[{scanned}/{total}] [{riskTag}] {proc.Name} score={result.RiskScore:F2} ({result.AIProvider})", Progress = pct, Result = result }); } catch (Exception ex) { progress.Report(new ScanProgress { Message = $"[warn] {proc.Name} — {ex.Message}", Progress = pct }); } await Task.Delay(20, ct); } progress.Report(new ScanProgress { Message = Lang.T($"[done] 扫描完成 — 共分析 {total} 个进程", $"[done] Scan complete — {total} processes analyzed"), Progress = 100, IsComplete = true }); } // ═══════════════════════════════════════════════════════════ // PROGRESS CALLBACK (UI thread) // ═══════════════════════════════════════════════════════════ private void OnScanProgress(ScanProgress sp) { if (sp.Progress > 0) ScanProgressBar.Value = sp.Progress; if (!string.IsNullOrEmpty(sp.ProcessName)) LblCurrentProcess.Text = sp.ProcessName; if (!string.IsNullOrEmpty(sp.Message)) AddLog(sp.Message); if (sp.Result != null) { _results.Add(sp.Result); UpdateStats(); } if (LstScanLog.Items.Count > 0) LstScanLog.ScrollIntoView(LstScanLog.Items[^1]); if (sp.IsComplete) { LblCurrentProcess.Text = Lang.T("扫描完成", "Scan complete"); LblProcCount.Text = Lang.T($" — {_results.Count} 条结果", $" — {_results.Count} results"); } } // ═══════════════════════════════════════════════════════════ // UI HELPERS // ═══════════════════════════════════════════════════════════ private void AddLog(string message) { var ts = DateTime.Now.ToString("HH:mm:ss"); _scanLog.Add($"{ts} {message}"); } private void UpdateStats() { var total = _results.Count; var high = _results.Count(r => r.RiskLevel == RiskLevel.High); var susp = _results.Count(r => r.RiskLevel == RiskLevel.Suspicious); var safe = total - high - susp; StatTotal.Text = total.ToString(); StatHigh.Text = high.ToString(); StatSuspicious.Text = susp.ToString(); StatSafe.Text = safe.ToString(); if (high > 0) { LblDashboardStatus.Text = Lang.T($"● {high} 条告警", $"● {high} ALERT{(high > 1 ? "S" : "")}"); LblDashboardStatus.Foreground = TryFindResource("AccentRedBrush") as System.Windows.Media.Brush; } else if (susp > 0) { LblDashboardStatus.Text = Lang.T($"● {susp} 条警告", $"● {susp} WARNING{(susp > 1 ? "S" : "")}"); LblDashboardStatus.Foreground = TryFindResource("AccentYellowBrush") as System.Windows.Media.Brush; } else { LblDashboardStatus.Text = Lang.T("● 监控中", "● MONITORING"); LblDashboardStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush; } } private void SetScanStatus(string text, bool isScanning) { LblScanStatus.Text = $"● {text}"; if (isScanning) { LblScanStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush; ScanStatusBadge.Background = TryFindResource("HighlightBlueBrush") as System.Windows.Media.Brush; } else { LblScanStatus.Foreground = TryFindResource("AccentGreenBrush") as System.Windows.Media.Brush; ScanStatusBadge.Background = new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromRgb(0xf0, 0xfd, 0xf4)); } } // ═══════════════════════════════════════════════════════════ // SETTINGS VIEW // ═══════════════════════════════════════════════════════════ private void LoadSettingsToUI() { CmbAIProvider.SelectedIndex = _settings.AI.Provider switch { AIProvider.OpenAICompatible => 1, AIProvider.Claude => 2, AIProvider.Local => 3, _ => 0 }; TxtApiKey.Password = _settings.AI.ApiKey; TxtBaseUrl.Text = _settings.AI.BaseUrl; TxtModel.Text = _settings.AI.Model; TxtCeMcpPath.Text = _settings.CeMcpPath; TxtRulesDir.Text = _settings.RulesDir; TxtLogDir.Text = _settings.LogDir; CmbScanProfile.SelectedIndex = _settings.ScanProfile switch { "fast" => 0, "deep" => 2, _ => 1 }; } private void BtnSaveSettings_Click(object sender, RoutedEventArgs e) { var providerTag = (CmbAIProvider.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "Heuristic"; var provider = providerTag switch { "OpenAICompatible" => AIProvider.OpenAICompatible, "Claude" => AIProvider.Claude, "Local" => AIProvider.Local, _ => AIProvider.Heuristic }; _settings.AI = AIConfig.DefaultFor(provider); _settings.AI.ApiKey = TxtApiKey.Password; _settings.AI.BaseUrl = TxtBaseUrl.Text.Trim(); _settings.AI.Model = TxtModel.Text.Trim(); _settings.CeMcpPath = TxtCeMcpPath.Text.Trim(); _settings.RulesDir = TxtRulesDir.Text.Trim(); _settings.LogDir = TxtLogDir.Text.Trim(); var profileTag = (CmbScanProfile.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "balanced"; _settings.ScanProfile = profileTag; _settings.Save(); _aiAnalyzer = new AIAnalyzer(_settings.AI); _signatures = _memoryScanner.LoadSignatures(_settings.RulesDir); AddLog(Lang.T("[settings] 设置已保存", "[settings] Settings saved")); System.Windows.MessageBox.Show( Lang.T("设置已保存,AI 分析器和签名库已重新加载。", "Settings saved. AI analyzer and signatures reloaded."), "MemScan-EDR", MessageBoxButton.OK, MessageBoxImage.Information); } private void BtnBrowseCeMcp_Click(object sender, RoutedEventArgs e) { var dlg = new OpenFileDialog { Title = Lang.T("选择 CE-MCP 服务器脚本", "Select CE-MCP Server Script"), Filter = "Python (*.py)|*.py|All (*.*)|*.*" }; if (dlg.ShowDialog() == true) TxtCeMcpPath.Text = dlg.FileName; } private void BtnBrowseRules_Click(object sender, RoutedEventArgs e) { var folder = BrowseFolder(Lang.T("选择扫描规则目录", "Select Scan Rules Directory")); if (folder != null) TxtRulesDir.Text = folder; } private void BtnBrowseLog_Click(object sender, RoutedEventArgs e) { var folder = BrowseFolder(Lang.T("选择日志目录", "Select Log Directory")); if (folder != null) TxtLogDir.Text = folder; } private static string? BrowseFolder(string description) { var dlg = new OpenFolderDialog { Title = description }; return dlg.ShowDialog() == true ? dlg.FolderName : null; } }