穿透静态检测:EDR对抗技术的分层实现
字数 2678
更新时间 2026-04-22 12:56:43
EDR静态检测对抗技术详解
1. 静态对抗概述
1.1 核心对抗逻辑
静态检测是EDR检测链条的起点,其逻辑核心是:在样本未运行时,从PE结构、字节序列、常量数据、导入表等静态特征中识别恶意意图。红队的对抗目标是在不破坏语义的前提下,抹除或稀释这些可辨识痕迹,使静态引擎失效。
1.2 对抗技术分层
- 文件级:整体加密/加壳
- 编译级:LLVM IR层混淆
- 数据级:编译期字符串加密
- 特征级:PE结构伪装与重排
- 内存级:运行时payload实时加解密(Sleep Mask等)
2. 整体加密技术
2.1 基本原理
加载器 + 加密payload结构,payload以密文形式存储于磁盘,静态引擎只能看到无规律字节序列。
2.2 工程价值
- 实现样本分发与更新的解耦
- 只需加载器通过静态检测,payload可随意更换
- 为后续技术栈叠加提供基础
2.3 局限性
仅解决磁盘可见性,内存解密瞬间暴露面最大,需配合内存层保护。
3. 编译期混淆
3.1 LLVM Pass混淆原理
在LLVM中间表示(IR)层进行代码变换,保持语义等价但改变字节层、控制流、数据层形态。
3.2 主要混淆类型
| 混淆类型 | 实现原理 | 对抗目标 |
|---|---|---|
| 控制流平坦化 | 拆分基本块,用switch-case+状态变量串联 | 破坏控制流图(CFG)可读性 |
| 虚假控制流 | 插入永不会执行的不透明谓词路径 | 增加分析复杂度 |
| 指令替换 | 等效指令替换(如a+b → a-(-b)) | 改变字节序列特征 |
3.3 参考项目
- Hikari、Pluto(OLLVM fork)
- gmh5225/awesome-llvm-security(资源集合)
3.4 使用注意事项
- 对动态检测几乎无效(行为特征不变)
- 编译时间与二进制体积显著增加
- 建议仅对核心敏感函数深度混淆
4. 字符串常量加密
4.1 技术必要性
未加密字符串是静态分析最直接的突破口,.rdata段中的API名称、C2 URL、User-Agent等可直接被strings工具提取。
4.2 编译期加密实现
template <int N>
class ObfuscatedString {
char encrypted[N];
char key;
constexpr char enc(char c, int i) const {
return c ^ (key + i);
}
public:
constexpr ObfuscatedString(const char (&str)[N], char k)
: encrypted{}, key(k) {
for (int i = 0; i < N; i++)
encrypted[i] = enc(str[i], i);
}
const char* decrypt(char* buf) const {
for (int i = 0; i < N; i++)
buf[i] = encrypted[i] ^ (key + i);
return buf;
}
};
#define OBFS(str) ObfuscatedString<sizeof(str)>(str, __TIME__[7])
4.3 推荐开源库
| 项目 | 特点 | 兼容性 |
|---|---|---|
| JustasMasiulis/xorstr | 64位密钥、16字节对齐、向量化优化 | Clang 5.0+/GCC 7.1+/MSVC v141 |
| skCrypter | 轻量、C++11兼容 | 跨平台 |
| ADVobfuscator | 老牌、Pass丰富 | 成熟稳定 |
| obfuscate | header-only | 简单易用 |
4.4 关键实践要点
- 优化等级必须为-O1或更高
- Release构建需strip符号
- 解密后立即清零缓冲区(SecureZeroMemory)
- 配合API哈希化调用形成完整链路
5. PE特征对抗
5.1 导入表对抗
5.1.1 敏感API规避
- 自定义GetProcAddress/GetModuleHandle:通过PEB遍历手动解析
- API哈希:编译期计算函数名哈希,运行时匹配
- 手动DLL加载:完整实现PE加载器
- 直接系统调用:syscall指令绕过用户态hook
5.1.2 导入表伪装
#pragma comment(linker, "/include:MessageBoxW")
从正常PE提取大量"无害"导入项,淹没敏感API特征,避免导入表稀疏异常。
5.2 段布局重构
5.2.1 段名$后缀特性
#pragma code_seg(".fn$a")
void StepOne() { }
#pragma code_seg(".fn$c")
void StepTwo() { }
#pragma code_seg(".fn$b")
void StepThree() { }
.text\(a、.text\)b同属.text段但可控制排列顺序。
5.2.2 自定义段
#pragma code_seg(".mycode")
void DecryptPayload(BYTE* buf, int len, BYTE key) { ... }
#pragma code_seg()
#pragma data_seg(".mydata")
unsigned char encPayload[] = { ... };
#pragma data_seg()
5.2.3 垃圾段填充
#pragma section(".junk", read)
__declspec(allocate(".junk"))
char junk[(__COUNTER__ + 1) * 0x100] = {0};
__COUNTER__使每次编译偏移不同。
5.2.4 段重命名
#pragma comment(linker, "/RENAME:.text=.code")
#pragma comment(linker, "/RENAME:.rdata=.cfg")
6. 内存层保护
6.1 Sleep Mask技术原理
利用C2 beacon大部分时间休眠的特性,休眠期间加密payload内存,执行前解密,减少明文暴露窗口。
6.2 主要实现方案
| 项目 | 核心机制 | 特点 |
|---|---|---|
| Ekko (Cracked5pider) | CreateTimerQueueTimer+ROP链+RC4 | 首个公开PoC |
| D1rkSleep (SaadAhla) | 仅加密PE Image Section | 性能优化 |
| Cronos (Idov31) | Waitable Timer替代Timer Queue | API面更换 |
| SWAPPALA (oldboy21) | 借合法DLL映射地址 | 伪装为Image Memory |
| SLEAPING (oldboy21) | 定时器工作线程恢复挂起线程 | 绕过CFG检查 |
6.3 Ekko实现要点
- CreateTimerQueueTimer注册定时器回调
- 回调构造ROP链执行:RW权限→RC4加密→等待→解密→RX权限
- 全部使用合法API调用链
6.4 SWAPPALA借壳机制
- 加载合法牺牲DLL
- unmap后同地址创建新Section映射payload
- 休眠时换回合法DLL内容
- 唤醒时换回payload
7. 云端扫描对抗
云端扫描本质是本地检测逻辑的延伸,对抗手段相同。云端额外能力是跨样本关联分析,属动态对抗范畴。
8. 防御方检测策略演进
8.1 行为链检测
VirtualAlloc(RWX) → WriteProcessMemory → CreateRemoteThread序列检测
8.2 内存扫描升级
- Private Memory with EXECUTE标记
- RX权限的非Image页
- 定时器回调地址异常
8.3 堆栈回溯检测
定时器回调运行时,线程调用栈不在已知模块内
8.4 跨实体关联
流量模式、进程创建序列、横向移动轨迹分析
9. 核心对抗原则
- 分层叠加优于单点深耕:多层面60%覆盖比单层面100%更有效
- 编译期优先:运行时代码可被hook,编译期变换只留结果
- 最小暴露窗口:payload明文存在时间越短越安全
- 持续演进:关注对抗历史而非单点技术
10. 技术局限性认知
- 静态技术只解决"磁盘可见性"问题
- 行为特征无法通过静态手段消除
- 过度定制化(如异常段名)本身可能成为特征
- 对抗是动态博弈过程,无永久有效方案
注:本文内容完全基于提供的链接文档整理,未添加外部知识。所有技术细节、代码示例、项目参考均来源于原文。
相似文章
相似文章