|
攻击者的主要目标是在不被发现的情况下达到某一目的。这通常涉及在环境中建立立足点,然后横向移动。在现实中,攻击者操作时几乎没有或根本没有关于目标安全控制的信息。因此,他们必须调整他们的策略,随着时间的推移,控制措施,以最大限度提高成功的可能性。
从攻击者的角度来看,这些新技术中更具挑战性的一项是 endpoint detection and response (EDR,终端检测与响应),它常被人们说是 antivirus (AV,反病毒) 的未来。传统的 AV 主要是使用签名和启发式分析的组合来对恶意代码进行预防和检测,然而,EDR 有两个主要功能,一是检测恶意行为或签名;二就是对分析和 IR (incident response,事件响应) 提供帮助。因此,EDR 解决方案已成为全面防御攻击者的事实上的要求。
EDR 专门用于检测终端设备上发生的可疑行为,例如进程注入。一旦识别出这些行为,防御方和事件响应者随后便会采取措施,其中可能包括将受感染主机与网络隔离,以便于终端设备日志收集、查看事件的时间线、收集和记录威胁指标,甚至可以终止可疑进程。
攻击者通过开发新的、高度复杂的技术来逃避 EDRs 的磁盘和内存检测。EDRs 这些功能的关键,源于它能够 hook 主机上所有正在运行的进程。
如果没有我们之前的那些高质量的工作,这个项目是不可能实现的。很多人公开了审查 EDR 和内存钩子方面开创新研究,重点是绕过特定产品的方法。本文的目的是更深入地探讨该主题,而不只专注于适用于特定产品的技术,而是识别所有 EDR 产品的系统性问题以及攻击者如何利用它们来绕过 EDR 产品,而无需了解客户的安全,这涉及深入研究这些先前讨论过的概念以及发现新的概念。
如果您想了解更多,下面有一些额外的资源可供参阅:
一、什么是 hooking
Hooking 是一种能够改变应用程序行为的技术,EDR 使用此技术可对进程的执行流进行监测,收集基于行为的分析信息,检测可疑活动和恶意活动。提高在初始技术 (代码执行) 和后利用技术 (权限提升、横向移动或勒索软件活动) 阶段的检测率。
这些钩子将数据发送到 EDR agent 上,以便实时处理遥测数据 。agent 通常安装于内核中,这意味着拥有最高访问权限。为什么要安装在内核中,有两个主要原因,一是避免被攻击者关闭或删除,因为访问内核中 (Ring Zero) 运行的服务并不是那么容易。若要被利用,通常需要借助某种漏洞,或者攻击者必须已在终端设备上获得了较高权限。由于攻击者以 "黑匣子" 的心态布局,通常假设最初的立足点还没有获得特权,那么必须通过后续 exploit 中获得,操作时如果不谨慎点,可能会被 EDR 捕获。另外一个原因是能够来控制和监视整个系统。
agent 通常通过加载 Dll 的方式来对进程进行下钩操作,Dll 重新映射已加载的函数,这样 agent 就能监控每个进程,收集遥测数据。agent 不断地接收信息,监控进程、磁盘甚至网络通信中的所有变化。agent 将数据传送到基于云平台的产品中,在这里,所有数据都被处理成可操作的数据,其中可能包含恶意的,也可能包含不是恶意的,虽然大部分预防控制是由 agent 执行的,但对攻击技术的修改通常可以绕过 agent 的初始检测。在基于野外收集的所有数据识别恶意行为方面上,EDR 平台能发挥真正作用。
例如,一个进程产生一个处于挂起状态的新进程并修改该新进程的内存权限,试图执行 WriteProcessMemory。写入加密后的数据,不会触发 agent 的任何恶意指标;然而,发送到 EDR 平台的遥测仍然可以查看这些数据,并确定这些事件是否为潜在的 process hollowing 技术,所有这些发生不到一秒,这意味着 agent 需要许多数据对这些分析决定做支撑。
为了进一步了解这个数据流是如何工作的,我们需要了解 Windows 体系结构。对于初学者来说,Windows 系统提供了大量的函数和 API 调用,应用程序可以利用它们来执行代码。Windows API 的主要功能是在调用 syscall 执行低级汇编指令之前平衡所有堆栈寄存器。
系统调用比如 NTAllocateVirtualMemory 提供了一个低级接口,允许进程与操作系统交互。这些是过渡到内核的低级汇编指令,用于告诉 CPU 执行一个动作,如分配内存、创建文件或将存储在特定缓冲区的数据写入磁盘。这些 syscalls 驻留在 ntdll.dll 中,虽然其中许多没有文档记录,但 syscalls 不能被直接调用,因为它们只执行一个低级汇编指令。
当一个进程被执行时,系统 Dll 被加载,此时 EDR agent 会去 hook 特定的 API 函数和 syscall,例如 VirtualAlloc 和 NTAllocateVirtualMemory。需要注意的是,每个 EDR 平台 hook 不同的函数和 syscall,提供不同的遥测,进而产生不同的信息和检测。随着执行流程的进行,触发 EDR 钩子,强制从系统 Dll 跳转到 EDR Dll 中执行,此时 EDR 执行一系列指令,最终还是要返回到系统 Dll 中。
正如你所看见那样,同一个 syscall 被调用 2 次,这是由于用户模式和内核模式之间转换。系统调用函数可能以字符 NT 或 ZW 为前缀。NT 系统调用表示来自用户模式的调用,而 ZW 系统调用表示内核模式的调用。无论哪个,底层 syscall 指令都是相同的。
二、为什么 EDRs 喜欢在用户模式下 hook
虽然内核模式是最高级访问类型,但它确实存在一些缺点,使 EDR 的有效性变复杂。在内核模式下,可见性非常有限,因为在用户模式下才有几个数据点可用。此外,基于第三方内核的驱动程序通常很难开发,如果不进行适当的审查,可能会导致系统不稳定。内核通常被认为是最脆弱的部分,内核模式代码中的任何出错都将导致问题发生,甚至使系统完全崩溃。
攻击者更青睐于用户模式,在用户模式下是无法直接访问底层硬件。在用户模式下运行的代码必须使用应用程序与硬件交互的 API 函数,从而提高稳定并减少系统级别的崩溃 (因为应用程序崩溃一般不会影响系统)。因此,在用户模式下运行的程序不需要给予太多的权限,这样还稳定。可以说,很多 EDR 产品严重依赖于用户模式下的 Hook 而不是内核模式下的 Hook,这让攻击者很感兴趣。
在用户模式下可以对进程进行下钩子操作,因此可以控制它们。由于应用程序在用户的上下文中运行,这意味着加载到进程中的所有内容都可以由用户以某种方式进行操作。需要注意的是,内存的一些敏感区域设置为 Execute,Read (ER-),可以防止对这些区域进行修改。我们将在下面讨论一些技术来解决这个问题。
三、在内存中绕过 EDRs
在我们寻找内存钩子之前,我们需要识别 EDR 的 Dll,这是缩小范围的关键,因为一个简单的应用程序可以加载许多不同的 Dll,并且它们可以根据进程需要的功能进行更改。例如,任何 Windows 套接字连接都需要 ws2_32.dll。
识别 EDR 的 Dll 就像查看 Dll 的名称或描述一样简单,其他技术可能涉及查找 Dll 的路径或查看 Dll 的代码签名证书。有几个很棒的工具可以做到这一点,但对于我们这个案例,将使用 ProcessHacker2,一个免费的进程监控软件。正如我们所看到的,加载多个 Dll,但其中一个在名称和描述上都很突出,因为名称包含 EDR 产品的名称,而描述表明了其用途。
现在我们已经确定了 EDR Dll 了,下一步是去搜索这些钩子。为此,我们需要更深入地了解这些系统级函数以及它们如何在堆栈上运行。如果我们使用开源调试器 x64dbg 看一下,可以看到每个 Dll 都有一系列导出函数,这些是应用程序可以利用的功能。
我们知道 syscalls 是应用程序访问内核执行低级指令的唯一途径。在 x64 体系结构中,每个 syscall 都应以当前存储在 RCX 中的值移至 R10 开始 (mov r10, rcx),然后将一个十六进制值移入 EAX 寄存器中,这个十六进制值是一个系统服务号,对于每个 syscall 都是唯一的。内核不知道实际执行的是什么指令,只是查找唯一的 syscall ID 来确定要执行的相应指令。在此示例中,可以看到典型的 syscall 在程序集中的大致样子。SYSCALL_ID 对于该特殊 syscall 始终是唯一的。
- mov r10, rcx
- mov eax, (SYSCALL_ID)
- test byte ptr ds:[7FFE0308],1
- jne ntdll (ADDRESS)
- syscall
- ret
- int 2E
- ret
复制代码
有了这些信息,我们知道 EDR 的 Dll 名称以及这些 syscall 长什么样子,因此可以去寻找这些函数的差异,我们还知道 EDR 通过修改前 5 个字节来重定向执行流程,从跳转开始 (例如 JMP 指令,E9) ,我们可以使用它 (E9) 通过在 ntdll.dll 中搜索与之匹配的潜在钩子。
在这里我们可以很清楚地看到被 hook 的 syscalls。
现在情况并非总是如此,因为某些产品试图通过在跳转到 EDR 本身之前先跳转同一系统 Dll 中的不同区域或当前进程中的线程来掩盖它们的跳转。在许多情况下,结果是相同的。我们仍然可以通过对程序集进行任何修改的 syscall 来识别这些指令。
有了已知所有钩子的知识,我们就可以通过 patch 正确的字节、覆盖 EDR 的钩子来恢复这些值。这可以防止任何遥测数据从进程传输到 agent,因为 EDR 的 Dll 没有要发送的数据。
这种方法理论上来讲非常棒,然而,有几个因素可能会使执行变得困难。第一个主要因素为在此进程完成之前,EDR 很有可能会重写所有钩子;另一个主要因素是准确了解每个 EDR 钩子的功能。每个产品会 hook 不同的 syscall,同时攻击者从不知道产品在使用哪个 syscall (取决于他们的开源情报),如果要 hook 不同 Windows 版本下的要被 hook 的函数,这样可能会导致 Payload 的大小以幂数级增长,需要额外的研究来确定在没有检测到所有产品的情况下在何处进行这些有针对性的修改。
一种更简单的方法是重新加载进程内存的一部分以刷新 EDR。 这是因为我们知道 EDR 的钩子是在进程生成时放置的。我们还知道 EDR 通常会对哪些 DLL 下钩子。我们可以通过使用 VirtualProtect 来定位这些 Dll 并在内存中操作它们,该函数将进程的内存权限的一部分更改为不同的值。这些值可以是读、写和执行的组合。
首先去寻找位于磁盘上的系统 Dll,这些文件位于 C:\Windows\System32\ 或 C:\Windows\SysWow64 (32 位) 目录下,这些存储在磁盘上的 Dll 是未被 hook 的,因为 EDR 仅 hook 加载到内存中的内容。一旦 Dll 在内存中打开,我们就可以获取 Dll 的 .text 区段的字节。Dll 的这一部分包含可执行程序集 - 在 NTDLL 的情况下,是带有 syscall 的可执行代码。
我们仍需要确保将字节数据写入进程内存中的正确位置,受地址空间布局随机化 (ASLR) 影响,地址空间布局随机化能够确保堆栈上的库位置在内存中随机定位。由于进程已在内存中建立,这可能会导致问题出现,因为如果我们没有将正确的字节数据应用到正确的内存地址当中,当应用程序尝试执行代码时,就会发生崩溃。我们需要 .text 区段中每个函数的偏移量,而不是尝试重新映射或进一步修改流程。每个函数都有一个偏移量,表示从所在的基地址开始的确切字节数,提供函数在堆栈中的位置。
使用 VirtualProtect,我们可以将 ntdll.dll 的 .text 区域更改为可写,虽然这是一个系统 Dll,但由于它已加载到进程中,可以更改内存权限而无需提升权限。同样要注意,我们只是更改了 Dll 的一部分以避免检测。一旦完成,就可以根据正确的偏移量写入字节数据,并将正确的、未 hook 的字节数据恢复到正确的内存地址。
既然 syscalls 已恢复到放置 EDR 的挂钩之前设置的值,则可以继续执行,而 syscalls 上没有任何挂钩。EDR 的 Dll 仍然存在,但它不会从钩子接收任何遥测数据,因为它们不再存在。EDR 不知道它的钩子不再存在,因为通常没有执行完整性检查来确保它们仍然处于活动状态。
这种 unhooking 方法非常有效,但是,更改内存区域的权限仍然会被检测,例如识别对 VirtualProtect 函数的调用。当 unhooking 攻击成功时,监控控制可以指示此攻击的存在。这可以通过使用其他技术覆盖内存部分来克服,绕过调用任何 Windows 函数来更改这些不可写内存区域的权限的需要。通过使用低级操作系统语言,我们可以在不修改执行、读取 (ER-) 权限的情况下覆盖和恢复系统函数的集合。
另一种技术涉及使用我们自己的程序集系统调用而不是依赖操作系统。这是避免触发 EDR 钩子的有效方法,因为 EDR 只 hook 它知道的内容,在这种情况下,它只知道系统 Dll 中存在的内容。这意味着我们可以通过创建我们自己的函数调用来设置寄存器以及我们自己的系统调用来避免检测到应用程序的内存部分,而不是在加载的系统 Dll 中。为此,我们需要包含设置的汇编代码,使用正确的值设置变量和寄存器并执行我们的 syscalls。这些汇编指令将包含在一个独立的汇编文件中,该文件将在我们编译 Payload 时加载到我们的代码中。系统调用值是动态的很重要,因为特定的系统调用 ID 值会根据 Windows 体系结构而变化。不同版本的 Windows 具有不同的系统调用,具体取决于操作系统,例如:
Windows Version | Syscall Number | Windows 10 / Server 2016 | 0x18 | Windows 8.1 / Server 2012 R2 | 0x17 |
如果你对此很感兴趣,并在开发自已的 syscalls 时,j00ru 的 Windows-Syscall 或许能给你提供更多帮助。大多数情况下代码看起来像下面这样:
我们需要包含的下一段代码是我们自己版本的 WinAPI 函数,用于在我们的 syscalls 执行之前将寄存器与正确的值对齐,这是由于 syscalls 的低级特性。这些函数通常位于 kernelbase.dll 或 kernel32.dll 中。我们继续以 NTAllocateVirtualMemory 为例。
- kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
- HANDLE ProcessHandle,
- PVOID *BaseAddress,
- ULONG_PTR ZeroBits,
- PSIZE_T RegionSize,
- ULONG AllocationType,
- ULONG Protect
- );
复制代码
在 syscalls 执行之前,这些值中的每一个都需要以特定顺序存储在堆栈上的寄存器中。当应用程序调用 VirtualAlloc 时,只需要四个参数。
- LPVOID VirtualAlloc(
- LPVOID lpAddress,
- SIZE_T dwSize,
- DWORD flAllocationType,
- DWORD flProtect
- );
复制代码
这些值总是动态的,唯一静态的是 VirtualAlloc 的句柄。对于一个产品来说,读取每一个字节串来确定系统 Dll 之外是否存在 API 或 syscalls 也是非常困难的。
虽然这听起来很容易开发,但有很多小的依赖项,如果没有正确地开发到您的代码中,可能会导致内核崩溃。之前我们回顾了正常请求的执行流程以及 WinAPI 和 syscalls 如何与内核交互。在开发自己的 syscalls 和函数时,还需要为所有可能的响应代码和与这些值中的每一个相关联的十六进制值提供参考。当指令返回时,我们的代码需要知道如何解释响应; 显然,这些响应不会是 "ALL GOOD" 或这个值你造成了错误。这些通常是系统给出错误代码的十六进制值。
虽然我们强调了攻击者可以用一些技术来规避 EDR 产品的预防和检测机制,但这并不意味着产品已经很完美了。EDR 产品仍然是对检测和预防网络威胁很重要的卓越产品。重点不应该是用其他东西替换这些产品,相反,应该关注如何改进这些产品,因为它们不会很快消失。
四、我们还能做什么
虽然我们只讨论了几种有效的技术,但仍有许多不同的方法可以实现相同目的。对于依赖 EDR 产品来帮助检测和预防网络威胁的组织来说,这似乎非常令人生畏。
一个艰难的事实是没有简单的解决办法,大部分补救措施只能靠这些产品的持续改进。第一个是落实防篡改传感器,它可以监视应用程序的行为,寻找进程内存变化的指标的特定特征,特别需要关注加载的系统 Dll。进程需要修改系统 Dll 的情况很少,因为它们仅用于与操作系统交互。这些传感器本质上是通过在创建初始进程并加载 Dll 后监视对系统 Dll 字节数据或权限的任何修改情况下来工作。
另一个更广泛的途径是摆脱依赖在用户模式下的 hooking,转向内核,这种方法有很多好处,因为内核是操作系统要加载的第一部分,因此,它可以定位和监视任何操纵用户模式钩子的尝试。无论是用于网络连接还是文件系统监控,大多数现代 EDR 产品已经在内核模式中具有某种功能。扩展这些光学器件不仅可以提供额外的遥测,还有助于防止攻击者进行任何篡改,因为修改内核模式资源需要某种特权访问。此外,诸如 PatchGuard 之类的机制可防止对内核中运行的资源进行任何修改。正如之前提到的,开发一个在内核空间运行的稳定产品确实需要付出更多的努力。
攻击者可以清除事件日志或欺骗命令行参数来混淆遥测数据,从而更难获得正确的信息。Windows 事件跟踪 (ETW) 使得这样做非常困难。ETW 提供了一种从用户和内核模式跟踪和记录事件的方法。ETW 主要在内核中运行,但它依赖于 ntdll.dll 中的 syscalls 提供的遥测数据。这些遥测数据被 ETW 用作数据点。ETW 的独特之处在于它不依赖于钩子,因为这些钩子都是 Windows 原生的,主要目的是将数据输入内核。这意味着永远不需要修改或 hook 这些 syscalls。相反,ETW 确定哪些数据是相关的,哪些不是。通过使用 ETW,产品可以根据来自用户模式钩子的数据进行相同的确定。
如何规避 ETW 的技术已公开,利用此技术进行的攻击不需要提升权限,这使得在现实世界中执行这些技术变得困难,因为攻击者的初始妥协点通常没有权限提升,主要为后利用目标。从主机捕获某种形式的 ETW 遥测可以为蓝队提供大量必要的数据,以对检测进行分类并了解威胁带来的风险。它还可以帮助事件响应人员了解攻击发生的方式和时间,从而为响应流程提供信息。
实施 EDR 很棒,但将它们作为针对网络攻击的单一检测来源可能会造成单点故障。此外,采用一套与 EDR 互补的综合安全控制措施可以非常有效地填补这些空白,以帮助预防和检测网络攻击。包括确保从所有终端设备收集事件日志,而不仅仅是关键系统或服务器,因此防御者能够将相关的攻击活动关联起来。这些事件可能不会清楚地表明绕过 EDR 攻击,但可以提供检测和预防攻击所需的其他有用的工件。
此外,基于网络的控制在检测恶意活动方面同样重要。包检查和 TLS 拦截可以检测网络上的恶意活动。即使攻击者能够绕过安全控制并不让日志记录事件,网络控制也可以检测出 C2 通道的出站连接。最后,应用程序白名单或其他限制应用程序执行的方法可能会阻碍攻击者绕过 EDR。
虽然这些都是帮助提高检测和预防能力的绝佳点,但事实仍然是,这在很大程度上是一场猫捉老鼠的游戏。攻击者总是会构建新的技术来逃避检测,而防御者则必须做出相应措施与之对抗。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|