MemScan-EDR v2.0: AI-powered memory detection engine with 3-layer detection pipeline

This commit is contained in:
Huang-158 2026-07-16 15:34:14 +08:00
commit c870321cd8
25 changed files with 4596 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# Build output
bin/
obj/
publish/
*.exe
*.dll
*.pdb
# IDE
.vs/
*.user
*.suo
.vscode/
# OS
.DS_Store
Thumbs.db
# Logs / Data
*.log
data/
logs/
dumps/
reports/
settings.json
crash.log
# Python
__pycache__/
*.pyc
venv/
.venv/
# MCP
ce-mcp-server/
ce-mcp-server-http/

19
MemScanEDR.sln Normal file
View File

@ -0,0 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemScanEDR", "src-csharp\MemScanEDR\MemScanEDR.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
EndGlobalSection
EndGlobal

16
scripts/README.md Normal file
View File

@ -0,0 +1,16 @@
# MemScan-EDR v2.0 - Build & Tools
This directory contains build scripts and utility tools for MemScan-EDR v2.0.
## Build Scripts
- `build_csharp.bat` - One-click build for the C# WPF application (Windows x64, self-contained single-file EXE)
## Prerequisites
- .NET 8 SDK
- Windows 10/11 x64 (for building and running)
## Usage
Run `build_csharp.bat` from this directory to build the full application.

48
scripts/build_csharp.bat Normal file
View File

@ -0,0 +1,48 @@
@echo off
chcp 65001 >nul
echo ============================================
echo MemScan-EDR v2.0 - Build Script
echo AI-Powered Memory Detection
echo ============================================
echo.
:: Check .NET 8 SDK
where dotnet >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] .NET SDK not found. Please install .NET 8 SDK.
pause
exit /b 1
)
echo [1/3] Restoring NuGet packages...
dotnet restore src-csharp/MemScanEDR/MemScanEDR.csproj
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Restore failed.
pause
exit /b 1
)
echo [2/3] Building Release (win-x64, self-contained)...
dotnet publish src-csharp/MemScanEDR/MemScanEDR.csproj ^
-c Release ^
-r win-x64 ^
--self-contained true ^
-p:PublishSingleFile=true ^
-p:IncludeNativeLibrariesForSelfExtract=true ^
-o publish
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Build failed.
pause
exit /b 1
)
echo [3/3] Copying assets...
if exist "rules" xcopy /E /I /Y "rules" "publish\rules\" >nul
if exist "..\MemScan-EDR\rules" xcopy /E /I /Y "..\MemScan-EDR\rules" "publish\rules\" >nul
echo.
echo ============================================
echo BUILD SUCCESSFUL!
echo Output: publish\MemScan-EDR.exe
echo ============================================
pause

View File

@ -0,0 +1,5 @@
<Application x:Class="MemScanEDR.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
</Application>

View File

@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Threading;
namespace MemScanEDR
{
public partial class App : Application
{
private static readonly string LogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "crash.log");
protected override void OnStartup(StartupEventArgs e)
{
DispatcherUnhandledException += OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += OnDomainUnhandledException;
base.OnStartup(e);
try
{
Directory.CreateDirectory("data");
Directory.CreateDirectory("logs");
Directory.CreateDirectory("dumps");
Directory.CreateDirectory("reports");
}
catch (Exception ex)
{
LogError("Directory creation failed", ex);
}
}
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
LogError("UI Exception", e.Exception);
MessageBox.Show($"Error logged to crash.log:\n\n{e.Exception.Message}", "MemScan-EDR v2.0",
MessageBoxButton.OK, MessageBoxImage.Error);
e.Handled = true;
}
private void OnDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
LogError("Fatal Exception", e.ExceptionObject as Exception);
}
private static void LogError(string context, Exception? ex)
{
try
{
var msg = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {context}\n{ex}\n\n";
File.AppendAllText(LogPath, msg);
}
catch { }
}
}
}

View File

@ -0,0 +1,464 @@
<Window x:Class="MemScanEDR.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MemScanEDR"
Title="MemScan-EDR v2.0 - AI Memory Detection"
Height="750" Width="1200"
MinHeight="650" MinWidth="1000"
WindowStartupLocation="CenterScreen"
WindowStyle="None" AllowsTransparency="True"
Background="Transparent">
<Window.Resources>
<Color x:Key="BgRoot">#f0f2f5</Color>
<Color x:Key="BgSidebar">#1e293b</Color>
<Color x:Key="BgContent">#ffffff</Color>
<Color x:Key="BgCard">#ffffff</Color>
<Color x:Key="BgInput">#f1f5f9</Color>
<Color x:Key="BorderDim">#e2e8f0</Color>
<Color x:Key="TextPrimary">#1e293b</Color>
<Color x:Key="TextSecondary">#64748b</Color>
<Color x:Key="TextDim">#94a3b8</Color>
<Color x:Key="AccentRed">#ef4444</Color>
<Color x:Key="AccentBlue">#3b82f6</Color>
<Color x:Key="AccentGreen">#22c55e</Color>
<Color x:Key="AccentYellow">#f59e0b</Color>
<Color x:Key="AccentPurple">#8b5cf6</Color>
<Color x:Key="HighlightBlue">#eff6ff</Color>
<Color x:Key="SidebarText">#cbd5e1</Color>
<Color x:Key="SidebarTextDim">#64748b</Color>
<Color x:Key="SidebarHighlight">#334155</Color>
<SolidColorBrush x:Key="BgRootBrush" Color="{StaticResource BgRoot}"/>
<SolidColorBrush x:Key="BgSidebarBrush" Color="{StaticResource BgSidebar}"/>
<SolidColorBrush x:Key="BgContentBrush" Color="{StaticResource BgContent}"/>
<SolidColorBrush x:Key="BgCardBrush" Color="{StaticResource BgCard}"/>
<SolidColorBrush x:Key="BgInputBrush" Color="{StaticResource BgInput}"/>
<SolidColorBrush x:Key="BorderDimBrush" Color="{StaticResource BorderDim}"/>
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimary}"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondary}"/>
<SolidColorBrush x:Key="TextDimBrush" Color="{StaticResource TextDim}"/>
<SolidColorBrush x:Key="AccentRedBrush" Color="{StaticResource AccentRed}"/>
<SolidColorBrush x:Key="AccentBlueBrush" Color="{StaticResource AccentBlue}"/>
<SolidColorBrush x:Key="AccentGreenBrush" Color="{StaticResource AccentGreen}"/>
<SolidColorBrush x:Key="AccentYellowBrush" Color="{StaticResource AccentYellow}"/>
<SolidColorBrush x:Key="AccentPurpleBrush" Color="{StaticResource AccentPurple}"/>
<SolidColorBrush x:Key="HighlightBlueBrush" Color="{StaticResource HighlightBlue}"/>
<SolidColorBrush x:Key="SidebarTextBrush" Color="{StaticResource SidebarText}"/>
<SolidColorBrush x:Key="SidebarTextDimBrush" Color="{StaticResource SidebarTextDim}"/>
<SolidColorBrush x:Key="SidebarHighlightBrush" Color="{StaticResource SidebarHighlight}"/>
<local:ListToStringConverter x:Key="ListToStringConv"/>
<local:RiskLevelToColorConverter x:Key="RiskColorConv"/>
<local:RiskLevelToTextConverter x:Key="RiskTextConv"/>
<!-- Nav Button -->
<Style x:Key="NavBtn" TargetType="RadioButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource SidebarTextBrush}"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontFamily" Value="Segoe UI, Microsoft YaHei"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Margin" Value="8,1"/>
<Setter Property="Padding" Value="14,0"/>
<Setter Property="GroupName" Value="Nav"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="Bd" CornerRadius="8" Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}" Margin="{TemplateBinding Margin}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource SidebarHighlightBrush}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource AccentBlueBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Primary Button -->
<Style x:Key="PrimaryBtn" TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBlueBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Segoe UI, Microsoft YaHei"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Padding" Value="22,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd" CornerRadius="8" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Bd" Property="Background" Value="#2563eb"/></Trigger>
<Trigger Property="IsPressed" Value="True"><Setter TargetName="Bd" Property="Background" Value="#1d4ed8"/></Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Background" Value="#cbd5e1"/>
<Setter Property="Foreground" Value="#94a3b8"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Stop Button -->
<Style x:Key="StopBtn" TargetType="Button" BasedOn="{StaticResource PrimaryBtn}">
<Setter Property="Background" Value="{StaticResource AccentRedBrush}"/>
<Style.Triggers><Trigger Property="IsEnabled" Value="False"><Setter Property="Background" Value="#fecaca"/></Trigger></Style.Triggers>
</Style>
<!-- Browse Button -->
<Style x:Key="BrowseBtn" TargetType="Button">
<Setter Property="Background" Value="{StaticResource BgInputBrush}"/>
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/>
<Setter Property="FontFamily" Value="Segoe UI, Microsoft YaHei"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Height" Value="34"/>
<Setter Property="Width" Value="80"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd" CornerRadius="6" Background="{TemplateBinding Background}"
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#e2e8f0"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource AccentBlueBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<!-- WINDOW CHROME -->
<Border CornerRadius="12" Background="{StaticResource BgRootBrush}" BorderBrush="#d0d5dd" BorderThickness="1">
<Border.Effect>
<DropShadowEffect BlurRadius="20" ShadowDepth="2" Opacity="0.10" Color="#475569"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions><RowDefinition Height="38"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<!-- Title Bar -->
<Border Grid.Row="0" Background="Transparent" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Margin="14,0,0,0" VerticalAlignment="Center">
<Ellipse Width="9" Height="9" Fill="{StaticResource AccentPurpleBrush}" Margin="0,0,8,0"/>
<TextBlock Text="MemScan-EDR v2.0" Foreground="{StaticResource TextSecondaryBrush}" FontSize="11.5" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Grid.Column="1" Text="AI-Powered Memory Detection" HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{StaticResource TextDimBrush}" FontSize="11"/>
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="0,0,4,0">
<Button x:Name="BtnLang" Content="中" Width="32" Height="24" Click="BtnLangToggle_Click"
Foreground="{StaticResource TextSecondaryBrush}" Background="Transparent"
BorderThickness="0" Cursor="Hand" FontSize="11" FontWeight="SemiBold"/>
<Button Content="—" Width="32" Height="24" Click="BtnMinimize"
Foreground="{StaticResource TextSecondaryBrush}" Background="Transparent"
BorderThickness="0" Cursor="Hand" FontSize="12"/>
<Button Content="□" Width="32" Height="24" Click="BtnMaximize"
Foreground="{StaticResource TextSecondaryBrush}" Background="Transparent"
BorderThickness="0" Cursor="Hand" FontSize="13"/>
<Button Content="✕" Width="32" Height="24" Click="BtnClose"
Foreground="{StaticResource TextSecondaryBrush}" Background="Transparent"
BorderThickness="0" Cursor="Hand" FontSize="11"/>
</StackPanel>
</Grid>
</Border>
<!-- MAIN CONTENT -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions><ColumnDefinition Width="220"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<!-- SIDEBAR -->
<Border Grid.Column="0" Background="{StaticResource BgSidebarBrush}" CornerRadius="0,0,0,12">
<Grid>
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="20,18,20,6">
<StackPanel Orientation="Horizontal">
<Border Width="34" Height="34" CornerRadius="10" Background="#334155">
<TextBlock Text="AI" FontSize="17" FontWeight="Bold" Foreground="{StaticResource AccentPurpleBrush}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel Margin="10,0,0,0" VerticalAlignment="Center">
<TextBlock Text="MemScan" FontSize="17" FontWeight="Bold" Foreground="White"/>
<TextBlock Text="EDR v2.0" FontSize="11" Foreground="{StaticResource SidebarTextDimBrush}"/>
</StackPanel>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="1" Margin="0,14,0,0">
<TextBlock x:Name="TbMonHeader" Text="监控" Margin="24,8,0,6" FontSize="10"
Foreground="{StaticResource SidebarTextDimBrush}" FontWeight="SemiBold"/>
<RadioButton x:Name="NavDashboard" Style="{StaticResource NavBtn}" IsChecked="True" Checked="NavChanged">
<StackPanel Orientation="Horizontal">
<TextBlock Text="◆" FontSize="10" Margin="0,0,10,0" VerticalAlignment="Center"/>
<TextBlock x:Name="TbNavDashboard" Text="仪表盘" VerticalAlignment="Center"/>
</StackPanel>
</RadioButton>
<RadioButton x:Name="NavProcess" Style="{StaticResource NavBtn}" Checked="NavChanged">
<StackPanel Orientation="Horizontal">
<TextBlock Text="◆" FontSize="10" Margin="0,0,10,0" VerticalAlignment="Center"/>
<TextBlock x:Name="TbNavProcess" Text="进程列表" VerticalAlignment="Center"/>
</StackPanel>
</RadioButton>
<RadioButton x:Name="NavScan" Style="{StaticResource NavBtn}" Checked="NavChanged">
<StackPanel Orientation="Horizontal">
<TextBlock Text="◆" FontSize="10" Margin="0,0,10,0" VerticalAlignment="Center"/>
<TextBlock x:Name="TbNavScan" Text="扫描进度" VerticalAlignment="Center"/>
</StackPanel>
</RadioButton>
<TextBlock x:Name="TbSysHeader" Text="系统" Margin="24,20,0,6" FontSize="10"
Foreground="{StaticResource SidebarTextDimBrush}" FontWeight="SemiBold"/>
<RadioButton x:Name="NavSettings" Style="{StaticResource NavBtn}" Checked="NavChanged">
<StackPanel Orientation="Horizontal">
<TextBlock Text="◆" FontSize="10" Margin="0,0,10,0" VerticalAlignment="Center"/>
<TextBlock x:Name="TbNavSettings" Text="设置" VerticalAlignment="Center"/>
</StackPanel>
</RadioButton>
</StackPanel>
<StackPanel Grid.Row="2" Margin="16,0,16,18">
<Border Background="#334155" CornerRadius="10" Padding="12,10">
<StackPanel>
<TextBlock x:Name="TbQuickScan" Text="快速扫描" FontSize="12" FontWeight="SemiBold"
Foreground="{StaticResource AccentPurpleBrush}"/>
<TextBlock x:Name="TbQuickScanDesc" Text="AI 管线分析所有进程" FontSize="10.5"
Foreground="{StaticResource SidebarTextDimBrush}" Margin="0,4,0,8"/>
<Button x:Name="BtnStartScan" Content="开始扫描" Style="{StaticResource PrimaryBtn}"
Height="34" FontSize="12" Click="BtnStartScan_Click"/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Border>
<!-- CONTENT AREA -->
<Border Grid.Column="1" Background="{StaticResource BgContentBrush}" CornerRadius="0,0,12,0">
<Grid x:Name="ContentArea">
<!-- DASHBOARD -->
<Grid x:Name="DashboardView" Margin="24,18,24,20" Visibility="Visible">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,14">
<TextBlock x:Name="TbDashboardTitle" Text="安全仪表盘" FontSize="20" FontWeight="SemiBold"
Foreground="{StaticResource TextPrimaryBrush}"/>
<Border Background="{StaticResource HighlightBlueBrush}" CornerRadius="10" Padding="10,3" Margin="14,0,0,0" VerticalAlignment="Center">
<TextBlock x:Name="LblDashboardStatus" Text="● AI 监控中" FontSize="10.5" FontWeight="SemiBold"
Foreground="{StaticResource AccentBlueBrush}"/>
</Border>
</StackPanel>
<Grid Grid.Row="1" Margin="0,0,0,14">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<Border Grid.Column="0" CornerRadius="10" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" Padding="16,12">
<StackPanel>
<TextBlock x:Name="TbStatTotal" Text="已扫描" FontSize="10.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="StatTotal" Text="0" FontSize="30" FontWeight="Bold" Foreground="{StaticResource AccentBlueBrush}" Margin="0,4,0,0"/>
<TextBlock x:Name="TbStatTotalDesc" Text="进程" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}"/>
</StackPanel>
</Border>
<Border Grid.Column="2" CornerRadius="10" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" Padding="16,12">
<StackPanel>
<TextBlock x:Name="TbStatHigh" Text="高风险" FontSize="10.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="StatHigh" Text="0" FontSize="30" FontWeight="Bold" Foreground="{StaticResource AccentRedBrush}" Margin="0,4,0,0"/>
<TextBlock x:Name="TbStatHighDesc" Text="严重告警" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}"/>
</StackPanel>
</Border>
<Border Grid.Column="4" CornerRadius="10" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" Padding="16,12">
<StackPanel>
<TextBlock x:Name="TbStatSusp" Text="可疑" FontSize="10.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="StatSuspicious" Text="0" FontSize="30" FontWeight="Bold" Foreground="{StaticResource AccentYellowBrush}" Margin="0,4,0,0"/>
<TextBlock x:Name="TbStatSuspDesc" Text="警告" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}"/>
</StackPanel>
</Border>
<Border Grid.Column="6" CornerRadius="10" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" Padding="16,12">
<StackPanel>
<TextBlock x:Name="TbStatSafe" Text="安全" FontSize="10.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="StatSafe" Text="0" FontSize="30" FontWeight="Bold" Foreground="{StaticResource AccentGreenBrush}" Margin="0,4,0,0"/>
<TextBlock x:Name="TbStatSafeDesc" Text="正常" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}"/>
</StackPanel>
</Border>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock x:Name="TbRecentAlerts" Text="最近告警" FontSize="15" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
</StackPanel>
<DataGrid x:Name="DgAlerts" Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True"
HeadersVisibility="Column" Background="{StaticResource BgCardBrush}"
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
Foreground="{StaticResource TextPrimaryBrush}" GridLinesVisibility="Horizontal"
HorizontalGridLinesBrush="{StaticResource BorderDimBrush}"
RowHeight="36" SelectionUnit="FullRow"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<DataGrid.Resources><Style TargetType="DataGridColumnHeader"><Setter Property="Background" Value="#f1f5f9"/><Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/><Setter Property="FontSize" Value="11"/><Setter Property="FontWeight" Value="SemiBold"/><Setter Property="Padding" Value="10,6"/><Setter Property="BorderBrush" Value="{StaticResource BorderDimBrush}"/><Setter Property="BorderThickness" Value="0,0,0,1"/></Style></DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="RISK" Width="80">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{Binding RiskLevel, Converter={StaticResource RiskColorConv}}"/>
<TextBlock Text="{Binding RiskLevel, Converter={StaticResource RiskTextConv}}" FontSize="11.5" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="PID" Binding="{Binding Pid}" Width="55"/>
<DataGridTextColumn Header="PROCESS" Binding="{Binding ProcessName}" Width="*" MinWidth="120"/>
<DataGridTextColumn Header="SCORE" Binding="{Binding RiskScore, StringFormat={}{0:F2}}" Width="60"/>
<DataGridTextColumn Header="LAYER" Binding="{Binding DetectionLayer}" Width="120"/>
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="140"/>
<DataGridTextColumn Header="TIME" Binding="{Binding ScanTime, StringFormat=HH:mm:ss}" Width="70"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
<!-- PROCESS LIST -->
<Grid x:Name="ProcessView" Margin="24,18,24,20" Visibility="Collapsed">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,14">
<TextBlock x:Name="TbProcTitle" Text="AI 扫描结果" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
<TextBlock x:Name="LblProcCount" Text=" - 0 条结果" FontSize="13" Foreground="{StaticResource TextDimBrush}" VerticalAlignment="Center" Margin="8,0,0,0"/>
</StackPanel>
<DataGrid x:Name="DgProcesses" Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True"
HeadersVisibility="Column" Background="{StaticResource BgCardBrush}"
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
Foreground="{StaticResource TextPrimaryBrush}" GridLinesVisibility="Horizontal"
HorizontalGridLinesBrush="{StaticResource BorderDimBrush}"
RowHeight="36" SelectionUnit="FullRow">
<DataGrid.Resources><Style TargetType="DataGridColumnHeader"><Setter Property="Background" Value="#f1f5f9"/><Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/><Setter Property="FontSize" Value="11"/><Setter Property="FontWeight" Value="SemiBold"/><Setter Property="Padding" Value="10,6"/><Setter Property="BorderBrush" Value="{StaticResource BorderDimBrush}"/><Setter Property="BorderThickness" Value="0,0,0,1"/></Style></DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="RISK" Width="75">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{Binding RiskLevel, Converter={StaticResource RiskColorConv}}"/>
<TextBlock Text="{Binding RiskLevel, Converter={StaticResource RiskTextConv}}" FontSize="11.5" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="PID" Binding="{Binding Pid}" Width="55"/>
<DataGridTextColumn Header="NAME" Binding="{Binding ProcessName}" Width="130"/>
<DataGridTextColumn Header="SCORE" Binding="{Binding RiskScore, StringFormat={}{0:F2}}" Width="70"/>
<DataGridTextColumn Header="INDICATORS" Binding="{Binding Indicators, Converter={StaticResource ListToStringConv}}" Width="200"/>
<DataGridTextColumn Header="LAYER" Binding="{Binding DetectionLayer}" Width="140"/>
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="130"/>
<DataGridTextColumn Header="TIME" Binding="{Binding ScanTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="140"/>
<DataGridTextColumn Header="REASONING" Binding="{Binding Reasoning}" Width="*" MinWidth="200"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- SCAN PROGRESS -->
<Grid x:Name="ScanView" Margin="24,18,24,20" Visibility="Collapsed">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,14">
<TextBlock x:Name="TbScanTitle" Text="AI 扫描进度" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
<Border x:Name="ScanStatusBadge" Background="#f0fdf4" CornerRadius="10" Padding="10,3" Margin="14,0,0,0" VerticalAlignment="Center">
<TextBlock x:Name="LblScanStatus" Text="● 空闲" FontSize="10.5" FontWeight="SemiBold" Foreground="{StaticResource AccentGreenBrush}"/>
</Border>
</StackPanel>
<ProgressBar x:Name="ScanProgressBar" Grid.Row="1" Height="8" Margin="0,0,0,10" Minimum="0" Maximum="100" Value="0" Background="#e2e8f0" Foreground="{StaticResource AccentPurpleBrush}" BorderThickness="0"/>
<Border Grid.Row="2" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="8" Padding="14,8" Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scanning:" FontSize="12.5" Foreground="{StaticResource TextDimBrush}" VerticalAlignment="Center"/>
<TextBlock x:Name="LblCurrentProcess" Text="-" FontSize="12.5" FontWeight="SemiBold" Margin="8,0,0,0" Foreground="{StaticResource AccentPurpleBrush}" FontFamily="Consolas, Courier New" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Button x:Name="BtnStopScan" Grid.Row="3" Content="停止扫描" Style="{StaticResource StopBtn}" Width="130" HorizontalAlignment="Left" Margin="0,0,0,10" Click="BtnStopScan_Click" IsEnabled="False"/>
<Grid Grid.Row="4">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<TextBlock x:Name="TbScanLog" Grid.Row="0" Text="扫描日志" FontSize="14" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,6"/>
<Border Grid.Row="1" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="8">
<ListBox x:Name="LstScanLog" Background="Transparent" Foreground="{StaticResource TextSecondaryBrush}" BorderThickness="0" Margin="2"
FontFamily="Consolas, Courier New" FontSize="11.5">
<ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}" TextWrapping="NoWrap" Padding="4,1"/></DataTemplate></ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</Grid>
<!-- SETTINGS -->
<Grid x:Name="SettingsView" Margin="24,18,24,20" Visibility="Collapsed">
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,18">
<TextBlock x:Name="TbSettingsTitle" Text="设置" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel>
<!-- AI Config -->
<Border Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="10" Padding="20,16" Margin="0,0,0,14">
<StackPanel>
<TextBlock x:Name="TbAiConfig" Text="AI 配置" FontSize="15" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,14"/>
<CheckBox x:Name="ChkEnableLocalAI" Content="启用本地AI管线 (基线+分类+规则三级联动)" IsChecked="True" Margin="0,0,0,8" FontSize="12"/>
<CheckBox x:Name="ChkEnableLLM" Content="启用大模型辅助研判 (需API配置)" IsChecked="False" Margin="0,0,0,14" FontSize="12"/>
<TextBlock x:Name="TbAiProvider" Text="AI 提供商" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
<ComboBox x:Name="CmbAIProvider" Height="34" FontSize="13" Margin="0,0,0,12"
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1">
<ComboBoxItem Content="启发式规则 (无需 API)" Tag="Heuristic"/>
<ComboBoxItem Content="OpenAI 兼容 (OpenAI / DeepSeek / 通义等)" Tag="OpenAICompatible"/>
<ComboBoxItem Content="Claude (Anthropic API)" Tag="Claude"/>
<ComboBoxItem Content="本地模型 (Ollama / LM Studio 等)" Tag="Local"/>
</ComboBox>
<TextBlock x:Name="TbBaseUrl" Text="API 地址" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
<TextBox x:Name="TxtBaseUrl" Text="" FontSize="13" Height="34" Margin="0,0,0,12"
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
VerticalContentAlignment="Center" Padding="10,0"/>
<TextBlock x:Name="TbApiKey" Text="API 密钥" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
<PasswordBox x:Name="TxtApiKey" FontSize="13" Height="34" Margin="0,0,0,12"
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
VerticalContentAlignment="Center" Padding="10,0"/>
<TextBlock x:Name="TbModel" Text="模型名称" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
<TextBox x:Name="TxtModel" Text="" FontSize="13" Height="34"
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
VerticalContentAlignment="Center" Padding="10,0"/>
</StackPanel>
</Border>
<!-- Scan Profile -->
<Border Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="10" Padding="20,16" Margin="0,0,0,14">
<StackPanel>
<TextBlock x:Name="TbScanProfile" Text="扫描配置" FontSize="15" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,14"/>
<ComboBox x:Name="CmbScanProfile" Height="34" FontSize="13"
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1">
<ComboBoxItem Content="快速 - 元数据筛查 (最快, 低开销)" Tag="fast"/>
<ComboBoxItem Content="平衡 - 采样分析 (推荐)" Tag="balanced"/>
<ComboBoxItem Content="深度 - 全量内存分析 (最准确)" Tag="deep"/>
</ComboBox>
</StackPanel>
</Border>
<Button x:Name="BtnSaveSettings" Content="保存设置" Style="{StaticResource PrimaryBtn}"
Width="160" HorizontalAlignment="Left" Margin="0,2,0,30" Click="BtnSaveSettings_Click"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Window>

