AV_EDR对抗痛点

参考链接https://www.bilibili.com/video/BV1dH8GzDErF/

思路来源: @RedCore_Moriaty

前言

EDR的主要闪光点并非在拦截,而是它的行为分析和语言模型。面对EDR的围追堵截,维持权限已经越做越难
对应C2 开发-二线红队与EDR的对抗强度决定一线红队的驻扎与打点

在现在这种对抗强度巨大的情景下,一线红队的目标应该专注于既定目标的完成,而不是权限的长期维持。 可以类比你上去给特朗普一巴掌,就要做好被安保摁住的准备,但其实在这样的防护下,能够完成这个艰巨的任务就已经很不容易了

因此二线红队,也就是C2开发,本身要做的核心任务就是给一线创造空间,破开最大的时间窗口。 也就是将精力投入到对抗的环节上

详叙

核心概念:进程注入链_延迟对抗

这里其实本质上就是在讲一件事情:如何用”奇技淫巧”(误)拖缓EDR的分析时间并达成为一线拉开达成既定目标的时间窗口

image-20250803002300725

手法概览

传统思路1:内存加载

C2通过远程控制下发 Beacon Payload,利用内存函数申请内存,将加密的 Payload 解密后直接放进内存,并执行,实现 无落地、隐蔽执行image-20250803134349862

1. 利用内存分配函数 API

常见的 Windows API 有:

  • VirtualAlloc
  • VirtualAllocEx
  • NtAllocateVirtualMemory
  • HeapAlloc

它们的目的是:在当前或远程进程的内存空间中申请一块内存区域,并指定权限(如 PAGE_EXECUTE_READWRITE)以支持后续执行。

2. 为 Beacon 分配内存并加载解密

一般 Beacon 是一个加密后的 Payload(二进制 blob),有如下特征:

  • 已加密(避免特征被查杀)
  • 存在于内存中,未写入磁盘(避免落地)
  • 加载时需要解密(如 XOR/AES/RC4)

过程是:

  • VirtualAlloc 申请内存
  • 将加密的 Beacon(Shellcode)解密成明文
  • 把解密后的内容写入申请的内存地址
3. 修改执行(跳转)

内存中的 Payload 并不会自动执行,需要控制程序流跳转到它那执行

常见方式:

  • CreateThread / NtCreateThreadEx
  • QueueUserAPC + Sleep
  • SetTimer Callback
  • 栈迁移 + jmp 指令跳转(Reflective DLL 中常见)

目标:让程序跳到你申请的内存执行 Beacon 逻辑。

这个现在根本行不通,不用说后期日志取证,在执行阶段就已经被拦截。

为何行不通,阐述防御机制

这和windows内存页自身的属性有关

正常进程加载的模块是 MEM_IMAGE 类型内存页自己手动申请的内存则是 MEM_PRIVATE。这个差异是蓝队检测内存注入、反射加载的基础指标之一。

只需要能监控到你执行的代码区域,只需要你落到MEM_PRIVATE,因此软件基于非MEM_IMAGE 类型内存页执行代码的判断就非常迅速且简单

这也叫做代码执行区审查

传统思路2:Module Stomping对抗代码合法审查检测

将合法模块加载到内存中后,覆盖其 .text 段或入口函数处的代码,用作恶意代码的跳板执行,借助其 MEM_IMAGE 属性掩盖真正 Payload 的行为

image-20250803161009933

1. 调用 LoadLibraryA("合法模块.dll")
  • 加载合法 DLL(比如 mshtml.dllduser.dll 等)
  • 一般我们会使用**LoadLibrary函数去动态加载DLL,但是这会出现一个问题,通过LoadLibrary函数去动态加载DLL会受到控制流(CFG)的限制。**CFG会组织执行未经过签名的和验证的代码。
  • 这里可以使用NtCreateSection函数以及NtMapViewOfSection函数来手动映射DLL文件,这可以确保映射的内存区域为SEC_IMAGE权限。
2. VirtualProtect 修改其 .text 段权限为 PAGE_EXECUTE_READWRITE
  • 或者直接操作 PEB 中的模块入口点(更隐蔽)
3. 将你的 Beacon Shellcode 写入 .text 区段 或 DllMain
  • 典型鸠占鹊巢,把合法模块原始逻辑覆盖掉

  • .text部分是通常包含了可执行的代码,那么攻击者就可以覆盖其这部分代码,将合法的代码替换为shellcode

