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

213 lines
7.3 KiB
C#
Raw Normal View History

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