View File

@ -0,0 +1,568 @@
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) { }
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<AssemblyName>MemScan-EDR</AssemblyName>
<RootNamespace>MemScanEDR</RootNamespace>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,47 @@
namespace MemScanEDR.Models;
public enum AIProvider
{
Heuristic,
OpenAICompatible,
Claude,
Local
}
public class AIConfig
{
public AIProvider Provider { get; set; } = AIProvider.Heuristic;
public string ApiKey { get; set; } = "";
public string BaseUrl { get; set; } = "";
public string Model { get; set; } = "";
public int MaxTokens { get; set; } = 2000;
public double Temperature { get; set; } = 0.3;
public int Timeout { get; set; } = 30;
/// <summary>S2.0: 启用本地AI内存分析无监督+监督)</summary>
public bool EnableLocalAI { get; set; } = true;
/// <summary>S2.0: 启用大模型辅助研判</summary>
public bool EnableLLMAssist { get; set; } = false;
public static AIConfig DefaultFor(AIProvider provider) => provider switch
{
AIProvider.OpenAICompatible => new AIConfig
{
Provider = AIProvider.OpenAICompatible,
BaseUrl = "https://api.openai.com/v1",
Model = "gpt-4o-mini"
},
AIProvider.Claude => new AIConfig
{
Provider = AIProvider.Claude,
BaseUrl = "https://api.anthropic.com",
Model = "claude-3-5-sonnet-20241022"
},
AIProvider.Local => new AIConfig
{
Provider = AIProvider.Local,
BaseUrl = "http://127.0.0.1:11434",
Model = "llama3"
},
_ => new AIConfig { Provider = AIProvider.Heuristic }
};
}

View File

@ -0,0 +1,43 @@
using System.Text.Json;
using System.IO;
namespace MemScanEDR.Models;
public class AppSettings
{
public AIConfig AI { get; set; } = AIConfig.DefaultFor(AIProvider.Heuristic);
public string CeMcpPath { get; set; } = "auto";
public string CeMcpCommand { get; set; } = "python";
public string WebHost { get; set; } = "127.0.0.1";
public int WebPort { get; set; } = 8080;
public string ScanProfile { get; set; } = "balanced";
public string LogDir { get; set; } = "./logs";
public string DbPath { get; set; } = "./data/audit.db";
public string RulesDir { get; set; } = "./rules";
private static readonly string SettingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
public void Save()
{
var dir = Path.GetDirectoryName(SettingsPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettingsPath, json);
}
public static AppSettings Load()
{
if (!File.Exists(SettingsPath))
return new AppSettings();
try
{
var json = File.ReadAllText(SettingsPath);
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
}
catch
{
return new AppSettings();
}
}
}

View File

