EDR内存与行为分析
原创 pandazhengzheng 2026-09-18 22:00 广东

目录
内存区域分类
内存扫描
查询进程信息
调用栈分析
线程状态分析
性能影响
熵值分析与加密 payload 检测
行为序列与规则引擎
pe-sieve 与 moneta 检测原理
小结
1. 内存区域分类
1.1 PE 映射到内存
启动 .exe 时,PE 文件中的节区作为块完整拷入内存。
EXE Process
┌──────────┐ ┌──────────────┐
│ Header ├────────────────────►│ Header │
├──────────┤ ├──────────────┤
│ │ │ .text │ Backed RX
│ .text ├────────────────────►│ │
├──────────┤ ├──────────────┤
│ .data ├────────────────────►│ .data │ Backed RW
└──────────┘ ├──────────────┤
│ Virtual │ Unbacked RW
│ Alloc() │
└──────────────┘ 1.2 Backed vs Unbacked
来自 PE 文件映像的内存区域称为 backed(映像支持的)。它们可信,因为是磁盘 PE 文件的 1:1 副本(已被 AV 扫描)。内存区域"backed"于磁盘文件。也称为 IMAGE 区域。
进程通过 VirtualAlloc() 分配额外内存时,是 unbacked(无映像支持的)。也称为 USER 或 PRIVATE 内存。没有文件"backing"此区域。
1.3 内存区域属性
USER/PRIVATE/Unbacked:坏,潜在恶意,shellcode;
IMAGE/Backed:好,相当可信。
主要因为 shellcode(来自 exploit 或进程注入)通常位于 PRIVATE 内存。线程也应从 backed 区域启动。PRIVATE RWX 内存更可疑。
1.4 可信与不可信内存区域
可信的 IMAGE 类型内存区域(IMG):

不可信的 PRIVATE 类型内存区域(PRV):

