332 lines
12 KiB
C#
332 lines
12 KiB
C#
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);
|
||
}
|