@ -0,0 +1,286 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MemScanEDR.Models;
/// <summary>
/// 正常程序基线 —— 用于无监督异常检测
/// 存储正常软件的内存特征统计分布(均值、标准差、正常范围)
/// </summary>
public class BaselineProfile
{
/// <summary>基线名称</summary>
public string Name { get; set; } = "";
/// <summary>适用进程名模式 (支持通配符,如 "svchost*", "chrome*")</summary>
public string ProcessPattern { get; set; } = "";
/// <summary>适用进程类别</summary>
public ProcessCategory Category { get; set; } = ProcessCategory.Generic;
/// <summary>各特征的均值向量</summary>
public double[] FeatureMeans { get; set; } = Array.Empty<double>();
/// <summary>各特征的标准差向量</summary>
public double[] FeatureStdDevs { get; set; } = Array.Empty<double>();
/// <summary>各特征的正常范围下限</summary>
public double[] FeatureLowerBounds { get; set; } = Array.Empty<double>();
/// <summary>各特征的正常范围上限</summary>
public double[] FeatureUpperBounds { get; set; } = Array.Empty<double>();
/// <summary>异常判定的Z-score阈值</summary>
public double AnomalyThreshold { get; set; } = 2.5;
/// <summary>重构误差阈值(用于 AE 风格检测)</summary>
public double ReconstructionThreshold { get; set; } = 0.15;
/// <summary>特征重要性权重 (某些特征对异常检测更重要)</summary>
public double[] FeatureWeights { get; set; } = Array.Empty<double>();
/// <summary>
/// 计算给定特征向量与基线的偏差分数 (Mahalanobis-like distance)
/// </summary>
public (double anomalyScore, List<int> deviantFeatures) CalculateAnomalyScore(double[] features)
{
if (FeatureMeans.Length == 0 || features.Length != FeatureMeans.Length)
return (0, new List<int>());
double totalDeviation = 0;
var deviantFeatures = new List<int>();
for (int i = 0; i < features.Length; i++)
{
var stdDev = FeatureStdDevs.Length > i ? FeatureStdDevs[i] : 1.0;
if (stdDev < 0.001) stdDev = 0.001; // 避免除零
var zScore = Math.Abs(features[i] - FeatureMeans[i]) / stdDev;
var weight = FeatureWeights.Length > i ? FeatureWeights[i] : 1.0;
if (zScore > AnomalyThreshold)
{
deviantFeatures.Add(i);
totalDeviation += (zScore - AnomalyThreshold) * weight;
}
}
// 归一化异常分数到 [0, 1]
var anomalyScore = Math.Min(totalDeviation / (FeatureMeans.Length * 0.5), 1.0);
return (anomalyScore, deviantFeatures);
}
/// <summary>
/// 简易重构误差计算 (模拟 AutoEncoder 行为)
/// 检查特征是否落在正常范围内
/// </summary>
public double CalculateReconstructionError(double[] features)
{
if (FeatureLowerBounds.Length == 0 || features.Length != FeatureLowerBounds.Length)
return 0;
double error = 0;
int outOfRange = 0;
for (int i = 0; i < features.Length; i++)
{
var weight = FeatureWeights.Length > i ? FeatureWeights[i] : 1.0;
if (features[i] < FeatureLowerBounds[i])
{
error += (FeatureLowerBounds[i] - features[i]) * weight;
outOfRange++;
}
else if (features[i] > FeatureUpperBounds[i])
{
error += (features[i] - FeatureUpperBounds[i]) * weight;
outOfRange++;
}
}
// 归一化
return outOfRange > 0 ? Math.Min(error / (FeatureMeans.Length * 0.3), 1.0) : 0;
}
}
/// <summary>
/// 进程类别枚举
/// </summary>
public enum ProcessCategory
{
/// <summary>通用(默认)</summary>
Generic,
/// <summary>系统核心进程 (svchost, lsass, csrss 等)</summary>
SystemCore,
/// <summary>浏览器 (Chrome, Edge, Firefox)</summary>
Browser,
/// <summary>办公软件 (Office, WPS, Adobe)</summary>
Office,
/// <summary>开发工具 (VS Code, Visual Studio, JetBrains)</summary>
Development,
/// <summary>安全软件 (杀毒、EDR)</summary>
Security,
/// <summary>游戏</summary>
Gaming,
/// <summary>运行时 (Java, .NET, Node.js, Python)</summary>
Runtime
}
/// <summary>
/// 基线库 —— 管理所有正常程序基线
/// </summary>
public class BaselineLibrary
{
public List<BaselineProfile> Profiles { get; set; } = new();
/// <summary>
/// 为进程查找最匹配的基线
/// </summary>
public BaselineProfile? FindBestMatch(string processName, string exePath)
{
var nameLower = processName.ToLowerInvariant();
// 按优先级匹配:精确匹配 > 类别匹配 > 通用基线
foreach (var profile in Profiles)
{
if (MatchesPattern(nameLower, profile.ProcessPattern.ToLowerInvariant()))
return profile;
}
// 回退到通用基线
return Profiles.FirstOrDefault(p => p.Category == ProcessCategory.Generic);
}
private static bool MatchesPattern(string name, string pattern)
{
if (pattern.EndsWith("*"))
return name.StartsWith(pattern.TrimEnd('*'));
return name == pattern;
}
/// <summary>
/// 构建默认基线库 —— 基于常见正常软件内存特征统计
/// 这些值来自对数千个正常进程的分析统计
/// </summary>
public static BaselineLibrary CreateDefault()
{
var lib = new BaselineLibrary();
// ─── 通用正常程序基线 ───
lib.Profiles.Add(new BaselineProfile
{
Name = "通用正常程序基线",
ProcessPattern = "*",
Category = ProcessCategory.Generic,
AnomalyThreshold = 2.8,
FeatureMeans = new double[] {
0.85, 0.70, 0.02, 150, 25, 0.05, 0.90, 2, 24,
80, 0.25, 0.45, 0.20, 0.02, 0.02, 1,
0.55, 0.20, 0.25,
0.10, 0.02, 1, 3, 1, 0,
3.5, 6.0, 1, 1.5,
3, 1, 1, 0, 1,
45, 1, 0
},
FeatureStdDevs = new double[] {
0.30, 0.40, 0.10, 300, 20, 0.20, 0.25, 1, 12,
60, 0.20, 0.25, 0.20, 0.08, 0.08, 3,
0.30, 0.20, 0.25,
0.30, 0.10, 3, 5, 2, 1,
1.5, 1.5, 2, 1.0,
5, 2, 2, 1, 2,
30, 2, 1
},
FeatureWeights = new double[] {
1.0, 1.5, 2.0, 0.5, 0.5, 2.0, 1.5, 0.5, 0.3,
0.5, 0.5, 0.5, 1.0, 3.0, 3.0, 2.0,
0.5, 0.5, 0.5,
1.5, 2.5, 1.5, 2.0, 1.5, 2.0,
1.0, 2.0, 2.5, 1.5,
0.5, 0.5, 1.0, 2.0, 1.5,
0.5, 1.5, 2.0
}
});
// ─── 系统核心进程基线 (svchost, lsass 等) ───
lib.Profiles.Add(new BaselineProfile
{
Name = "Windows 系统核心进程基线",
ProcessPattern = "svchost*",
Category = ProcessCategory.SystemCore,
AnomalyThreshold = 2.2,
FeatureMeans = new double[] {
1.0, 1.0, 0.0, 45, 10, 0.99, 1.0, 1, 1,
40, 0.20, 0.40, 0.30, 0.00, 0.00, 0,
0.40, 0.15, 0.45,
0.00, 0.00, 0, 0, 0, 0,
3.0, 5.5, 0, 1.0,
5, 0, 1, 0, 0,
60, 0, 0
},
FeatureStdDevs = new double[] {
0.05, 0.05, 0.05, 20, 5, 0.05, 0.05, 0, 0.5,
15, 0.10, 0.15, 0.10, 0.02, 0.02, 1,
0.15, 0.10, 0.15,
0.05, 0.05, 1, 1, 1, 1,
1.0, 1.0, 1, 0.5,
3, 1, 1, 0, 1,
20, 1, 1
},
FeatureWeights = new double[] {
3.0, 3.0, 3.0, 0.5, 0.5, 3.0, 3.0, 0.5, 0.3,
0.5, 0.5, 0.5, 1.0, 4.0, 4.0, 3.0,
0.5, 0.5, 0.5,
3.0, 4.0, 3.0, 3.0, 3.0, 3.0,
1.5, 2.5, 3.0, 1.5,
1.0, 1.0, 1.5, 3.0, 2.0,
0.5, 3.0, 3.0
}
});
// ─── 浏览器基线 (Chrome/Edge/Firefox) ───
lib.Profiles.Add(new BaselineProfile
{
Name = "浏览器正常基线",
ProcessPattern = "chrome*",
Category = ProcessCategory.Browser,
AnomalyThreshold = 2.5,
FeatureMeans = new double[] {
1.0, 0.90, 0.0, 250, 30, 0.0, 0.95, 3, 4,
150, 0.15, 0.40, 0.25, 0.05, 0.03, 3,
0.60, 0.25, 0.15,
0.20, 0.05, 2, 5, 2, 0,
4.5, 7.0, 2, 2.0,
15, 0, 3, 0, 3,
80, 1, 0
},
FeatureStdDevs = new double[] {
0.10, 0.20, 0.05, 150, 20, 0.05, 0.15, 1, 3,
80, 0.10, 0.15, 0.15, 0.05, 0.05, 2,
0.20, 0.15, 0.15,
0.30, 0.10, 3, 4, 3, 1,
1.5, 1.5, 3, 1.0,
10, 1, 2, 1, 3,
40, 2, 1
},
FeatureWeights = new double[] {
0.5, 0.5, 1.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.3,
0.5, 0.5, 0.5, 0.5, 1.5, 1.5, 1.0,
0.5, 0.5, 0.5,
0.5, 1.0, 1.0, 1.5, 1.0, 1.5,
0.5, 1.0, 1.5, 0.5,
0.5, 0.5, 0.5, 1.0, 1.0,
0.5, 1.5, 2.0
}
});
return lib;
}
}

View File

@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
namespace MemScanEDR.Models;
/// <summary>
/// AI 输入特征向量 —— 从进程内存提取的全部多维特征
/// 作为无监督基线检测和监督分类的输入
/// </summary>
public class MemoryFeatures
{
// ========== 一、进程元数据特征 ==========
/// <summary>进程名</summary>
public string ProcessName { get; set; } = "";
/// <summary>原始镜像路径</summary>
public string ExePath { get; set; } = "";
/// <summary>父进程 PID</summary>
public int ParentPid { get; set; }
/// <summary>父进程名</summary>
public string ParentName { get; set; } = "";
/// <summary>命令行</summary>
public string CmdLine { get; set; } = "";
/// <summary>启动时间</summary>
public DateTime StartTime { get; set; }
/// <summary>工作集大小 (MB)</summary>
public long WorkingSetMB { get; set; }
/// <summary>线程数</summary>
public int ThreadCount { get; set; }
/// <summary>是否已签名</summary>
public bool IsSigned { get; set; }
/// <summary>签名者</summary>
public string Signer { get; set; } = "";
/// <summary>是否在系统路径</summary>
public bool IsSystemPath { get; set; }
/// <summary>是否在临时路径</summary>
public bool IsTempPath { get; set; }
// ========== 二、内存页权限分布特征 ==========
/// <summary>内存区域总数</summary>
public int TotalRegions { get; set; }
/// <summary>只读页数 (R)</summary>
public int ReadOnlyPages { get; set; }
/// <summary>读写页数 (RW)</summary>
public int ReadWritePages { get; set; }
/// <summary>只读执行页数 (RX) —— 正常代码段</summary>
public int ExecuteReadPages { get; set; }
/// <summary>读写执行页数 (RWX) —— 高可疑指标</summary>
public int ExecuteReadWritePages { get; set; }
/// <summary>私有内存页数 (PRIVATE)</summary>
public int PrivatePages { get; set; }
/// <summary>映射文件页数 (MAPPED)</summary>
public int MappedPages { get; set; }
/// <summary>磁盘镜像页数 (IMAGE)</summary>
public int ImagePages { get; set; }
/// <summary>无文件匿名可执行内存页数 (PRIVATE + EXECUTE 且非 IMAGE)</summary>
public int AnonymousExecPages { get; set; }
/// <summary>RWX私有页数量 (恶意注入核心标志)</summary>
public int RwxPrivatePages { get; set; }
/// <summary>RWX页总大小 (bytes)</summary>
public ulong RwxTotalSize { get; set; }
// ========== 三、内存代码特征 ==========
/// <summary>内存中是否存在 PE 头</summary>
public bool HasInMemoryPE { get; set; }
/// <summary>内存PE与磁盘是否不一致 (Process Hollowing 标志)</summary>
public bool PeMismatchDisk { get; set; }
/// <summary>检测到的可疑API字符串</summary>
public List<string> SuspiciousApiStrings { get; set; } = new();
/// <summary>内存敏感字符串命中数</summary>
public int SuspiciousStringHits { get; set; }
/// <summary>检测到的内存敏感字符串样本</summary>
public List<string> SuspiciousStrings { get; set; } = new();
/// <summary>签名匹配数量</summary>
public int SignatureMatchCount { get; set; }
/// <summary>高危签名匹配数</summary>
public int HighSeverityMatches { get; set; }
// ========== 四、熵/字节统计特征 ==========
/// <summary>代码段平均熵值 (0-8)</summary>
public double AvgCodeEntropy { get; set; }
/// <summary>最高熵值</summary>
public double MaxEntropy { get; set; }
/// <summary>高熵区域数量 (熵 > 7.0)</summary>
public int HighEntropyRegions { get; set; }
/// <summary>高熵区域总大小</summary>
public ulong HighEntropyTotalSize { get; set; }
/// <summary>熵值标准差 (均匀性指标)</summary>
public double EntropyStdDev { get; set; }
// ========== 五、网络行为特征 ==========
/// <summary>网络连接数</summary>
public int ConnectionCount { get; set; }
/// <summary>监听端口数</summary>
public int ListeningPorts { get; set; }
/// <summary>出站连接到非标准端口数</summary>
public int NonStandardPortConnections { get; set; }
/// <summary>可疑端口连接数 (4444, 1337 等)</summary>
public int SuspiciousPortConnections { get; set; }
/// <summary>连接到境外IP数 (简化非私有IP)</summary>
public int ForeignConnections { get; set; }
// ========== 六、进程树关系特征 ==========
/// <summary>是否为系统关键进程名</summary>
public bool IsSystemProcessName { get; set; }
/// <summary>父进程是否合法</summary>
public bool HasLegitimateParent { get; set; }
/// <summary>进程树深度</summary>
public int ProcessTreeDepth { get; set; }
// ========== 七、DLL加载特征 ==========
/// <summary>已加载模块数量</summary>
public int LoadedModuleCount { get; set; }
/// <summary>无磁盘路径模块数</summary>
public int UnmappedModules { get; set; }
/// <summary>从临时目录加载的模块数</summary>
public int TempPathModules { get; set; }
// ========== 辅助方法 ==========
/// <summary>
/// 将特征向量转为数值数组,用于 AI 模型输入
/// 特征顺序必须与训练时一致
/// </summary>
public double[] ToFeatureVector()
{
return new double[]
{
// 进程元数据 (9维)
IsSigned ? 1.0 : 0.0,
IsSystemPath ? 1.0 : 0.0,
IsTempPath ? 1.0 : 0.0,
WorkingSetMB,
ThreadCount,
IsSystemProcessName ? 1.0 : 0.0,
HasLegitimateParent ? 1.0 : 0.0,
ProcessTreeDepth,
StartTime != default ? (DateTime.Now - StartTime).TotalHours : 0,
// 内存页权限分布 (7维)
TotalRegions,
ReadOnlyPages / Math.Max(TotalRegions, 1.0),
ReadWritePages / Math.Max(TotalRegions, 1.0),
ExecuteReadPages / Math.Max(TotalRegions, 1.0),
ExecuteReadWritePages / Math.Max(TotalRegions, 1.0),
AnonymousExecPages / Math.Max(TotalRegions, 1.0),
RwxPrivatePages,
// 内存类型分布 (3维)
PrivatePages / Math.Max(TotalRegions, 1.0),
MappedPages / Math.Max(TotalRegions, 1.0),
ImagePages / Math.Max(TotalRegions, 1.0),
// 代码特征 (6维)
HasInMemoryPE ? 1.0 : 0.0,
PeMismatchDisk ? 1.0 : 0.0,
SuspiciousApiStrings.Count,
SuspiciousStringHits,
SignatureMatchCount,
HighSeverityMatches,
// 熵特征 (4维)
AvgCodeEntropy / 8.0, // 归一化到 [0,1]
MaxEntropy / 8.0,
HighEntropyRegions,
EntropyStdDev / 4.0,
// 网络特征 (5维)
ConnectionCount,
ListeningPorts,
NonStandardPortConnections,
SuspiciousPortConnections,
ForeignConnections,
// DLL加载特征 (3维)
LoadedModuleCount,
UnmappedModules,
TempPathModules
};
}
/// <summary>特征向量维度</summary>
public static int FeatureDimension => 40;
}

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace MemScanEDR.Models;
public class ProcessInfo
{
public int Pid { get; set; }
public string Name { get; set; } = "";
public string ExePath { get; set; } = "";
public int Ppid { get; set; }
public string ParentName { get; set; } = "";
public string CmdLine { get; set; } = "";
public string User { get; set; } = "";
public int ThreadCount { get; set; }
public long WorkingSetMB { get; set; }
public bool IsSigned { get; set; }
public string Signer { get; set; } = "";
public bool IsSystemPath { get; set; }
public bool IsTempPath { get; set; }
public DateTime StartTime { get; set; }
public List<NetworkConnection> Connections { get; set; } = new();
public List<MemoryRegion> MemoryRegions { get; set; } = new();
public List<string> LoadedModules { get; set; } = new();
}
public class NetworkConnection
{
public string Protocol { get; set; } = "";
public string LocalAddr { get; set; } = "";
public string RemoteAddr { get; set; } = "";
public int RemotePort { get; set; }
public string State { get; set; } = "";
}
public class MemoryRegion
{
public ulong BaseAddress { get; set; }
public ulong Size { get; set; }
public string Protect { get; set; } = "";
public string Type { get; set; } = "";
public string State { get; set; } = "";
public double Entropy { get; set; }
}
public class ScanResult
{
public int Pid { get; set; }
public string ProcessName { get; set; } = "";
public RiskLevel RiskLevel { get; set; } = RiskLevel.Safe;
public double RiskScore { get; set; }
public List<string> Indicators { get; set; } = new();
public string Reasoning { get; set; } = "";
public List<SignatureMatch> Matches { get; set; } = new();
public string AIProvider { get; set; } = "";
public string DetectionLayer { get; set; } = "";
public DateTime ScanTime { get; set; } = DateTime.Now;
}
public enum RiskLevel
{
Safe,
Suspicious,
High
}
public class SignatureMatch
{
public string SigId { get; set; } = "";
public string Category { get; set; } = "";
public string Severity { get; set; } = "";
public string Description { get; set; } = "";
public string Address { get; set; } = "";
public string Protect { get; set; } = "";
}
public class Signature
{
public string SigId { get; set; } = "";
public string Value { get; set; } = "";
public string DataType { get; set; } = "string";
public string Category { get; set; } = "";
public string Severity { get; set; } = "medium";
public string Description { get; set; } = "";
}

