EDR内存与行为分析

· 2026-09-18 22:00 · 42 阅读

原创 pandazhengzheng 2026-09-18 22:00 广东

目录

  1. 内存区域分类

  2. 内存扫描

  3. 查询进程信息

  4. 调用栈分析

  5. 线程状态分析

  6. 性能影响

  7. 熵值分析与加密 payload 检测

  8. 行为序列与规则引擎

  9. pe-sieve 与 moneta 检测原理

  10. 小结


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):

Memory Regions Good

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

Memory Regions Bad

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

/NtMapViewOfSection(SEC_IMAGE)

MEM_MAPPED

section 映射(非映像)

中

MapViewOfFile

/NtMapViewOfSection(SEC_COMMIT)

MEM_PRIVATE

私有内存(VirtualAlloc)

低

VirtualAlloc

/VirtualAllocEx

关键: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 ProcessParameters.CommandLine

中(可被篡改)

父进程 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)。

跳转微信打开

本文转载自微信公众号「安全分析与研究」,查看原文。版权归原作者所有,如有侵权请联系删除。