MemScan-EDR/src-csharp/MemScanEDR/Services/FeatureExtractor.cs

332 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}