4. 调用模块导出函数 或 创建线程执行入口点
  • 实际执行的就是你写入的 Beacon,而看上去是合法模块的行为
对应传统思路1的进阶反制

之前我们是通过VirtualAlloc申请私有内存,将shellcode写入到其中。而MoudleStumping是通过劫持合法dll, 篡改合法内存执行代码。.text 区域是 MEM_IMAGE 类型 → 不在 EDR 的 MEM_PRIVATE+EXECUTE 监控范围内

没有新模块注入 → dlllist, pe-sieve, memory scanner 等不会报告新增模块

所在模块路径存在于磁盘 → 无法一眼看出是注入模块

EDR防御机制(基于业务与排查方法阐述

通过EDR的角度来看,从日志而言是肯定能观测到鸠占鹊巢的行为,但正常系统的dll也存在这种覆写行为,因此EDR需要结合其他维度进行排查

因此免杀的目的,其实就是拉高EDR的阈值,为一线人员争取更多完成既定任务的时间。

这里拿出一个情境来说:
作为EDR而言,在一个生产环境中,用户每日都在进行打开WPS,Outlook,打印等流程。基于这个行为制作出了对应的行为模型和标杆,那么你作为外部测试人员,哪怕是通过Moudle Stumping鸠占鹊巢的手段达成了驻留的目的,但你在里面执行命令的操作势必就犹如在地铁上刷登机牌一样,对于EDR日常模拟出的用户行为模型是明显的不正常行为

EDR with VBS防御机制

在 Win10 之后(特别是 Win11 默认开启),VBS 指的是:

通过 Hyper-V 虚拟化技术实现的内核隔离机制,用来强化 Credential Guard、HVCI、Kernel Code Integrity 等功能。

VBS 与 EDR 的整合:做了哪些事?

功能 作用 对红队影响
HVCI(Hypervisor-protected Code Integrity) 强制所有内核代码都要经过签名校验 拒绝加载未签名或伪造签名的驱动
Credential Guard 将LSASS运行在 VSM(隔离容器)中 传统 mimikatz 无法直接读内存
Kernel-mode Code Integrity(KMCI) 加强驱动签名验证与运行时保护 加载 BYOVD 驱动时易被拦截/拒绝
KDP(Kernel Data Protection) 保护某些内核对象和结构不可修改 Patch SSDT/EDR回调 失败率提高
EDR Sensor Isolation 将 EDR 驱动模块运行在 VTL1(虚拟化信任层) 红队无法在 VTL0(正常内核)中卸载/patch 探针

简言之:你即使能提权拿到 SYSTEM,甚至能注入内核,也无法“真正”对抗驻留在 VBS 支持下的 EDR 内核组件。

在 Windows 11 中,EDR 利用 VBS 做的最关键事情是**:加强对内核代码完整性、驱动签名验证、EDR 模块隔离的控制**,导致红队几乎无法使用传统 BYOVD 手法加载未签名或漏洞驱动来做内核 Patch 或卸载。

注解:BYOVD手法 也就是利用内核驱动关闭杀软进程

参考奇安信攻防社区-BYOVD技术实战:利用内核驱动关闭杀软进程

因此在VBS机制的保护下,硬刚EDR其实是损耗比很大的工作,与其追求卸载杀软进程,不如从绕和驻留的角度进一步入手这个思路。

EDR_ETW订阅机制

引申:为什么我们在当前用户权限下,无法影响/patch EDR 内部的 ETW 订阅机制?

ETW是**一种Windows内核和应用程序级的事件跟踪机制,允许捕获系统运行时的事件,**EtwEventWrite函数是用于生成这些事件的函数。当恶意软件或攻击者希望隐藏进程的活动或避免监控时,他们可能会使用ETW补丁技术来拦截或修改EtwEventWrite函数,从而阻止进程生成ETW事件。

相对的 阐述一下BypassETW手法(如图)

image-20250803164347649

但在当前情境下,我们通过BypassETW实现规避监控是很困难的,可以通过logman query providers观察到微软内置的一些监控进程。 可以看到哪怕没有EDR,微软自带的威胁情报也能监控的死死的。

image-20250803164601270


本站由 Satoru 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。