MemScan-EDR/src-csharp/MemScanEDR/MainWindow.xaml.cs

569 lines
26 KiB
C#
Raw Permalink Normal View History

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<string> → comma-separated string ───
public class ListToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is List<string> list && list.Count > 0 ? string.Join(", ", list) : "—";
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
// ─── Value Converter: RiskLevel → Brush color ───
public class RiskLevelToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is RiskLevel level)
{
return level switch
{
RiskLevel.High => new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromRgb(0xef, 0x44, 0x44)), // Red
RiskLevel.Suspicious => new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromRgb(0xf5, 0x9e, 0x0b)), // Yellow/Amber
_ => new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromRgb(0x22, 0xc5, 0x5e)) // Green
};
}
return new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromRgb(0x22, 0xc5, 0x5e));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
// ─── Value Converter: RiskLevel → display text ───
public class RiskLevelToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is RiskLevel level)
{
return level switch
{
RiskLevel.High => Lang.T("高危", "HIGH"),
RiskLevel.Suspicious => Lang.T("可疑", "SUSP"),
_ => Lang.T("安全", "SAFE")
};
}
return "—";
}
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 v2.0
// ═══════════════════════════════════════════════════════════════
public partial class MainWindow : Window
{
// ── Services ───────────────────────────────────────────────
private readonly ProcessCollector _processCollector = new();
private readonly MemoryScanner _memoryScanner = new();
private AIAnalyzer? _aiAnalyzer;
// ── v2.0: 检测管线 ──
private DetectionPipeline? _pipeline;
// ── Application state ──────────────────────────────────────
private AppSettings _settings = null!;
private readonly ObservableCollection<ScanResult> _results = new();
private readonly ObservableCollection<string> _scanLog = new();
private List<Signature> _signatures = new();
private CancellationTokenSource? _cts;
private bool _isScanning;
// ═══════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════
public MainWindow()
{
try
{
InitializeComponent();
// Bind observable collections
DgAlerts.ItemsSource = _results;
DgProcesses.ItemsSource = _results;
LstScanLog.ItemsSource = _scanLog;
// Load settings & initialize
_settings = AppSettings.Load();
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
var scanMode = _settings.ScanProfile switch
{
"fast" => ScanMode.Fast,
"deep" => ScanMode.Deep,
_ => ScanMode.Balanced
};
_aiAnalyzer = new AIAnalyzer(_settings.AI, _signatures, scanMode);
_pipeline = new DetectionPipeline(scanMode);
// Apply saved settings to the UI
LoadSettingsToUI();
ApplyLanguage();
AddLog(Lang.T("[init] MemScan-EDR v2.0 AI 内存检测引擎已加载",
"[init] MemScan-EDR v2.0 AI memory detection engine loaded"));
AddLog(Lang.T($"[init] 扫描模式: {scanMode} | 本地AI: {(_settings.AI.EnableLocalAI ? "" : "")}",
$"[init] Scan mode: {scanMode} | Local AI: {(_settings.AI.EnableLocalAI ? "enabled" : "disabled")}"));
}
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 v2.0",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// ═══════════════════════════════════════════════════════════
// LANGUAGE TOGGLE
// ═══════════════════════════════════════════════════════════
private void BtnLangToggle_Click(object sender, RoutedEventArgs e)
{
Lang.SetChinese(!Lang.IsChinese);
ApplyLanguage();
}
private void ApplyLanguage()
{
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("AI 扫描", "AI SCAN");
TbQuickScanDesc.Text = Lang.T("AI 管线分析所有进程", "Pipeline analyze all");
BtnStartScan.Content = Lang.T("开始扫描", "Start Scan");
// Dashboard
TbDashboardTitle.Text = Lang.T("安全仪表盘", "Security Dashboard");
TbStatTotal.Text = Lang.T("已扫描", "TOTAL");
TbStatTotalDesc.Text = Lang.T("进程", "Processes");
TbStatHigh.Text = Lang.T("高风险", "HIGH");
TbStatHighDesc.Text = Lang.T("严重告警", "Critical");
TbStatSusp.Text = Lang.T("可疑", "SUSP");
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("AI 扫描进度", "AI Scan Progress");
BtnStopScan.Content = Lang.T("停止扫描", "Stop Scan");
TbScanLog.Text = Lang.T("扫描日志", "Scan Log");
// Process view
TbProcTitle.Text = Lang.T("AI 扫描结果", "AI Scan Results");
// 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");
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 (no API)"), Tag = "Heuristic" };
CmbAIProvider.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("OpenAI 兼容", "OpenAI-Compatible"), Tag = "OpenAICompatible" };
CmbAIProvider.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("Claude (Anthropic)", "Claude (Anthropic)"), Tag = "Claude" };
CmbAIProvider.Items[3] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("本地模型 (Ollama)", "Local (Ollama)"), Tag = "Local" };
CmbScanProfile.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("快速 - 元数据筛查", "Fast - metadata only"), Tag = "fast" };
CmbScanProfile.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("平衡 - 采样分析 (推荐)", "Balanced (recommended)"), Tag = "balanced" };
CmbScanProfile.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("深度 - 全量内存分析", "Deep - full memory"), Tag = "deep" };
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();
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("初始化 AI 管线...", "Initializing AI pipeline...");
NavScan.IsChecked = true;
SetScanStatus(Lang.T("AI 扫描中", "AI SCANNING"), true);
AddLog(Lang.T($"[scan] v2.0 AI 扫描开始 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
$"[scan] v2.0 AI scan started — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
var progress = new Progress<ScanProgress>(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 v2.0 (background)
// ═══════════════════════════════════════════════════════════
private async Task RunScanAsync(IProgress<ScanProgress> progress, CancellationToken ct)
{
// Phase 1: 枚举进程
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;
var highCount = 0;
var suspCount = 0;
var safeCount = 0;
// Phase 2: 逐进程 AI 分析
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}] AI 分析 {proc.Name} (PID {proc.Pid})...",
$"[{scanned}/{total}] AI analyzing {proc.Name} (PID {proc.Pid})..."),
Progress = pct
});
try
{
// v2.0: 使用 AI 管线分析
var result = await _aiAnalyzer!.AnalyzeProcessAsync(proc);
if (result.RiskLevel == RiskLevel.High) highCount++;
else if (result.RiskLevel == RiskLevel.Suspicious) suspCount++;
else safeCount++;
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} layer={result.DetectionLayer}",
Progress = pct,
Result = result
});
// 统计更新
progress.Report(new ScanProgress
{
Message = $"STATS | safe={safeCount} susp={suspCount} high={highCount}",
Progress = pct
});
}
catch (Exception ex)
{
progress.Report(new ScanProgress
{
Message = $"[warn] {proc.Name} — {ex.Message}",
Progress = pct
});
}
// 控制扫描速度(避免 CPU 飙升)
await Task.Delay(10, ct);
}
// 结束总结
var summary = Lang.T(
$"[done] 扫描完成 — {total}进程 | 安全={safeCount} 可疑={suspCount} 高风险={highCount}",
$"[done] Scan complete — {total} processes | Safe={safeCount} Suspicious={suspCount} High={highCount}");
progress.Report(new ScanProgress { Message = summary, 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("AI 扫描完成", "AI 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("● AI 监控中", "● AI 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("AccentPurpleBrush") 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;
// v2.0: AI 管线开关
ChkEnableLocalAI.IsChecked = _settings.AI.EnableLocalAI;
ChkEnableLLM.IsChecked = _settings.AI.EnableLLMAssist;
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.AI.EnableLocalAI = ChkEnableLocalAI.IsChecked ?? true;
_settings.AI.EnableLLMAssist = ChkEnableLLM.IsChecked ?? false;
var profileTag = (CmbScanProfile.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "balanced";
_settings.ScanProfile = profileTag;
_settings.Save();
// 重新初始化
var scanMode = _settings.ScanProfile switch
{
"fast" => ScanMode.Fast,
"deep" => ScanMode.Deep,
_ => ScanMode.Balanced
};
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
_aiAnalyzer = new AIAnalyzer(_settings.AI, _signatures, scanMode);
_pipeline = new DetectionPipeline(scanMode);
AddLog(Lang.T($"[settings] 设置已保存 (扫描模式: {scanMode}, 本地AI: {(_settings.AI.EnableLocalAI ? "" : "")})",
$"[settings] Settings saved (mode: {scanMode}, local AI: {(_settings.AI.EnableLocalAI ? "on" : "off")})"));
System.Windows.MessageBox.Show(
Lang.T("设置已保存。AI 分析引擎已重新初始化。", "Settings saved. AI engine re-initialized."),
"MemScan-EDR v2.0", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void BtnBrowseRules_Click(object sender, RoutedEventArgs e) { }
private void BtnBrowseLog_Click(object sender, RoutedEventArgs e) { }
}