15. 高级规避技术
15.1 Sleep Obfuscation(内存加密休眠)
核心思想: Beacon空闲时加密自身内存(RW状态) → EDR内存扫描只能看到加密数据 → 定时器/APC触发解密(RWX) → 执行任务 → 再次加密。EDR扫描时看到的永远是加密内存。
三大主流实现原理对比:
| 技术 | 触发机制 | 核心API | 原理 |
|---|---|---|---|
| Ekko | Timer Queue | CreateTimerQueueTimer + NtContinue | 链式Timer回调: Timer1→修改内存为RW+加密 → Sleep → Timer2→解密+修改为RX+继续执行。所有回调通过NtContinue传递伪造线程上下文 |
| Foliage | APC | NtQueueApcThread + NtContinue | 在工作线程上排队一系列APC: APC1→加密内存+RW → APC2→Sleep → APC3→解密+RX+恢复执行。每个APC传递不同的线程上下文 |
| Zilean | Wait Object | RegisterWait + NtContinue | Ekko变体,使用RegisterWait回调替代Timer Queue |
| Hypnus | 多模式 | TpSetTimer/TpSetWait/NtQueueApcThread | Rust实现,三大模式+调用栈伪装+堆加密。动态注册CFG(控制流防护)目标 |
实际攻击流程(Ekko为例):
1. CreateTimerQueueTimer注册回调 → 指向NtContinue(带伪造CONTEXT)
2. 回调触发: VirtualProtect(payload, RW) → SystemFunction032(RC4加密)
3. Sleep(等待时间)
4. 第二个Timer触发: SystemFunction032(RC4解密) → VirtualProtect(payload, RX)
5. NtContinue恢复原始线程上下文 → 继续执行Beacon
6. 循环
检测与对抗:
- Hunt-Sleeping-Beacons(HSB): 枚举定时器+分析回调地址是否指向NtContinue → 发现Sleep Obfuscation
- EkkoMod绕过HSB: Timer回调指针指向NtContinue前8字节(nop指令0F 1F 84 00 00 00 00 00) → 执行时nop→NtContinue → HSB不识别为NtContinue回调
- Stack Duplication: 复制线程的寄存器+栈(包含返回地址) → 休眠时调用栈看起来像正常回调线程 → 避免栈包含NtSignalAndWaitForSingleObject的IOC
- Module Stomping配合: 将payload加载到合法DLL的内存区域 → 内存看起来属于合法模块 → 避免unbacked memory检测
- 内存状态转换: 安全做法是RW↔RX(不经过RWX) → Havoc的Sleep Mask使用RWX容易被EDR标记
Linux Sleep Obfuscation(SilentPulse): - 2025年出现Linux版本: 使用POSIX timer_create + SIGEV_THREAD → 回调中加密/解密 - 面临类似挑战: 栈分析可检测 → 需要类似Stack Duplication的规避 - 工具: Ekko(已归档) / Hypnus(Rust推荐)
15.2 间接系统调用演进
从直接到间接系统调用的进化:
- 直接系统调用(Direct Syscall): 在自己的代码中硬编码syscall指令 → EDR通过调用栈分析发现syscall不在ntdll中 → 检测
- 间接系统调用(Indirect Syscall): 在ntdll中找到syscall; ret指令地址 → jmp到该地址执行 → 调用栈显示返回地址在ntdll内 → 合法
- Tartarus' Gate: 运行时随机化syscall stub地址 → 每次执行stub不同 → 避免硬编码特征
- HalosGate/TartarusGate: ntdll被hook时(前几条指令被改为jmp) → 搜索相邻syscall stub → 跳过hook找到干净stub
- Mockingjay: 不需要ntdll! 在已加载的合法DLL中搜索syscall; ret指令序列 → 完全不涉及ntdll → 无ntdll hook可检测
- RecycledGate: 在已加载DLL中搜索syscall指令序列 → 更通用化的Mockingjay
API Hashing(工具层面): - 不使用GetModuleHandle+GetProcAddress(会被监控) → 手动实现模块遍历+函数名hash匹配 → 完全规避API监控 - 实战: toxoglosser使用GetModuleHandle+GetProcAddress via hashing + Tartarus' Gate + 无LazyDLL
15.3 ETW/AMSI绕过演进
ETW Patching(事件跟踪):
- 方式1: 修改EtwEventWrite入口为ret 0(直接返回) → 所有ETW事件静默丢弃
- 方式2: 更底层Patch → 修改ntdll!EtwEventWrite的前几字节 → 不触发内存保护
- 检测: EDR检查ntdll内存完整性 → 需要在Patch后恢复或使用硬件断点替代
AMSI Bypass(反恶意软件扫描接口):
- 方式1: Patch AmsiScanBuffer/AmsiScanString入口为直接返回 → 所有内容扫描返回"干净"
- 方式2: 硬件断点Hooking → DR寄存器设置断点在AmsiScanBuffer → 不修改内存 → 更隐蔽
- 方式3: CLM(Constrained Language Mode)绕过 → PowerShell受限模式下绕过执行策略
- 进化: 从[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')这种特征明显的脚本 → 到C# inline编译或直接系统调用级别的Patch
15.4 注入技术演进
模块踩踏(Module Stomping): - 原理: 加载一个合法DLL → 将其内存内容覆盖为恶意payload → 内存看起来属于合法DLL - 优势: 避免unbacked memory(无 backing 文件的内存) → EDR常见的内存扫描IOC失效 - 实战: 选择不常用的系统DLL踩踏 → 减少被检查概率
映射注入(Map Injection): - 原理: 手动解析PE → 分段映射到目标进程 → 不使用LoadLibrary → 无模块加载事件 - 优势: 不触发DllMain通知 → 不出现在模块列表中
早期APC注入(Early Bird): - 原理: 创建挂起进程 → QueueUserAPC注入 → ResumeThread → APC在主线程初始化前执行 - 优势: 在EDR的hook安装前就完成注入 → 绕过进程创建监控
硬件断点注入: - 原理: 使用DR0-DR3寄存器设置硬件断点 → 不修改任何代码内存 → 在断点触发时执行恶意逻辑 - 优势: 无内存修改 → EDR的完整性检查无法发现