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

213 lines
7.3 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.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; }
}