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

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