MemScan-EDR v2.0: AI-powered memory detection engine with 3-layer detection pipeline
This commit is contained in:
commit
c870321cd8
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal 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
19
MemScanEDR.sln
Normal 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
16
scripts/README.md
Normal 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
48
scripts/build_csharp.bat
Normal 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
|
||||||
5
src-csharp/MemScanEDR/App.xaml
Normal file
5
src-csharp/MemScanEDR/App.xaml
Normal 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>
|
||||||
55
src-csharp/MemScanEDR/App.xaml.cs
Normal file
55
src-csharp/MemScanEDR/App.xaml.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
464
src-csharp/MemScanEDR/MainWindow.xaml
Normal file
464
src-csharp/MemScanEDR/MainWindow.xaml
Normal 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>
|
||||||
568
src-csharp/MemScanEDR/MainWindow.xaml.cs
Normal file
568
src-csharp/MemScanEDR/MainWindow.xaml.cs
Normal 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) { }
|
||||||
|
}
|
||||||
24
src-csharp/MemScanEDR/MemScanEDR.csproj
Normal file
24
src-csharp/MemScanEDR/MemScanEDR.csproj
Normal 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>
|
||||||
47
src-csharp/MemScanEDR/Models/AIConfig.cs
Normal file
47
src-csharp/MemScanEDR/Models/AIConfig.cs
Normal 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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
43
src-csharp/MemScanEDR/Models/AppSettings.cs
Normal file
43
src-csharp/MemScanEDR/Models/AppSettings.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
286
src-csharp/MemScanEDR/Models/BaselineProfile.cs
Normal file
286
src-csharp/MemScanEDR/Models/BaselineProfile.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
229
src-csharp/MemScanEDR/Models/MemoryFeatures.cs
Normal file
229
src-csharp/MemScanEDR/Models/MemoryFeatures.cs
Normal 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;
|
||||||
|
}
|
||||||
85
src-csharp/MemScanEDR/Models/ProcessInfo.cs
Normal file
85
src-csharp/MemScanEDR/Models/ProcessInfo.cs
Normal 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; } = "";
|
||||||
|
}
|
||||||
450
src-csharp/MemScanEDR/Services/AIAnalyzer.cs
Normal file
450
src-csharp/MemScanEDR/Services/AIAnalyzer.cs
Normal 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"":""""}";
|
||||||
|
}
|
||||||
175
src-csharp/MemScanEDR/Services/BaselineEngine.cs
Normal file
175
src-csharp/MemScanEDR/Services/BaselineEngine.cs
Normal 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
|
||||||
|
}
|
||||||
237
src-csharp/MemScanEDR/Services/ClassificationEngine.cs
Normal file
237
src-csharp/MemScanEDR/Services/ClassificationEngine.cs
Normal 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
|
||||||
|
}
|
||||||
279
src-csharp/MemScanEDR/Services/DetectionPipeline.cs
Normal file
279
src-csharp/MemScanEDR/Services/DetectionPipeline.cs
Normal 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
|
||||||
|
}
|
||||||
145
src-csharp/MemScanEDR/Services/EntropyAnalyzer.cs
Normal file
145
src-csharp/MemScanEDR/Services/EntropyAnalyzer.cs
Normal 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; }
|
||||||
|
}
|
||||||
331
src-csharp/MemScanEDR/Services/FeatureExtractor.cs
Normal file
331
src-csharp/MemScanEDR/Services/FeatureExtractor.cs
Normal 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);
|
||||||
|
}
|
||||||
254
src-csharp/MemScanEDR/Services/MemoryScanner.cs
Normal file
254
src-csharp/MemScanEDR/Services/MemoryScanner.cs
Normal 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);
|
||||||
|
}
|
||||||
212
src-csharp/MemScanEDR/Services/PEHeaderAnalyzer.cs
Normal file
212
src-csharp/MemScanEDR/Services/PEHeaderAnalyzer.cs
Normal 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; }
|
||||||
|
}
|
||||||
168
src-csharp/MemScanEDR/Services/ProcessCollector.cs
Normal file
168
src-csharp/MemScanEDR/Services/ProcessCollector.cs
Normal 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);
|
||||||
|
}
|
||||||
403
src-csharp/MemScanEDR/Services/RuleEngine.cs
Normal file
403
src-csharp/MemScanEDR/Services/RuleEngine.cs
Normal 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; }
|
||||||
|
}
|
||||||
17
src-csharp/MemScanEDR/app.manifest
Normal file
17
src-csharp/MemScanEDR/app.manifest
Normal 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>
|
||||||
Loading…
Reference in New Issue
Block a user