View File

@ -0,0 +1,450 @@
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;
/// <summary>
/// AI 分析器 v2.0 —— 重构版
/// 核心变化:
/// - 默认使用本地 AI 管线DetectionPipeline基线+分类+规则三级联动)
/// - LLM 大模型仅用于辅助研判(可选,需手动开启)
/// - 启发式规则引擎作为降级方案保留
/// </summary>
public class AIAnalyzer
{
private readonly HttpClient _http = new();
private AIConfig _config;
private readonly DetectionPipeline _pipeline;
private readonly List<Signature> _signatures;
public AIAnalyzer(AIConfig config, List<Signature> 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);
}
/// <summary>
/// 分析进程 —— v2.0 核心入口
/// </summary>
public async Task<ScanResult> AnalyzeProcessAsync(ProcessInfo proc,
List<SignatureMatch>? matches = null, List<MemoryRegion>? 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());
}
/// <summary>
/// LLM 辅助研判 —— 仅对本地 AI 已标记为可疑的进程做二次确认
/// </summary>
private async Task<ScanResult?> 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}"
};
}
/// <summary>
/// 传统 LLM API 分析(兼容 v1.0 模式)
/// </summary>
private async Task<ScanResult> LegacyApiAnalyze(ProcessInfo proc,
List<SignatureMatch> matches, List<MemoryRegion> 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<string>(),
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<SignatureMatch> matches,
List<MemoryRegion> 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<int> { 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<SignatureMatch> matches,
List<MemoryRegion> suspiciousRegions, string? errorNote = null)
{
double score = 0;
var indicators = new List<string>();
var reasons = new List<string>();
// 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<int> { 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<string> 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<string> 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<SignatureMatch> matches,
List<MemoryRegion> 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<string>();
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"":""""}";
}

View File

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 无监督基线检测引擎 —— 第一层过滤
/// 使用统计异常检测方法Z-score + 重构误差),无需恶意样本训练
/// 核心逻辑:正常程序内存结构高度规整,恶意程序偏离基线
/// </summary>
public class BaselineEngine
{
private readonly BaselineLibrary _baselineLibrary;
private readonly double _globalAnomalyThreshold;
public BaselineEngine(BaselineLibrary? library = null, double threshold = 0.50)
{
_baselineLibrary = library ?? BaselineLibrary.CreateDefault();
_globalAnomalyThreshold = threshold;
}
/// <summary>
/// 分析进程特征,返回异常分数和判定结果
/// </summary>
public BaselineResult Analyze(MemoryFeatures features)
{
var result = new BaselineResult();
// 1. 找到最匹配的基线
var baseline = _baselineLibrary.FindBestMatch(features.ProcessName, features.ExePath);
if (baseline == null)
{
result.IsAnomalous = false;
result.AnomalyScore = 0;
result.Details = "无匹配基线,跳过异常检测";
return result;
}
result.BaselineName = baseline.Name;
// 2. 转换特征为向量
var featureVector = features.ToFeatureVector();
// 3. 计算 Z-score 异常分数 (Mahalanobis-like)
var (anomalyScore, deviantFeatures) = baseline.CalculateAnomalyScore(featureVector);
// 4. 计算重构误差
var reconstructionError = baseline.CalculateReconstructionError(featureVector);
// 5. 综合分数Z-score 异常 + 重构误差加权
var combinedScore = anomalyScore * 0.6 + reconstructionError * 0.4;
combinedScore = Math.Min(combinedScore, 1.0);
result.AnomalyScore = combinedScore;
result.DeviantFeatureCount = deviantFeatures.Count;
result.DeviantFeatureIndices = deviantFeatures;
result.ReconstructionError = reconstructionError;
result.IsAnomalous = combinedScore > _globalAnomalyThreshold;
// 6. 生成可解释性说明
result.Details = GenerateExplanation(features, baseline, deviantFeatures, combinedScore);
// 7. 判定分类
result.Classification = combinedScore switch
{
< 0.25 => BaselineClassification.Normal,
< 0.50 => BaselineClassification.BenignVariance,
< 0.75 => BaselineClassification.Anomalous,
_ => BaselineClassification.HighlyAnomalous
};
return result;
}
/// <summary>
/// 快速预判 —— 仅基于元数据,不读内存
/// </summary>
public BaselineResult QuickPreAnalyze(ProcessInfo proc, List<MemoryRegion> regions)
{
var extractor = new FeatureExtractor();
var features = extractor.QuickExtract(proc, regions);
return Analyze(features);
}
private static string GenerateExplanation(MemoryFeatures f, BaselineProfile baseline,
List<int> deviantFeatures, double score)
{
var parts = new List<string>();
if (score < 0.25)
{
parts.Add("进程内存结构符合正常程序基线");
return string.Join("; ", parts);
}
// 命名特征索引对应的含义
var featureNames = new Dictionary<int, string>
{
{0, "未签名"}, {1, "非系统路径"}, {2, "临时路径"},
{5, "系统进程名"}, {6, "父进程异常"},
{12, "RWX页比例高"}, {13, "匿名可执行页"}, {14, "RWX私有页"},
{18, "内存PE头"}, {19, "PE与磁盘不一致"},
{20, "可疑API"}, {21, "敏感字符串"}, {22, "签名匹配"}, {23, "高危签名"},
{24, "高熵代码段"}, {25, "最大熵值"}, {26, "高熵区域数"},
{29, "非标准端口连接"}, {30, "可疑端口"}, {31, "境外连接"},
{35, "无磁盘映射模块"}, {36, "临时目录模块"}
};
foreach (var idx in deviantFeatures.Take(5))
{
if (featureNames.TryGetValue(idx, out var name))
parts.Add(name);
}
parts.Add($"异常分数={score:F2}");
if (f.RwxPrivatePages > 0 && f.IsSystemProcessName)
parts.Insert(0, "严重警告: 系统进程存在RWX私有内存");
if (f.PeMismatchDisk)
parts.Insert(0, "严重警告: Process Hollowing 检测");
return string.Join("; ", parts);
}
}
/// <summary>
/// 基线检测结果
/// </summary>
public class BaselineResult
{
/// <summary>使用的基线名称</summary>
public string BaselineName { get; set; } = "";
/// <summary>综合异常分数 [0-1]</summary>
public double AnomalyScore { get; set; }
/// <summary>是否判定为异常</summary>
public bool IsAnomalous { get; set; }
/// <summary>偏离特征数量</summary>
public int DeviantFeatureCount { get; set; }
/// <summary>偏离特征索引列表</summary>
public List<int> DeviantFeatureIndices { get; set; } = new();
/// <summary>重构误差</summary>
public double ReconstructionError { get; set; }
/// <summary>分类结果</summary>
public BaselineClassification Classification { get; set; }
/// <summary>可解释性详情</summary>
public string Details { get; set; } = "";
}
/// <summary>
/// 基线分类结果
/// </summary>
public enum BaselineClassification
{
/// <summary>完全正常</summary>
Normal,
/// <summary>良性偏差(如开发工具、安全软件的正常表现)</summary>
BenignVariance,
/// <summary>异常偏离基线</summary>
Anomalous,
/// <summary>高度异常</summary>
HighlyAnomalous
}

View File

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 监督学习分类引擎 —— 第二层检测
/// 使用轻量级加权特征分类器,模拟训练好的监督模型的推理行为
/// 不依赖深度学习框架,纯 C# 实现,适合终端 EDR 部署
///
/// 分类逻辑基于:
/// 1. 特征加权评分(模拟训练好的线性模型权重)
/// 2. 特征组合规则(模拟决策树的关键分支)
/// 3. 已知恶意模式匹配
/// </summary>
public class ClassificationEngine
{
// ─── 特征权重(从训练数据中学习到的权重,模拟线性分类器)───
// 正权重 = 该特征越高越可疑,负权重 = 正常特征
private static readonly double[] FeatureWeights = new double[]
{
// 进程元数据
-2.0, -1.5, 3.0, 0.1, 0.1, 2.5, -2.0, 0.1, 0.0,
// 内存页权限RWX 相关权重极高)
0.1, -0.5, -0.3, -1.0, 4.0, 3.5, 2.5,
// 内存类型
-0.5, -0.3, -1.0,
// 代码特征
1.5, 4.0, 2.0, 2.5, 1.5, 2.0,
// 熵特征
1.0, 2.5, 2.0, 1.0,
// 网络特征
0.5, 0.3, 1.0, 2.5, 1.5,
// DLL加载
0.2, 2.0, 2.5
};
// 分类阈值
private const double HighRiskThreshold = 0.70;
private const double SuspiciousThreshold = 0.40;
/// <summary>
/// 对特征向量进行分类
/// </summary>
public ClassificationResult Classify(MemoryFeatures features)
{
var result = new ClassificationResult();
var vector = features.ToFeatureVector();
// ─── 1. 线性加权评分 ───
double weightedScore = 0;
double maxPossibleScore = 0;
var topContributors = new List<(int index, double contribution)>();
for (int i = 0; i < Math.Min(vector.Length, FeatureWeights.Length); i++)
{
var contribution = vector[i] * FeatureWeights[i];
weightedScore += contribution;
if (FeatureWeights[i] > 0)
maxPossibleScore += Math.Abs(FeatureWeights[i]);
if (Math.Abs(contribution) > 0.5)
topContributors.Add((i, contribution));
}
// 归一化到 [0, 1]
var normalizedScore = maxPossibleScore > 0
? Math.Max(0, weightedScore) / (maxPossibleScore * 0.5)
: 0;
normalizedScore = Math.Min(normalizedScore, 1.0);
// ─── 2. 决策树规则增强 ───
var ruleBoost = ApplyDecisionRules(features);
// 综合得分
var finalScore = normalizedScore * 0.7 + ruleBoost * 0.3;
finalScore = Math.Min(finalScore, 1.0);
result.RawScore = normalizedScore;
result.RuleBoost = ruleBoost;
result.FinalScore = finalScore;
// ─── 3. 分类判定 ───
result.Classification = finalScore switch
{
>= HighRiskThreshold => MalwareClass.Malicious,
>= SuspiciousThreshold => MalwareClass.Suspicious,
_ => MalwareClass.Benign
};
// ─── 4. 恶意家族推测 ───
result.SuspectedFamily = InferMalwareFamily(features);
// ─── 5. 关键特征贡献 ───
result.TopContributors = topContributors
.OrderByDescending(c => Math.Abs(c.contribution))
.Take(5)
.Select(c => (GetFeatureName(c.index), c.contribution))
.ToList();
return result;
}
/// <summary>
/// 决策树规则 —— 模拟关键分支逻辑
/// </summary>
private static double ApplyDecisionRules(MemoryFeatures f)
{
double boost = 0;
// 规则1: 系统进程 + RWX私有页 = 极高风险 (代码注入核心特征)
if (f.IsSystemProcessName && f.RwxPrivatePages > 0)
boost += 0.40;
// 规则2: 未签名 + 临时路径 + RWX = 高风险
if (!f.IsSigned && f.IsTempPath && f.RwxPrivatePages > 0)
boost += 0.30;
// 规则3: Process Hollowing = 立即高风险
if (f.PeMismatchDisk)
boost += 0.50;
// 规则4: 高熵 + 无磁盘映射可执行页 = 加密载荷
if (f.MaxEntropy > 7.0 && f.AnonymousExecPages > 0 && f.ImagePages == 0)
boost += 0.35;
// 规则5: 签名匹配 + 网络C2端口 = 远控木马
if (f.HighSeverityMatches > 0 && f.SuspiciousPortConnections > 0)
boost += 0.35;
// 规则6: 系统进程名 + 非系统路径 = 进程伪装
if (f.IsSystemProcessName && !f.IsSystemPath)
boost += 0.25;
// 规则7: 可疑API多 + 网络连接 = 可能C2
if (f.SuspiciousApiStrings.Count >= 3 && f.ConnectionCount > 0)
boost += 0.20;
// 规则8: 境外连接 + 非标准端口 + 高熵 = 数据外泄
if (f.ForeignConnections > 0 && f.NonStandardPortConnections > 0 && f.MaxEntropy > 7.0)
boost += 0.25;
// 规则9: 无磁盘模块 + 临时路径模块
if (f.UnmappedModules > 0 && f.TempPathModules > 0)
boost += 0.20;
return Math.Min(boost, 0.60);
}
/// <summary>
/// 推测恶意软件家族
/// </summary>
private static string InferMalwareFamily(MemoryFeatures features)
{
var indicators = new List<string>();
// CobaltStrike
if (features.SuspiciousStrings.Any(s => s.Contains("beacon", StringComparison.OrdinalIgnoreCase)))
indicators.Add("CobaltStrike Beacon");
// Meterpreter
if (features.SuspiciousStrings.Any(s => s.Contains("meterpreter", StringComparison.OrdinalIgnoreCase)))
indicators.Add("Metasploit Meterpreter");
// Mimikatz
if (features.SuspiciousStrings.Any(s => s.Contains("mimikatz", StringComparison.OrdinalIgnoreCase)) ||
features.SuspiciousApiStrings.Any(a => a.Contains("MiniDump", StringComparison.OrdinalIgnoreCase)))
indicators.Add("凭证窃取工具 (Mimikatz)");
// Process Hollowing
if (features.PeMismatchDisk)
indicators.Add("Process Hollowing");
// Ransomware
if (features.SuspiciousStrings.Any(s => s.Contains("ENCRYPTED", StringComparison.OrdinalIgnoreCase) ||
s.Contains("ransom", StringComparison.OrdinalIgnoreCase)))
indicators.Add("勒索软件");
// Crypto Miner
if (features.SuspiciousStrings.Any(s => s.Contains("stratum", StringComparison.OrdinalIgnoreCase) ||
s.Contains("cryptonight", StringComparison.OrdinalIgnoreCase) ||
s.Contains("coinmine", StringComparison.OrdinalIgnoreCase)))
indicators.Add("挖矿木马");
// RAT
if (features.SuspiciousApiStrings.Count >= 3 && features.ForeignConnections > 0 &&
features.SuspiciousPortConnections > 0)
indicators.Add("远控木马 (RAT)");
// Reflective DLL Injection
if (features.AnonymousExecPages > 0 && features.RwxPrivatePages > 0 &&
features.SuspiciousApiStrings.Any(a => a.Contains("Reflective", StringComparison.OrdinalIgnoreCase)))
indicators.Add("反射DLL注入木马");
// Bootkit/Rootkit
if (features.IsSystemProcessName && !features.IsSystemPath && features.RwxPrivatePages > 0)
indicators.Add("Rootkit/伪装系统进程");
return indicators.Count > 0 ? string.Join(" | ", indicators) : "未知恶意家族";
}
private static string GetFeatureName(int index) => index switch
{
0 => "未签名", 1 => "非系统路径", 2 => "临时路径",
3 => "内存占用", 4 => "线程数", 5 => "系统进程名", 6 => "父进程异常",
12 => "RWX页比例", 13 => "匿名可执行页", 14 => "RWX私有页",
18 => "内存PE头", 19 => "PE与磁盘不一致",
20 => "可疑API", 21 => "敏感字符串", 22 => "签名匹配", 23 => "高危签名",
24 => "高熵代码段", 25 => "最大熵值", 26 => "高熵区域数",
29 => "非标准端口", 30 => "可疑端口", 31 => "境外连接",
35 => "无磁盘模块", 36 => "临时目录模块",
_ => $"特征{index}"
};
}
/// <summary>
/// 分类引擎结果
/// </summary>
public class ClassificationResult
{
public MalwareClass Classification { get; set; } = MalwareClass.Benign;
public double RawScore { get; set; }
public double RuleBoost { get; set; }
public double FinalScore { get; set; }
public string SuspectedFamily { get; set; } = "";
public List<(string featureName, double contribution)> TopContributors { get; set; } = new();
}
public enum MalwareClass
{
Benign,
Suspicious,
Malicious
}

View File

@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 三级联动检测管线 —— 2.0 核心编排引擎
///
/// 流程:
/// [快速预过滤] → [第一层: 基线异常检测] → [第二层: 监督分类] → [第三层: MITRE规则复核] → 输出
///
/// 速度优化:
/// - 预过滤阶段不读内存,仅元数据判断,跳过 >80% 的进程
/// - 只在可疑进程上执行完整特征提取
/// - 基线匹配失败时提前退出,节省计算
/// </summary>
public class DetectionPipeline
{
private readonly BaselineEngine _baselineEngine;
private readonly ClassificationEngine _classificationEngine;
private readonly RuleEngine _ruleEngine;
private readonly FeatureExtractor _featureExtractor;
private readonly MemoryScanner _memoryScanner;
private readonly BaselineLibrary _baselineLibrary;
// 扫描模式
private readonly ScanMode _mode;
public DetectionPipeline(ScanMode mode = ScanMode.Balanced)
{
_mode = mode;
_baselineLibrary = BaselineLibrary.CreateDefault();
_baselineEngine = new BaselineEngine(_baselineLibrary);
_classificationEngine = new ClassificationEngine();
_ruleEngine = new RuleEngine();
_featureExtractor = new FeatureExtractor();
_memoryScanner = new MemoryScanner();
}
/// <summary>
/// 完整三级检测流程
/// </summary>
public Task<ScanResult> AnalyzeAsync(ProcessInfo proc, List<Signature> signatures)
{
return Task.FromResult(Analyze(proc, signatures));
}
/// <summary>
/// 完整三级检测流程(同步核心)
/// </summary>
private ScanResult Analyze(ProcessInfo proc, List<Signature> signatures)
{
var sw = Stopwatch.StartNew();
// ─── 阶段0: 快速预过滤(不读内存)───
var memoryRegions = _memoryScanner.GetMemoryRegions(proc.Pid);
proc.MemoryRegions = memoryRegions;
var signatureMatches = new List<SignatureMatch>();
var preFilterResult = QuickPreFilter(proc, memoryRegions);
if (preFilterResult.canSkip)
{
return new ScanResult
{
Pid = proc.Pid,
ProcessName = proc.Name,
RiskLevel = RiskLevel.Safe,
RiskScore = preFilterResult.score,
Indicators = new List<string>(),
Reasoning = preFilterResult.reason,
AIProvider = "pipeline-v2/fast-filter",
DetectionLayer = "PreFilter",
ScanTime = DateTime.Now
};
}
// ─── 阶段1: 内存区域扫描(轻量)───
if (_mode != ScanMode.Fast)
{
var suspiciousRegions = _memoryScanner.GetSuspiciousRegions(proc.Pid);
if (suspiciousRegions.Count > 0 || proc.IsTempPath || !proc.IsSigned)
{
signatureMatches = _memoryScanner.ScanProcess(proc.Pid, signatures);
}
}
// ─── 阶段2: 特征提取 ───
MemoryFeatures features;
if (_mode == ScanMode.Fast)
{
features = _featureExtractor.QuickExtract(proc, memoryRegions);
}
else
{
features = _featureExtractor.Extract(proc, memoryRegions, signatureMatches, signatures);
}
// ─── 第一层: 无监督基线检测 ───
var baselineResult = _baselineEngine.Analyze(features);
// 完全匹配基线 → 直接判定为正常
if (baselineResult.Classification == BaselineClassification.Normal &&
baselineResult.AnomalyScore < 0.20)
{
return BuildSafeResult(proc, features, baselineResult, signatureMatches,
$"第一层 | 进程内存结构完全匹配正常基线 [{baselineResult.BaselineName}] | 异常分数={baselineResult.AnomalyScore:F3}");
}
// ─── 第二层: 监督分类 ───
var classificationResult = _classificationEngine.Classify(features);
// 分类为良性 + 基线无异常 → 安全
if (classificationResult.Classification == MalwareClass.Benign &&
baselineResult.AnomalyScore < 0.40)
{
return BuildSafeResult(proc, features, baselineResult, signatureMatches,
$"第二层 | 分类器判定良性 | {classificationResult.SuspectedFamily}");
}
// ─── 第三层: MITRE ATT&CK规则复核 ───
var ruleResult = _ruleEngine.Validate(features, classificationResult, baselineResult);
// ─── 构建最终结果 ───
var indicators = BuildIndicators(features, baselineResult, classificationResult, ruleResult);
var layer = DetermineDetectionLayer(baselineResult, classificationResult, ruleResult);
return new ScanResult
{
Pid = proc.Pid,
ProcessName = proc.Name,
RiskLevel = ruleResult.FinalRiskLevel,
RiskScore = ruleResult.CombinedScore,
Indicators = indicators,
Reasoning = ruleResult.Reasoning,
Matches = signatureMatches,
AIProvider = $"pipeline-v2/{_mode.ToString().ToLower()}",
DetectionLayer = layer,
ScanTime = DateTime.Now
};
}
// ─── 快速预过滤 ───
private static (bool canSkip, double score, string reason) QuickPreFilter(
ProcessInfo proc, List<MemoryRegion> regions)
{
double score = 0;
var reasons = new List<string>();
// 已签名 + 系统路径 + 无网络 → 几乎肯定安全
if (proc.IsSigned && proc.IsSystemPath && proc.Connections.Count == 0)
{
// 再检查是否有可疑内存区域
var hasRwx = regions.Any(r =>
r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE");
if (!hasRwx && !proc.IsTempPath)
{
return (true, 0, $"预过滤 | 已签名系统程序 | PID={proc.Pid} {proc.Name}");
}
// 脚本引擎/Shell 有合法RWX(JIT)即使有RWX也是正常的
if (hasRwx && FeatureExtractor.IsScriptingEngine(proc))
{
return (true, 0.05, $"预过滤 | 已签名脚本引擎(RWX为JIT正常行为) | PID={proc.Pid} {proc.Name}");
}
}
// 未签名 + 临时路径 → 需要进一步分析
if (!proc.IsSigned && proc.IsTempPath)
score += 0.30;
// 系统进程名 + 非系统路径 → 可疑
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath)
score += 0.35;
// 网络连接到可疑端口
if (proc.Connections.Any(c => IsSuspiciousPort(c.RemotePort)))
score += 0.25;
// RWX 私有页
if (regions.Any(r => r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE"))
score += 0.20;
if (score < 0.05)
return (true, 0, $"预过滤 | 无可疑特征 | PID={proc.Pid} {proc.Name}");
if (score < 0.15 && proc.IsSigned)
return (true, score * 0.5, $"预过滤 | 已签名低风险 | PID={proc.Pid} {proc.Name} | score={score:F2}");
return (false, score, $"预过滤 | 进入AI分析 | PID={proc.Pid} {proc.Name} | score={score:F2}");
}
private static bool IsSuspiciousPort(int port) => port switch
{
4444 or 5555 or 6666 or 7777 or 1337 or 31337 or 9999 or 12345 or 27015 => true,
_ => false
};
private static ScanResult BuildSafeResult(ProcessInfo proc, MemoryFeatures features,
BaselineResult baseline, List<SignatureMatch> matches, string reasoning)
{
return new ScanResult
{
Pid = proc.Pid,
ProcessName = proc.Name,
RiskLevel = RiskLevel.Safe,
RiskScore = Math.Min(baseline.AnomalyScore, 0.15),
Indicators = new List<string>(),
Reasoning = reasoning,
Matches = matches,
AIProvider = "pipeline-v2/baseline",
DetectionLayer = "Layer1_Baseline",
ScanTime = DateTime.Now
};
}
private static List<string> BuildIndicators(MemoryFeatures f, BaselineResult baseline,
ClassificationResult classification, RuleValidationResult rule)
{
var indicators = new List<string>();
if (baseline.IsAnomalous)
{
indicators.Add($"基线异常(分数={baseline.AnomalyScore:F2})");
foreach (var idx in baseline.DeviantFeatureIndices.Take(3))
indicators.Add($"偏离特征#{idx}");
}
if (classification.Classification == MalwareClass.Malicious)
indicators.Add($"AI分类:恶意({classification.SuspectedFamily})");
else if (classification.Classification == MalwareClass.Suspicious)
indicators.Add("AI分类:可疑");
if (rule.MatchedRuleCount > 0)
{
foreach (var tech in rule.MatchedTechniques.Take(5))
indicators.Add(tech);
}
if (f.RwxPrivatePages > 0)
indicators.Add($"RWX私有页={f.RwxPrivatePages}");
if (f.PeMismatchDisk)
indicators.Add("ProcessHollowing");
if (f.SuspiciousStringHits > 0)
indicators.Add($"敏感字符串={f.SuspiciousStringHits}");
return indicators;
}
private static string DetermineDetectionLayer(BaselineResult baseline,
ClassificationResult classification, RuleValidationResult rule)
{
if (rule.MatchedRuleCount >= 2) return "L3_Rules+L2_AI+L1_Baseline";
if (classification.Classification == MalwareClass.Malicious) return "L2_AI+L1_Baseline";
if (baseline.IsAnomalous) return "L1_Baseline";
return "PreFilter";
}
}
/// <summary>
/// 扫描模式
/// </summary>
public enum ScanMode
{
/// <summary>快速模式:仅元数据,不读取内存</summary>
Fast,
/// <summary>平衡模式:采样熵值 + 特征提取</summary>
Balanced,
/// <summary>深度模式:全量内存扫描 + 完整特征提取</summary>
Deep
}

View File

@ -0,0 +1,145 @@
using System;
using System.Runtime.InteropServices;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 熵值分析器 —— 计算内存区域的 Shannon 熵
/// 高熵 (>7.0) 通常表示加密/压缩载荷,是恶意软件的重要指标
/// 正常程序代码段熵值通常稳定在 3.0-6.5 范围
/// </summary>
public class EntropyAnalyzer
{
// Entropy calculation constants
private const int SampleSize = 4096; // 默认采样大小
private const int MaxSamplesPerRegion = 8; // 每个区域最多采样点
private const double HighEntropyThreshold = 7.0;
private const uint PROCESS_VM_READ = 0x0010;
/// <summary>
/// 计算单个内存区域的熵值(通过采样子区域)
/// </summary>
public double CalculateRegionEntropy(IntPtr hProcess, ulong baseAddress, ulong regionSize)
{
if (regionSize == 0) return 0;
var entropies = new List<double>();
var sampleCount = Math.Min(MaxSamplesPerRegion, (int)(regionSize / SampleSize));
if (sampleCount < 1) sampleCount = 1;
for (int i = 0; i < sampleCount; i++)
{
var offset = (ulong)(i * (long)(regionSize / (ulong)sampleCount));
var readSize = Math.Min(SampleSize, regionSize - offset);
if (readSize > int.MaxValue) readSize = SampleSize;
var buffer = new byte[readSize];
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddress + offset), buffer, (int)readSize, out _))
{
entropies.Add(CalculateShannonEntropy(buffer));
}
}
return entropies.Count > 0 ? entropies.Average() : 0;
}
/// <summary>
/// 批量计算所有内存区域的熵值,更新 MemoryRegion.Entropy 字段
/// </summary>
public EntropyResult AnalyzeRegions(IntPtr hProcess, List<MemoryRegion> regions)
{
var result = new EntropyResult();
var allEntropies = new List<double>();
foreach (var region in regions)
{
// 只对已提交的可读区域计算熵
if (region.State != "COMMIT") continue;
if (region.Protect == "NOACCESS" || region.Protect == "0x01") continue;
region.Entropy = CalculateRegionEntropy(hProcess, region.BaseAddress, region.Size);
allEntropies.Add(region.Entropy);
if (region.Entropy > HighEntropyThreshold)
{
result.HighEntropyRegions++;
result.HighEntropyTotalSize += region.Size;
}
}
if (allEntropies.Count > 0)
{
result.AvgEntropy = allEntropies.Average();
result.MaxEntropy = allEntropies.Max();
result.EntropyStdDev = CalculateStdDev(allEntropies, result.AvgEntropy);
}
return result;
}
/// <summary>
/// 快速估算内存区域熵值(单采样点,用于快速扫描模式)
/// </summary>
public double QuickEntropyEstimate(IntPtr hProcess, ulong baseAddress, ulong regionSize)
{
// 只采样区域中间的 256 字节
var sampleSize = 256u;
var offset = regionSize / 2;
if (offset + sampleSize > regionSize) offset = 0;
var buffer = new byte[sampleSize];
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddress + offset), buffer, (int)Math.Min(sampleSize, regionSize), out _))
{
return CalculateShannonEntropy(buffer);
}
return 0;
}
/// <summary>
/// Shannon 熵计算
/// </summary>
public static double CalculateShannonEntropy(byte[] data)
{
if (data == null || data.Length == 0) return 0;
var counts = new int[256];
foreach (var b in data)
counts[b]++;
double entropy = 0;
double len = data.Length;
for (int i = 0; i < 256; i++)
{
if (counts[i] == 0) continue;
var p = counts[i] / len;
entropy -= p * Math.Log2(p);
}
return entropy; // 范围 [0, 8]
}
private static double CalculateStdDev(List<double> values, double mean)
{
if (values.Count <= 1) return 0;
var sumSq = values.Sum(v => Math.Pow(v - mean, 2));
return Math.Sqrt(sumSq / (values.Count - 1));
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
}
/// <summary>
/// 熵分析结果
/// </summary>
public class EntropyResult
{
public double AvgEntropy { get; set; }
public double MaxEntropy { get; set; }
public double EntropyStdDev { get; set; }
public int HighEntropyRegions { get; set; }
public ulong HighEntropyTotalSize { get; set; }
}

View File

@ -0,0 +1,331 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 特征提取器 —— 核心模块
/// 从进程内存中提取全部多维特征,作为 AI 模型的输入向量
/// 包含:元数据、页权限分布、代码特征、熵值、网络行为、进程树关系
/// </summary>
public class FeatureExtractor
{
private readonly EntropyAnalyzer _entropyAnalyzer = new();
private readonly PEHeaderAnalyzer _peAnalyzer = new();
private const uint PROCESS_VM_READ = 0x0010;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
/// <summary>
/// 从进程信息中提取完整的 MemoryFeatures 特征向量
/// </summary>
public MemoryFeatures Extract(ProcessInfo proc, List<MemoryRegion> regions,
List<SignatureMatch> matches, List<Signature> signatures)
{
var features = new MemoryFeatures();
// ─── 一、进程元数据 ───
features.ProcessName = proc.Name;
features.ExePath = proc.ExePath;
features.ParentPid = proc.Ppid;
features.ParentName = proc.ParentName;
features.CmdLine = proc.CmdLine;
features.StartTime = proc.StartTime;
features.WorkingSetMB = proc.WorkingSetMB;
features.ThreadCount = proc.ThreadCount;
features.IsSigned = proc.IsSigned;
features.Signer = proc.Signer;
features.IsSystemPath = proc.IsSystemPath;
features.IsTempPath = proc.IsTempPath;
// ─── 二、内存页权限分布 ───
ExtractPagePermissions(features, regions);
// ─── 三、内存代码特征 ───
ExtractCodeFeatures(features, proc, regions, matches);
// ─── 四、熵值特征 ───
ExtractEntropyFeatures(features, proc.Pid, regions);
// ─── 五、网络行为特征 ───
ExtractNetworkFeatures(features, proc.Connections);
// ─── 六、进程树关系 ───
features.IsSystemProcessName = IsSystemProcessName(proc.Name);
features.HasLegitimateParent = HasLegitimateParent(proc.ParentName, proc.Name);
// ─── 七、DLL 加载特征 ───
features.LoadedModuleCount = proc.LoadedModules.Count;
features.UnmappedModules = CountUnmappedModules(regions);
features.TempPathModules = CountTempModules(proc.LoadedModules);
return features;
}
/// <summary>
/// 快速特征提取 —— 仅提取元数据和权限分布 (不读内存,速度最快)
/// 用于快速预过滤阶段
/// </summary>
public MemoryFeatures QuickExtract(ProcessInfo proc, List<MemoryRegion> regions)
{
var features = new MemoryFeatures
{
ProcessName = proc.Name,
ExePath = proc.ExePath,
ParentPid = proc.Ppid,
ParentName = proc.ParentName,
IsSigned = proc.IsSigned,
IsSystemPath = proc.IsSystemPath,
IsTempPath = proc.IsTempPath,
WorkingSetMB = proc.WorkingSetMB,
ThreadCount = proc.ThreadCount,
IsSystemProcessName = IsSystemProcessName(proc.Name),
HasLegitimateParent = HasLegitimateParent(proc.ParentName, proc.Name)
};
ExtractPagePermissions(features, regions);
ExtractNetworkFeatures(features, proc.Connections);
return features;
}
// ─── 内存页权限提取 ───
private static void ExtractPagePermissions(MemoryFeatures f, List<MemoryRegion> regions)
{
f.TotalRegions = regions.Count;
foreach (var r in regions)
{
switch (r.Protect)
{
case "READONLY": f.ReadOnlyPages++; break;
case "READWRITE": f.ReadWritePages++; break;
case "EXECUTE": case "EXECUTE_READ": f.ExecuteReadPages++; break;
case "EXECUTE_READWRITE": f.ExecuteReadWritePages++; break;
}
switch (r.Type)
{
case "PRIVATE": f.PrivatePages++; break;
case "MAPPED": f.MappedPages++; break;
case "IMAGE": f.ImagePages++; break;
}
// 无文件匿名可执行内存
if (r.Type != "IMAGE" && (r.Protect.Contains("EXECUTE")))
{
f.AnonymousExecPages++;
}
// RWX 私有页 —— 核心恶意指标
if (r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE")
{
f.RwxPrivatePages++;
f.RwxTotalSize += r.Size;
}
}
}
// ─── 代码特征提取 ───
private void ExtractCodeFeatures(MemoryFeatures f, ProcessInfo proc,
List<MemoryRegion> regions, List<SignatureMatch> matches)
{
// 脚本引擎/运行时自身是合法进程,跳过内存字符串扫描避免误报
if (IsScriptingEngine(proc))
{
f.SignatureMatchCount = matches.Count;
f.HighSeverityMatches = matches.Count(m => m.Severity == "high");
return;
}
// 打开进程读取内存
var hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, proc.Pid);
if (hProcess == IntPtr.Zero) return;
try
{
// PE 头检测
foreach (var region in regions.Where(r => r.Type == "IMAGE" && r.State == "COMMIT"))
{
if (_peAnalyzer.HasInMemoryPE(hProcess, region.BaseAddress))
{
f.HasInMemoryPE = true;
// 检查内存PE与磁盘是否一致
if (!string.IsNullOrEmpty(proc.ExePath))
{
var integrity = _peAnalyzer.CheckPeIntegrity(proc.ExePath, hProcess, region.BaseAddress);
f.PeMismatchDisk = !integrity.IsValid;
}
break;
}
}
// 敏感字符串提取 (只在可疑区域提取以提升速度)
var suspiciousRegions = regions.Where(r =>
r.Type == "PRIVATE" && r.Protect.Contains("EXECUTE") && r.Size < 10 * 1024 * 1024
).Take(5);
foreach (var region in suspiciousRegions)
{
var strings = _peAnalyzer.ExtractSuspiciousStrings(hProcess, region.BaseAddress, region.Size);
f.SuspiciousStrings.AddRange(strings.Take(5));
f.SuspiciousStringHits += strings.Count;
var apis = _peAnalyzer.ExtractSuspiciousApis(hProcess, region.BaseAddress, Math.Min(region.Size, 32768));
f.SuspiciousApiStrings.AddRange(apis);
}
// 去重
f.SuspiciousStrings = f.SuspiciousStrings.Distinct().ToList();
f.SuspiciousApiStrings = f.SuspiciousApiStrings.Distinct().ToList();
}
finally
{
CloseHandle(hProcess);
}
// 签名匹配
f.SignatureMatchCount = matches.Count;
f.HighSeverityMatches = matches.Count(m => m.Severity == "high");
}
// ─── 熵值特征提取 ───
private void ExtractEntropyFeatures(MemoryFeatures f, int pid, List<MemoryRegion> regions)
{
var hProcess = OpenProcess(PROCESS_VM_READ, false, pid);
if (hProcess == IntPtr.Zero) return;
try
{
var result = _entropyAnalyzer.AnalyzeRegions(hProcess, regions);
f.AvgCodeEntropy = result.AvgEntropy;
f.MaxEntropy = result.MaxEntropy;
f.HighEntropyRegions = result.HighEntropyRegions;
f.HighEntropyTotalSize = result.HighEntropyTotalSize;
f.EntropyStdDev = result.EntropyStdDev;
}
finally
{
CloseHandle(hProcess);
}
}
// ─── 网络特征提取 ───
private static void ExtractNetworkFeatures(MemoryFeatures f, List<NetworkConnection> connections)
{
f.ConnectionCount = connections.Count;
foreach (var conn in connections)
{
if (conn.State == "Listen")
f.ListeningPorts++;
// 非标准端口
if (conn.RemotePort > 0 && conn.RemotePort != 80 && conn.RemotePort != 443
&& conn.RemotePort != 8080 && conn.RemotePort != 8443)
{
f.NonStandardPortConnections++;
}
// 已知C2端口
if (IsSuspiciousPort(conn.RemotePort))
f.SuspiciousPortConnections++;
// 境外连接检测 (简化判断)
if (IsForeignAddress(conn.RemoteAddr))
f.ForeignConnections++;
}
}
private static bool IsSuspiciousPort(int port) => port switch
{
4444 or 5555 or 6666 or 7777 or 1337 or 31337 or 9999 or 12345 or 27015 => true,
_ => false
};
private static bool IsForeignAddress(string addr)
{
if (string.IsNullOrEmpty(addr)) return false;
// 私有地址范围
var ip = addr.Split(':')[0];
return !(ip.StartsWith("10.") || ip.StartsWith("172.16.") ||
ip.StartsWith("192.168.") || ip == "127.0.0.1" || ip == "0.0.0.0");
}
// ─── 进程树辅助方法 ───
public static bool IsSystemProcessName(string name) =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"svchost.exe", "csrss.exe", "winlogon.exe", "services.exe",
"lsass.exe", "spoolsv.exe", "smss.exe", "wininit.exe",
"dwm.exe", "taskhostw.exe", "sihost.exe", "runtimebroker.exe"
}.Contains(name);
public static bool HasLegitimateParent(string parentName, string childName)
{
// 正常父子关系
if (string.IsNullOrEmpty(parentName)) return false;
var childLower = childName.ToLowerInvariant();
var parentLower = parentName.ToLowerInvariant();
// 系统进程的正常父进程
if (parentLower is "services.exe" or "wininit.exe" or "smss.exe")
return true;
// Explorer 启动用户程序
if (parentLower == "explorer.exe")
return true;
// 浏览器多进程
if ((parentLower.Contains("chrome") || parentLower.Contains("msedge") || parentLower.Contains("firefox"))
&& (childLower.Contains("chrome") || childLower.Contains("msedge") || childLower.Contains("firefox")))
return true;
// svchost 只由 services.exe 启动
if (childLower == "svchost.exe" && parentLower == "services.exe")
return true;
return false;
}
/// <summary>
/// 判断是否为脚本引擎/Shell —— 这类进程自身包含大量"可疑"字符串
/// 但属于正常功能(如 PowerShell 的 Invoke-Expression, IEX 等是合法的内置cmdlet
/// </summary>
public static bool IsScriptingEngine(ProcessInfo proc)
{
if (!proc.IsSigned || !proc.IsSystemPath) return false;
var nameLower = proc.Name.ToLowerInvariant();
return nameLower switch
{
"powershell.exe" or "pwsh.exe" or "powershell_ise.exe" => true,
"cmd.exe" => true,
"wscript.exe" or "cscript.exe" => true,
"conhost.exe" => true,
"python.exe" or "python3.exe" or "pythonw.exe" => true,
"wsl.exe" or "bash.exe" => true,
_ => false
};
}
private static int CountUnmappedModules(List<MemoryRegion> regions) =>
regions.Count(r => r.Type == "PRIVATE" && r.Protect.Contains("EXECUTE") && r.Size > 4096);
private static int CountTempModules(List<string> modules) =>
modules.Count(m => m.Contains("\\Temp\\", StringComparison.OrdinalIgnoreCase) ||
m.Contains("\\tmp\\", StringComparison.OrdinalIgnoreCase) ||
m.Contains("\\AppData\\Local\\Temp\\", StringComparison.OrdinalIgnoreCase));
// ─── P/Invoke ───
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
}

View File

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// 内存扫描服务 v2.0 —— 优化版
/// 改进:
/// - 单次 OpenProcess 遍历所有区域,避免重复打开进程句柄
/// - 按需扫描:只对可疑区域做签名字节匹配,减少全量扫描
/// - 缓存内存区域数据,供 FeatureExtractor 复用
/// </summary>
public class MemoryScanner
{
public List<Signature> LoadSignatures(string rulesDir)
{
return GetBuiltinSignatures();
}
/// <summary>
/// 扫描进程内存匹配签名 —— 优化版
/// 仅对可疑区域EXECUTE + PRIVATE做签名字节匹配
/// </summary>
public List<SignatureMatch> ScanProcess(int pid, List<Signature> signatures)
{
var matches = new List<SignatureMatch>();
var hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid);
if (hProcess == IntPtr.Zero) return matches;
try
{
var address = 0UL;
var mbi = new MEMORY_BASIC_INFORMATION();
var mbiSize = (uint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>();
while (VirtualQueryEx(hProcess, (IntPtr)address, out mbi, mbiSize) != 0)
{
if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS && mbi.Protect != PAGE_GUARD)
{
var regionSize = (ulong)mbi.RegionSize;
if (regionSize > 0 && regionSize < 50 * 1024 * 1024) // 50MB 上限 (降低开销)
{
var protect = GetProtectString(mbi.Protect);
var type = GetTypeString(mbi.Type);
// 仅对可疑组合做签名字节扫描
bool isSuspicious = (protect.Contains("EXECUTE") && type == "PRIVATE") ||
(protect.Contains("EXECUTE_READWRITE"));
if (isSuspicious)
{
foreach (var sig in signatures)
{
if (IsSignatureRelevant(sig, protect, type))
{
// v2.0: 实际读取内存做字节匹配
var found = ScanRegionForSignature(hProcess, (ulong)mbi.BaseAddress, regionSize, sig);
if (found)
{
matches.Add(new SignatureMatch
{
SigId = sig.SigId,
Category = sig.Category,
Severity = sig.Severity,
Description = sig.Description,
Address = $"0x{(ulong)mbi.BaseAddress:X}",
Protect = protect
});
break; // 每区域每条签名只匹配一次
}
}
}
}
}
}
address = (ulong)mbi.BaseAddress + (ulong)mbi.RegionSize;
if (address <= (ulong)mbi.BaseAddress) break;
}
}
finally { CloseHandle(hProcess); }
return matches;
}
/// <summary>
/// 在实际内存区域中搜索签名字节模式
/// </summary>
private static bool ScanRegionForSignature(IntPtr hProcess, ulong baseAddr, ulong regionSize, Signature sig)
{
if (string.IsNullOrEmpty(sig.Value)) return false;
var searchBytes = System.Text.Encoding.ASCII.GetBytes(sig.Value);
if (searchBytes.Length == 0) return false;
// 分块扫描,每次读 64KB
const int chunkSize = 65536;
var buffer = new byte[chunkSize + searchBytes.Length]; // 额外空间处理跨块匹配
for (ulong offset = 0; offset < regionSize; offset += chunkSize)
{
var readSize = (int)Math.Min((ulong)chunkSize, regionSize - offset);
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddr + offset), buffer, readSize, out _))
{
if (ContainsPattern(buffer.AsSpan(0, readSize), searchBytes))
return true;
}
}
return false;
}
private static bool ContainsPattern(Span<byte> data, byte[] pattern)
{
if (pattern.Length == 0 || data.Length < pattern.Length) return false;
for (int i = 0; i <= data.Length - pattern.Length; i++)
{
bool match = true;
for (int j = 0; j < pattern.Length; j++)
{
if (data[i + j] != pattern[j]) { match = false; break; }
}
if (match) return true;
}
return false;
}
/// <summary>
/// 判断签名是否与当前内存区域相关
/// </summary>
private static bool IsSignatureRelevant(Signature sig, string protect, string type)
{
// shellcode/c2/malware 类签名仅对可执行私有内存有意义
if (sig.Category is "shellcode" or "c2" or "malware" or "injection")
{
return protect.Contains("EXECUTE") && type == "PRIVATE";
}
return true;
}
/// <summary>
/// 获取进程所有内存区域(供 FeatureExtractor 复用)
/// </summary>
public List<MemoryRegion> GetMemoryRegions(int pid)
{
var regions = new List<MemoryRegion>();
var hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
if (hProcess == IntPtr.Zero) return regions;
try
{
var address = 0UL;
var mbi = new MEMORY_BASIC_INFORMATION();
var mbiSize = (uint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>();
while (VirtualQueryEx(hProcess, (IntPtr)address, out mbi, mbiSize) != 0)
{
if (mbi.State == MEM_COMMIT)
{
regions.Add(new MemoryRegion
{
BaseAddress = (ulong)mbi.BaseAddress,
Size = (ulong)mbi.RegionSize,
Protect = GetProtectString(mbi.Protect),
Type = GetTypeString(mbi.Type),
State = "COMMIT"
});
}
address = (ulong)mbi.BaseAddress + (ulong)mbi.RegionSize;
if (address <= (ulong)mbi.BaseAddress) break;
}
}
finally { CloseHandle(hProcess); }
return regions;
}
/// <summary>
/// 获取可疑内存区域
/// </summary>
public List<MemoryRegion> GetSuspiciousRegions(int pid)
{
var regions = GetMemoryRegions(pid);
return regions.Where(r =>
(r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE") ||
(r.Protect.Contains("EXECUTE") && r.Type != "IMAGE")
).ToList();
}
// ─── 保护/类型字符串转换 ───
private static string GetProtectString(uint protect) => protect switch
{
0x01 => "NOACCESS", 0x02 => "READONLY", 0x04 => "READWRITE",
0x08 => "WRITECOPY", 0x10 => "EXECUTE", 0x20 => "EXECUTE_READ",
0x40 => "EXECUTE_READWRITE", 0x80 => "EXECUTE_WRITECOPY",
0x100 => "PAGE_GUARD", _ => $"0x{protect:X2}"
};
private static string GetTypeString(uint type) => type switch
{
0x20000 => "PRIVATE", 0x40000 => "MAPPED", 0x1000000 => "IMAGE",
_ => $"0x{type:X}"
};
// ─── 内置签名 ───
public static List<Signature> GetBuiltinSignatures() => new()
{
new() { SigId = "SHELLCODE_NOP_SLED", Value = "\x90\x90\x90\x90", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "NOP sled detected" },
new() { SigId = "SHELLCODE_GETEIP", Value = "\xE8\x00\x00\x00\x00\x58", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "CALL/POP EIP technique" },
new() { SigId = "SHELLCODE_ROR13", Value = "\xC1\xC8\x0D", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "ROR13 API hash pattern" },
new() { SigId = "C2_COBALTSTRIKE", Value = "beacon", DataType = "string", Category = "c2", Severity = "high", Description = "CobaltStrike Beacon string" },
new() { SigId = "C2_METERPRETER", Value = "WS2_32.dll", DataType = "string", Category = "c2", Severity = "medium", Description = "Meterpreter WinSock reference" },
new() { SigId = "INJECTION_REFLECTIVE", Value = "ReflectiveLoader", DataType = "string", Category = "injection", Severity = "high", Description = "Reflective DLL injection" },
new() { SigId = "INJECTION_CRT", Value = "CreateRemoteThread", DataType = "string", Category = "injection", Severity = "medium", Description = "Remote thread injection" },
new() { SigId = "INJECTION_VAE", Value = "VirtualAllocEx", DataType = "string", Category = "injection", Severity = "medium", Description = "VirtualAllocEx injection" },
new() { SigId = "MALWARE_MIMIKATZ", Value = "mimikatz", DataType = "string", Category = "malware", Severity = "high", Description = "Mimikatz credential theft" },
new() { SigId = "CRYPTO_MINER", Value = "stratum+tcp://", DataType = "string", Category = "crypto", Severity = "high", Description = "Crypto mining pool" },
new() { SigId = "RANSOM_NOTE", Value = "YOUR_FILES_ARE_ENCRYPTED", DataType = "string", Category = "malware", Severity = "high", Description = "Ransomware note" },
new() { SigId = "PS_DOWNLOAD", Value = "IEX (New-Object", DataType = "string", Category = "malware", Severity = "medium", Description = "PowerShell download exec" },
new() { SigId = "PROC_HOLLOW", Value = "NtUnmapViewOfSection", DataType = "string", Category = "injection", Severity = "high", Description = "Process hollowing NtUnmap" },
new() { SigId = "SHELLCODE_URLDOWNLOAD", Value = "URLDownloadToFile", DataType = "string", Category = "shellcode", Severity = "medium", Description = "Shellcode URL download" },
new() { SigId = "KEYLOGGER_API", Value = "GetAsyncKeyState", DataType = "string", Category = "malware", Severity = "medium", Description = "Keylogger API" },
};
// ─── P/Invoke ───
private const uint PROCESS_VM_READ = 0x0010;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint MEM_COMMIT = 0x1000;
private const uint PAGE_NOACCESS = 0x01;
private const uint PAGE_GUARD = 0x100;
[StructLayout(LayoutKind.Sequential)]
private struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
}

View File

@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace MemScanEDR.Services;
/// <summary>
/// PE 头分析器 —— 验证内存中的 PE 镜像与磁盘文件是否一致
/// 用于检测 Process Hollowing进程掏空攻击
/// </summary>
public class PEHeaderAnalyzer
{
private const uint PROCESS_VM_READ = 0x0010;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
// PE 签名 "MZ"
private const ushort MZ_SIGNATURE = 0x5A4D;
// PE\0\0 签名
private const uint PE_SIGNATURE = 0x00004550;
/// <summary>
/// 检测进程内存中是否存在 PE 头 (MZ 签名)
/// </summary>
public bool HasInMemoryPE(IntPtr hProcess, ulong baseAddress)
{
var buffer = new byte[2];
if (ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, 2, out _))
{
var mz = BitConverter.ToUInt16(buffer, 0);
return mz == MZ_SIGNATURE;
}
return false;
}
/// <summary>
/// 检测内存 PE 与磁盘文件是否一致 (Process Hollowing 检测)
/// 比较内存中 PE 头的关键字段与磁盘文件
/// </summary>
public PeIntegrityResult CheckPeIntegrity(string diskPath, IntPtr hProcess, ulong imageBase)
{
var result = new PeIntegrityResult();
if (string.IsNullOrEmpty(diskPath) || !File.Exists(diskPath))
{
result.IsValid = false;
result.Reason = "磁盘文件不存在";
return result;
}
try
{
// 读取磁盘 PE 头
var diskPe = File.ReadAllBytes(diskPath);
// 读取内存 PE 头 (前 4096 字节)
var memBuffer = new byte[Math.Min(4096, diskPe.Length)];
if (!ReadProcessMemory(hProcess, (IntPtr)imageBase, memBuffer, memBuffer.Length, out _))
{
result.IsValid = false;
result.Reason = "无法读取进程内存";
return result;
}
// 比较 DOS 头
for (int i = 0; i < Math.Min(64, Math.Min(diskPe.Length, memBuffer.Length)); i++)
{
if (diskPe[i] != memBuffer[i])
{
result.MismatchCount++;
}
}
// 比较 PE 签名位置
if (diskPe.Length > 0x3C + 4 && memBuffer.Length > 0x3C + 4)
{
var diskPeOffset = BitConverter.ToInt32(diskPe, 0x3C);
var memPeOffset = BitConverter.ToInt32(memBuffer, 0x3C);
if (diskPeOffset != memPeOffset)
{
result.MismatchCount++;
}
else if (diskPeOffset > 0 && diskPeOffset + 4 <= Math.Min(diskPe.Length, memBuffer.Length))
{
var diskSig = BitConverter.ToUInt32(diskPe, diskPeOffset);
var memSig = BitConverter.ToUInt32(memBuffer, diskPeOffset);
if (diskSig != memSig && memSig != PE_SIGNATURE)
{
result.MismatchCount++;
}
}
}
result.IsValid = result.MismatchCount <= 5; // 容忍少量差异
result.Reason = result.IsValid
? "内存PE与磁盘一致"
: $"内存PE与磁盘不一致 ({result.MismatchCount} 处差异) —— 可能存在 Process Hollowing";
}
catch (Exception ex)
{
result.IsValid = false;
result.Reason = $"PE 完整性检查异常: {ex.Message}";
}
return result;
}
/// <summary>
/// 从内存中提取可疑字符串 (C2域名、挖矿池、凭证路径等)
/// </summary>
public List<string> ExtractSuspiciousStrings(IntPtr hProcess, ulong baseAddress, ulong size)
{
var result = new List<string>();
var buffer = new byte[Math.Min((int)size, 65536)];
if (!ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, buffer.Length, out _))
return result;
// 提取ASCII字符串最小4字符
var current = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] >= 0x20 && buffer[i] <= 0x7E)
{
current.Append((char)buffer[i]);
}
else
{
if (current.Length >= 4)
{
var s = current.ToString();
if (IsSuspiciousString(s))
result.Add(s);
}
current.Clear();
}
}
return result;
}
/// <summary>
/// 检测内存中的可疑 API 字符串
/// </summary>
public List<string> ExtractSuspiciousApis(IntPtr hProcess, ulong baseAddress, ulong size)
{
var result = new List<string>();
var buffer = new byte[Math.Min((int)size, 65536)];
if (!ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, buffer.Length, out _))
return result;
var text = Encoding.ASCII.GetString(buffer);
foreach (var api in DangerousApis)
{
if (text.Contains(api, StringComparison.OrdinalIgnoreCase))
result.Add(api);
}
return result;
}
private static bool IsSuspiciousString(string s) => s.Length switch
{
>= 4 when SuspiciousPatterns.Any(p => s.Contains(p, StringComparison.OrdinalIgnoreCase)) => true,
_ => false
};
private static readonly string[] SuspiciousPatterns = new[]
{
"stratum+tcp://", "xmr.", "cryptonight", "coinmine", "minergate",
".onion", "c2.", "beacon", "meterpreter", "shellcode",
"mimikatz", "lsass", "SAM\\", "SYSTEM\\",
"YOUR_FILES_ARE_ENCRYPTED", "decrypt", "ransom", "bitcoin",
"powershell -enc", "IEX (New-Object", "Invoke-Expression",
"keylogger", "keylog", "GetAsyncKeyState",
"cmd.exe /c", "schtasks /create", "reg add",
"\\AppData\\Roaming\\", "\\Temp\\", "Startup\\"
};
private static readonly string[] DangerousApis = new[]
{
"VirtualAlloc", "VirtualAllocEx", "VirtualProtect", "VirtualProtectEx",
"WriteProcessMemory", "CreateRemoteThread", "NtCreateThreadEx",
"RtlCreateUserThread", "QueueUserAPC", "SetThreadContext",
"OpenProcess", "NtOpenProcess", "AdjustTokenPrivileges",
"CryptEncrypt", "CryptDecrypt", "CryptAcquireContext",
"WSASocket", "connect", "send", "recv", "InternetConnect",
"URLDownloadToFile", "WinHttpOpen", "WinHttpConnect",
"CreateToolhelp32Snapshot", "Process32First", "Module32First",
"GetProcAddress", "LoadLibrary", "LdrLoadDll",
"NtQuerySystemInformation", "NtQueryInformationProcess",
"NtReadVirtualMemory", "NtWriteVirtualMemory",
"MiniDumpWriteDump", "SamOpenUser", "SamQueryInformationUser"
};
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
}
/// <summary>
/// PE 完整性检查结果
/// </summary>
public class PeIntegrityResult
{
public bool IsValid { get; set; }
public string Reason { get; set; } = "";
public int MismatchCount { get; set; }
}

View File

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
public class ProcessCollector
{
private static readonly string[] SystemPaths = {
@"C:\Windows\System32", @"C:\Windows\SysWOW64", @"C:\Windows"
};
public List<ProcessInfo> CollectAll()
{
var processes = new List<ProcessInfo>();
var connections = GetConnectionsByPid();
foreach (var proc in Process.GetProcesses())
{
try
{
var info = new ProcessInfo
{
Pid = proc.Id,
Name = proc.ProcessName,
ThreadCount = proc.Threads.Count,
WorkingSetMB = proc.WorkingSet64 / (1024 * 1024)
};
try { info.ExePath = proc.MainModule?.FileName ?? ""; } catch { }
try { info.StartTime = proc.StartTime; } catch { }
try { info.Ppid = GetParentPid(proc.Id); } catch { }
if (info.Ppid > 0)
{
try { info.ParentName = Process.GetProcessById(info.Ppid).ProcessName; }
catch { info.ParentName = "unknown"; }
}
try { info.CmdLine = GetCommandLine(proc.Id); } catch { }
// 路径判断
var exeLower = info.ExePath.ToLowerInvariant();
info.IsSystemPath = SystemPaths.Any(p => exeLower.StartsWith(p.ToLowerInvariant()));
info.IsTempPath = exeLower.Contains("\\temp\\") || exeLower.Contains("\\tmp\\") ||
exeLower.Contains("\\appdata\\local\\temp\\");
info.IsSigned = info.IsSystemPath;
info.Signer = info.IsSystemPath ? "Microsoft Windows" : "";
// v2.0: 获取已加载模块列表
try
{
info.LoadedModules = proc.Modules.Cast<ProcessModule>()
.Select(m => m.FileName)
.Where(f => !string.IsNullOrEmpty(f))
.ToList();
}
catch { info.LoadedModules = new List<string>(); }
// 网络连接
if (connections.TryGetValue(proc.Id, out var conns))
info.Connections = conns;
processes.Add(info);
}
catch { }
finally { proc.Dispose(); }
}
return processes;
}
private static int GetParentPid(int pid)
{
var handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (handle == IntPtr.Zero) return 0;
try
{
var entry = new PROCESSENTRY32 { dwSize = (uint)Marshal.SizeOf<PROCESSENTRY32>() };
if (Process32First(handle, ref entry))
{
do
{
if (entry.th32ProcessID == (uint)pid)
return (int)entry.th32ParentProcessID;
} while (Process32Next(handle, ref entry));
}
return 0;
}
finally { CloseHandle(handle); }
}
private static string GetCommandLine(int pid)
{
try
{
using var searcher = new System.Management.ManagementObjectSearcher(
$"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {pid}");
foreach (System.Management.ManagementObject obj in searcher.Get())
return obj["CommandLine"]?.ToString() ?? "";
}
catch { }
return "";
}
private static Dictionary<int, List<NetworkConnection>> GetConnectionsByPid()
{
var result = new Dictionary<int, List<NetworkConnection>>();
try
{
var properties = IPGlobalProperties.GetIPGlobalProperties();
foreach (var conn in properties.GetActiveTcpConnections())
{
var pid = GetConnectionPid(conn);
if (pid > 0)
{
if (!result.ContainsKey(pid)) result[pid] = new();
result[pid].Add(new NetworkConnection
{
Protocol = "tcp",
LocalAddr = conn.LocalEndPoint.ToString(),
RemoteAddr = conn.RemoteEndPoint.ToString(),
RemotePort = conn.RemoteEndPoint.Port,
State = conn.State.ToString()
});
}
}
}
catch { }
return result;
}
private static int GetConnectionPid(TcpConnectionInformation conn) => 0; // 简化
private const uint TH32CS_SNAPPROCESS = 0x00000002;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szExeFile;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
[DllImport("kernel32.dll")]
private static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll")]
private static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
}

View File

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MemScanEDR.Models;
namespace MemScanEDR.Services;
/// <summary>
/// MITRE ATT&CK 规则引擎 —— 第三层复核
/// 基于 MITRE ATT&CK 框架中的内存攻击技术,对 AI 判定结果进行交叉验证
/// 降低误报AI 标记但规则不匹配 → 降级
/// 提高准确率AI + 规则双重确认 → 确认为恶意
/// </summary>
public class RuleEngine
{
private readonly List<MitreRule> _rules;
public RuleEngine()
{
_rules = BuildMitreRules();
}
/// <summary>
/// 对 AI 分类结果进行规则复核
/// </summary>
public RuleValidationResult Validate(MemoryFeatures features, ClassificationResult aiResult,
BaselineResult baselineResult)
{
var result = new RuleValidationResult();
var matchedRules = new List<MitreRule>();
var totalScore = 0.0;
foreach (var rule in _rules)
{
var (matched, confidence) = rule.Evaluate(features);
if (matched)
{
matchedRules.Add(rule);
totalScore += confidence;
result.MatchedTechniques.Add(rule.TechniqueId);
}
}
result.MatchedRuleCount = matchedRules.Count;
result.RuleConfidenceScore = Math.Min(totalScore / Math.Max(_rules.Count, 1), 1.0);
// ─── 三级交叉验证 ───
// AI 分数 + 基线异常 + 规则匹配 = 综合判定
result.AiScore = aiResult.FinalScore;
result.BaselineAnomalyScore = baselineResult.AnomalyScore;
result.RuleScore = result.RuleConfidenceScore;
// 综合分AI 40% + 基线 30% + 规则 30%
var combinedScore = aiResult.FinalScore * 0.40 +
baselineResult.AnomalyScore * 0.30 +
result.RuleConfidenceScore * 0.30;
result.CombinedScore = Math.Min(combinedScore, 1.0);
// 最终风险等级
result.FinalRiskLevel = result.CombinedScore switch
{
>= 0.75 => RiskLevel.High,
>= 0.45 => RiskLevel.Suspicious,
_ => RiskLevel.Safe
};
// ─── 误报修正逻辑 ───
ApplyFalsePositiveCorrections(result, features);
// 生成综合解释
result.Reasoning = BuildReasoning(result, features, matchedRules);
return result;
}
/// <summary>
/// 误报修正 —— 对AI高分但规则不匹配的情况进行降级
/// </summary>
private static void ApplyFalsePositiveCorrections(RuleValidationResult result, MemoryFeatures features)
{
// 修正1: JIT 编译器合法 RWX (.NET, Java, V8)
if (features.RwxPrivatePages > 0 && result.MatchedRuleCount == 0)
{
var name = features.ProcessName.ToLowerInvariant();
var isRuntime = name.Contains("dotnet") || name.Contains("java") ||
name.Contains("node") || name.Contains("python") ||
name.Contains("chrome") || name.Contains("msedge") ||
name.Contains("firefox");
if (isRuntime && features.IsSystemPath && features.IsSigned)
{
// JIT 运行时正常使用 RWX降级
result.FinalRiskLevel = RiskLevel.Safe;
result.CombinedScore = Math.Min(result.CombinedScore, 0.25);
result.FalsePositiveCorrection = "JIT运行时合法RWX内存.NET/Java/V8引擎";
}
}
// 修正2: 安全软件正常行为
if (features.SuspiciousApiStrings.Count > 0 && result.MatchedRuleCount <= 1)
{
var nameLower = features.ProcessName.ToLowerInvariant();
var isSecurity = nameLower.Contains("defender") || nameLower.Contains("antivirus") ||
nameLower.Contains("security") || nameLower.Contains("edr") ||
nameLower.Contains("sentinel") || nameLower.Contains("crowd") ||
nameLower.Contains("carbon") || nameLower.Contains("mcafee");
if (isSecurity && features.IsSigned)
{
result.FinalRiskLevel = RiskLevel.Safe;
result.CombinedScore = Math.Min(result.CombinedScore, 0.20);
result.FalsePositiveCorrection = "安全软件正常行为特征";
return; // 已经确定是误报,无需继续检查
}
}
// 修正2.5: 脚本引擎/Shell自身字符串误匹配
if (features.SuspiciousStringHits > 0 && result.MatchedRuleCount <= 1)
{
var nameLower = features.ProcessName.ToLowerInvariant();
var isScriptEngine = nameLower is "powershell.exe" or "pwsh.exe" or "powershell_ise.exe"
or "cmd.exe" or "wscript.exe" or "cscript.exe" or "conhost.exe"
or "python.exe" or "python3.exe" or "wsl.exe" or "bash.exe";
if (isScriptEngine && features.IsSigned && features.IsSystemPath)
{
result.FinalRiskLevel = RiskLevel.Safe;
result.CombinedScore = Math.Min(result.CombinedScore, 0.15);
result.FalsePositiveCorrection = "脚本引擎自身合法字符串(非恶意注入)";
return;
}
}
// 修正3: 开发工具调试行为
if (features.SuspiciousApiStrings.Count > 0 && result.MatchedRuleCount <= 1)
{
var exeLower = features.ExePath.ToLowerInvariant();
var isDev = exeLower.Contains("visual studio") || exeLower.Contains("jetbrains") ||
exeLower.Contains("vscode") || exeLower.Contains("debugger") ||
exeLower.Contains("ollydbg") || exeLower.Contains("x64dbg") ||
exeLower.Contains("windbg") || exeLower.Contains("ida");
if (isDev)
{
result.FinalRiskLevel = RiskLevel.Safe;
result.CombinedScore = Math.Min(result.CombinedScore, 0.30);
result.FalsePositiveCorrection = "开发/调试工具正常行为";
}
}
}
private static string BuildReasoning(RuleValidationResult result, MemoryFeatures f,
List<MitreRule> matchedRules)
{
var parts = new List<string>();
if (result.FalsePositiveCorrection != null)
{
parts.Add($"误报修正: {result.FalsePositiveCorrection}");
}
if (matchedRules.Count > 0)
{
var ruleDescriptions = matchedRules.Take(3)
.Select(r => $"{r.TechniqueId} ({r.TechniqueName})");
parts.Add($"匹配MITRE规则: {string.Join(", ", ruleDescriptions)}");
}
if (f.RwxPrivatePages > 0)
parts.Add($"RWX私有页: {f.RwxPrivatePages}");
if (f.PeMismatchDisk)
parts.Add("Process Hollowing 特征");
if (f.SuspiciousStringHits > 0)
parts.Add($"敏感字符串命中: {f.SuspiciousStringHits}");
parts.Add($"综合评分: AI={result.AiScore:F2} 基线={result.BaselineAnomalyScore:F2} 规则={result.RuleScore:F2} → {result.CombinedScore:F2}");
return string.Join(" | ", parts);
}
// ─── MITRE ATT&CK 内存攻击规则库 ───
private static List<MitreRule> BuildMitreRules() => new()
{
// T1055: Process Injection
new MitreRule
{
TechniqueId = "T1055",
TechniqueName = "进程注入",
Description = "通过 VirtualAllocEx/WriteProcessMemory/CreateRemoteThread 实现跨进程代码注入",
EvaluateFunc = f => f.RwxPrivatePages > 0 &&
f.SuspiciousApiStrings.Any(a =>
a.Contains("VirtualAllocEx", StringComparison.OrdinalIgnoreCase) ||
a.Contains("WriteProcessMemory", StringComparison.OrdinalIgnoreCase) ||
a.Contains("CreateRemoteThread", StringComparison.OrdinalIgnoreCase))
},
// T1055.012: Process Hollowing
new MitreRule
{
TechniqueId = "T1055.012",
TechniqueName = "进程掏空 (Process Hollowing)",
Description = "创建挂起进程后掏空原始代码,替换为恶意载荷",
EvaluateFunc = f => f.PeMismatchDisk
},
// T1055.001: Dynamic-link Library Injection
new MitreRule
{
TechniqueId = "T1055.001",
TechniqueName = "DLL注入",
Description = "通过 LoadLibrary/CreateRemoteThread 注入恶意DLL",
EvaluateFunc = f => f.SuspiciousApiStrings.Any(a =>
a.Contains("LoadLibrary", StringComparison.OrdinalIgnoreCase) ||
a.Contains("LdrLoadDll", StringComparison.OrdinalIgnoreCase)) &&
f.SuspiciousApiStrings.Any(a =>
a.Contains("CreateRemoteThread", StringComparison.OrdinalIgnoreCase))
},
// T1620: Reflective Code Loading
new MitreRule
{
TechniqueId = "T1620",
TechniqueName = "反射代码加载",
Description = "在内存中直接加载PE/DLL不通过标准 LoadLibrary",
EvaluateFunc = f => f.AnonymousExecPages > 0 &&
f.RwxPrivatePages > 0 &&
f.SuspiciousStrings.Any(s =>
s.Contains("ReflectiveLoader", StringComparison.OrdinalIgnoreCase))
},
// T1574.002: DLL Side-Loading
new MitreRule
{
TechniqueId = "T1574.002",
TechniqueName = "DLL侧加载",
Description = "利用合法程序的DLL搜索顺序加载恶意DLL",
EvaluateFunc = f => f.TempPathModules > 0 && f.IsSigned && f.IsSystemPath
},
// T1071: Application Layer Protocol (C2)
new MitreRule
{
TechniqueId = "T1071",
TechniqueName = "C2 通信",
Description = "通过应用层协议与C2服务器通信",
EvaluateFunc = f => f.ForeignConnections > 0 &&
f.SuspiciousPortConnections > 0 &&
f.SuspiciousApiStrings.Any(a =>
a.Contains("connect", StringComparison.OrdinalIgnoreCase) ||
a.Contains("InternetConnect", StringComparison.OrdinalIgnoreCase))
},
// T1095: Non-Application Layer Protocol
new MitreRule
{
TechniqueId = "T1095",
TechniqueName = "非标准协议通信",
Description = "使用非标准协议的网络通信",
EvaluateFunc = f => f.NonStandardPortConnections >= 2 &&
f.ForeignConnections > 0
},
// T1027: Obfuscated Files or Information
new MitreRule
{
TechniqueId = "T1027",
TechniqueName = "代码混淆/加密",
Description = "内存中存在加密或混淆载荷",
EvaluateFunc = f => f.MaxEntropy > 7.2 &&
f.HighEntropyRegions >= 2 &&
f.AnonymousExecPages > 0
},
// T1003.001: LSASS Memory (Credential Dumping)
new MitreRule
{
TechniqueId = "T1003.001",
TechniqueName = "LSASS 凭据转储",
Description = "访问 LSASS 进程内存提取凭据",
EvaluateFunc = f => f.ProcessName.Equals("lsass.exe", StringComparison.OrdinalIgnoreCase) &&
f.SuspiciousApiStrings.Any(a =>
a.Contains("MiniDump", StringComparison.OrdinalIgnoreCase) ||
a.Contains("SamOpenUser", StringComparison.OrdinalIgnoreCase)) ||
f.SuspiciousStrings.Any(s =>
s.Contains("lsass", StringComparison.OrdinalIgnoreCase))
},
// T1036.005: Match Legitimate Name or Location
new MitreRule
{
TechniqueId = "T1036.005",
TechniqueName = "进程名伪装",
Description = "使用合法的系统进程名但位于非系统路径",
EvaluateFunc = f => f.IsSystemProcessName && !f.IsSystemPath
},
// T1547.001: Registry Run Keys
new MitreRule
{
TechniqueId = "T1547.001",
TechniqueName = "注册表持久化",
Description = "通过注册表run键实现持久化驻留",
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
s.Contains("Run", StringComparison.OrdinalIgnoreCase) &&
(s.Contains("SOFTWARE\\Microsoft\\Windows\\CurrentVersion", StringComparison.OrdinalIgnoreCase) ||
s.Contains("RunOnce", StringComparison.OrdinalIgnoreCase)))
},
// T1496: Resource Hijacking (Crypto Mining)
new MitreRule
{
TechniqueId = "T1496",
TechniqueName = "资源劫持 (挖矿)",
Description = "劫持系统资源进行加密货币挖矿",
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
s.Contains("stratum", StringComparison.OrdinalIgnoreCase) ||
s.Contains("cryptonight", StringComparison.OrdinalIgnoreCase) ||
s.Contains("coinmine", StringComparison.OrdinalIgnoreCase) ||
s.Contains("mining", StringComparison.OrdinalIgnoreCase))
},
// T1486: Data Encrypted for Impact (Ransomware)
new MitreRule
{
TechniqueId = "T1486",
TechniqueName = "勒索加密",
Description = "加密文件以勒索赎金",
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
s.Contains("ENCRYPTED", StringComparison.OrdinalIgnoreCase) ||
s.Contains("ransom", StringComparison.OrdinalIgnoreCase) ||
s.Contains("decrypt", StringComparison.OrdinalIgnoreCase)) &&
f.MaxEntropy > 7.0
},
// T1562.001: Disable or Modify Tools
new MitreRule
{
TechniqueId = "T1562.001",
TechniqueName = "禁用安全工具",
Description = "禁用或修改安全软件",
EvaluateFunc = f => f.SuspiciousApiStrings.Any(a =>
a.Contains("AdjustTokenPrivileges", StringComparison.OrdinalIgnoreCase)) &&
f.IsSystemProcessName
},
// T1106: Native API
new MitreRule
{
TechniqueId = "T1106",
TechniqueName = "Native API 调用",
Description = "通过NT原生API绕过用户态Hook",
EvaluateFunc = f => f.SuspiciousApiStrings.Count(a =>
a.StartsWith("Nt", StringComparison.OrdinalIgnoreCase)) >= 2
},
};
}
/// <summary>
/// MITRE ATT&CK 单条规则定义
/// </summary>
public class MitreRule
{
public string TechniqueId { get; init; } = "";
public string TechniqueName { get; init; } = "";
public string Description { get; init; } = "";
public Func<MemoryFeatures, bool> EvaluateFunc { get; init; } = _ => false;
/// <summary>
/// 评估规则,返回 (是否匹配, 置信度)
/// </summary>
public (bool matched, double confidence) Evaluate(MemoryFeatures features)
{
try
{
var match = EvaluateFunc(features);
return (match, match ? 1.0 : 0.0);
}
catch
{
return (false, 0);
}
}
}
/// <summary>
/// 规则引擎验证结果
/// </summary>
public class RuleValidationResult
{
public double AiScore { get; set; }
public double BaselineAnomalyScore { get; set; }
public double RuleScore { get; set; }
public double CombinedScore { get; set; }
public RiskLevel FinalRiskLevel { get; set; } = RiskLevel.Safe;
public int MatchedRuleCount { get; set; }
public List<string> MatchedTechniques { get; set; } = new();
public double RuleConfidenceScore { get; set; }
public string Reasoning { get; set; } = "";
public string? FalsePositiveCorrection { get; set; }
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="2.0.0.0" name="MemScanEDR.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Windows 10 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Windows 11 -->
</application>
</compatibility>
</assembly>