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

332 lines
12 KiB
C#
Raw Permalink Normal View History

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