1.5 Copy-On-Write(COW)
内存页的一个酷特性是 Copy-On-Write(写时复制)。内存扫描器能检查内存页是否被写入,这对只读 .text 节区(应在进程间共享)是不寻常的。moneta 通过 PSAPI_WORKING_SET_EX_BLOCK(来自 PSAPI_WORKING_SET_EX_INFORMATION 结构)使用此特性。
Data-only 攻击(如 AMSI patch 或 ETW patch)受偏好,因为它们不触发 COW(修改的是已可写的页,而非 .text)。
1.6 内存类型的 VAD 视角
Windows 内核通过 VAD(Virtual Address Descriptor)树管理进程虚拟地址空间。VAD 的 Type 字段决定内存类型:
VAD Type | 含义 | 可信度 | 来源 |
|---|---|---|---|
MEM_IMAGE | 映像映射(PE 加载) | 高 | LoadLibrary/ |
MEM_MAPPED | section 映射(非映像) | 中 | MapViewOfFile/ |
MEM_PRIVATE | 私有内存(VirtualAlloc) | 低 | VirtualAlloc/ |
关键:VAD 类型由映射方式决定,一旦通过 SEC_IMAGE 映射,永久为 MEM_IMAGE,后续覆写不改变 VAD 类型。
1.7 保护属性的可疑组合
保护属性 | 可疑度 | 说明 |
|---|---|---|
PAGE_EXECUTE_READWRITE(RWX) | 极高 | 合法代码极少用 RWX |
PAGE_EXECUTE_READ(RX)+ Private | 高 | 私有可执行 = shellcode |
PAGE_EXECUTE_READ(RX)+ Image | 低 | 合法映像代码 |
PAGE_READWRITE(RW) | 低 | 数据区域 |
PAGE_EXECUTE_WRITECOPY(RWX Copy) | 中 | 映像节区可写副本 |
1.8 参考资源
Windows Processes: Nefarious Anomalies and You: Memory Regions
Bypassing pe-sieve and moneta
Solving the Unhooking Problem
NtCreateSection + NtMapViewOfSection Code Injection
2. 内存扫描
2.1 内存签名扫描
内存签名扫描检测内存中的恶意代码,在 .text 或数据节区(栈、堆、.data 等)。
Event
│
Process ▼
┌───────────┐ ┌───────────┐
│ │ │ │
│ .text ◄────────┤ EDR │ Read + Scan
│ (bad) │ │ │
│ .data ◄────────┤ │
│ (bad) │ └───────────┘
└───────────┘ 基本与 AV 签名扫描相同:grep 或 yara 内存内容匹配已知恶意签名。
2.2 内存扫描的性能
内存扫描是性能密集操作。不持续进行,而是依赖触发器。
2.3 扫描触发时机
触发类型 | 说明 | 频率 |
|---|---|---|
周期性 | 固定间隔扫描 | 每 30s-数小时 |
事件触发 | 敏感事件发生时扫描 | 按需 |
可疑进程 | 高风险进程定期扫描 | 按进程 |
全量 | 系统空闲时全量扫描 | 每天/每周 |
2.4 扫描范围
EDR 通过 NtQueryVirtualMemory 枚举进程虚拟地址空间,筛选可执行区域:
MEMORY_BASIC_INFORMATION mbi;
PVOID addr = NULL;
while (VirtualQueryEx(hProc, addr, &mbi, sizeof(mbi))) {
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & PAGE_EXECUTE || mbi.Protect & PAGE_EXECUTE_READ ||
mbi.Protect & PAGE_EXECUTE_READWRITE)) {
if (mbi.Type == MEM_PRIVATE) {
// Private+Execute:强可疑,重点扫描
scanRegion(hProc, mbi.BaseAddress, mbi.RegionSize);
} elseif (mbi.Type == MEM_IMAGE) {
// Image+Execute:校验完整性
verifyImageIntegrity(hProc, mbi.BaseAddress, mbi.RegionSize);
}
}
addr = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
}2.5 YARA 内存扫描
rule shellcode_generic {
strings:
$s1 = { 4C 8B D1 B8 } // mov r10,rcx; mov eax (syscall stub)
$s2 = { FC 48 83 E4 F0 } // cld; and rsp,0xF0 (shellcode prologue)
$s3 = { 65 48 8B 04 25 60 00 00 00 } // mov rax, gs:[0x60] (PEB access)
condition:
any of ($s*)
}
rule reflective_loader {
strings:
$mz = { 4D 5A } // MZ header
$pe = { 50 45 00 00 } // PE signature
condition:
$mz at 0 and $pe
}2.6 内存扫描的规避
规避技术 | 原理 | 效果 |
|---|---|---|
Module Stomping | shellcode 在 MEM_IMAGE | 不在 Private+Execute 扫描范围 |
Sleep Mask | 睡眠时加密自身 | 扫描时看到密文 |
避免常见字节模式 | 不用典型 shellcode 序言 | YARA 不匹配 |
分散内存 | 拆分为小块 | 降低单区域可疑度 |
3. 查询进程信息
3.1 事件富化
EDR 接收事件后,会尝试富化它:
进程信息(如可执行名和命令行参数)
内存扫描(可能)
进程映像文件扫描(罕见)
┌──────────────┐
┌─────────────┐ EtwWrite() ┌──────────┐ Kernel callbacks │ │
│ Process ├───────────►│ ├─────────────────────►│ │
│ │ │ │ │ │
├─────────────┤ │ OS │ ETW │ EDR │
│ ntdll.dll │ │ ├─────────────────────►│ │
│ │ syscall │ │ ETW-TI │ │
│ ├───────────►│ ├─────────────────────►│ │
│ │ │ │ │ │
│ │ └──────────┘ │ │
│ │ │ │
│ │◄─────────────────────────────────────────────│ │
│ │ Query Process Information │ │
│ │ (NtQueryInformationProcess, │ │
│ │ memory scan, ...) │ │
└─────────────┘ └──────────────┘ 3.2 富化查询的信息
信息 | 获取方式 | 可信度 |
|---|---|---|
映像路径 | QueryFullProcessImageName | 高 |
命令行 | PEB | 中(可被篡改) |
父进程 PID | NtQueryInformationProcess(ProcessBasicInformation) | 中(可被篡改) |
进程签名 | 查询映像文件的签名状态 | 高 |
进程时间 | GetProcessTimes | 高 |
线程列表 | NtQuerySystemInformation | 高 |
内存区域 | NtQueryVirtualMemory | 高 |
句柄表 | NtQuerySystemInformation(SystemHandleInformation) | 高 |
3.3 PEB 解析
EDR 通过解析 PEB(Process Environment Block)获取进程信息:
typedefstruct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN BitField;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr; // 已加载模块链表
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 命令行等
PVOID SubSystemData;
PVOID ProcessHeap;
// ...
} PEB;
typedefstruct _RTL_USER_PROCESS_PARAMETERS {
// ...
UNICODE_STRING CommandLine; // 命令行
UNICODE_STRING ImagePathName; // 映像路径
// ...
} RTL_USER_PROCESS_PARAMETERS;注意:PEB 位于进程自身内存,可被进程自身篡改(命令行欺骗、映像欺骗——见第 4 篇)。
3.4 信息可信度
并非所有信息同等可信:
信息 | 可信度 | 原因 |
|---|---|---|
映像路径(QueryFullProcessImageName) | 高 | 内核查询,不易篡改 |
命令行(PEB) | 中 | PEB 在进程内存,可篡改 |
父进程 PID | 中 | 可通过属性列表篡改 |
签名状态 | 高 | 磁盘文件签名验证 |
内存区域属性 | 高 | VAD 在内核,不易篡改 |
4. 调用栈分析
4.1 调用栈回溯的原理
EDR 在 hook 处理例程中回溯当前线程调用栈,检查调用方合法性。x64 Windows 使用基于表的栈回溯(table-based unwinding)。