与猫捉老鼠相似的研究逃逸安全防护软件的技巧
2022-5-20 11:55:0 Author: www.4hou.com(查看原文) 阅读量:33 收藏

我会在本文中列出一系列可用于绕过行业领先的企业终端保护解决方案的技术。出于安全的考虑,所以我决定不公开发布源代码。

在模拟攻击中,“初始访问”阶段的一个关键挑战是绕过企业终端上的检测和响应能力 (EDR)。商业命令和控制框架向红队操作员提供不可修改的 shellcode 和二进制文件,这些文件由终端保护行业大量签名,为了执行该植入,该 shellcode 的签名(静态和行为)需要被混淆。

我会在将介绍以下技术,其最终目的是执行恶意的shellcode,也被称为(shellcode)加载程序:

Shellcode 加密;

减少熵;

退出(本地)反病毒沙箱;

导入表混淆;

禁用 Windows 事件跟踪 (ETW);

规避常见的恶意 API 调用模式;

直接系统调用和规避“系统调用标记”;

删除 ntdll.dll 中的挂钩;

欺骗线程调用堆栈;

信标的内存加密;

自定义反射加载程序;

可扩展配置文件中的 OpSec 配置;

1. Shellcode加密

让我们从静态 shellcode 混淆话题开始。在我的加载程序中,我利用了 XOR 或 RC4 加密算法,因为它易于实现并且不会留下大量加载程序执行的加密活动的外部指标。用于混淆 shellcode 静态签名的 AES 加密会在二进制文件的导入地址表中留下痕迹。在此加载程序的早期版本中,我已经让 Windows Defender 专门触发了 AES 解密函数(例如 CryptDecrypt、CryptHashData、CryptDeriveKey 等)。

1.png

dumpbin /imports 的输出,这是二进制文件中仅使用 AES 解密函数的痕迹

2. 减少熵

许多 AV/EDR 解决方案在评估未知二进制时考虑二进制熵。由于我们正在加密 shellcode,我们的二进制文件的熵相当高,这清楚地表明二进制文件中的代码部分被混淆了。

有几种方法可以减少二进制的熵,两种简单的方法是:

2.1 将低熵资源添加到二进制文件中,例如(低熵)图像。

2.2添加字符串,例如英语词典或某些“字符串 C:\Program Files\Google\Chrome\Application\100.0.4896.88\chrome.dll”输出。

一个更好的解决方案是设计和实现一种算法,将 shellcode 混淆(编码/加密)成英文单词(低熵)。

3. 退出(本地)反病毒沙箱

许多 EDR 解决方案将在本地沙箱中运行二进制文件几秒钟以检查其行为。为了避免对最终用户体验的影响,他们检查二进制文件的时间不能超过几秒钟(我曾经见过Avast检查时间超过30秒,但这是一个例外)。我们可以通过延迟shellcode的执行来滥用这个限制。简单地计算一个质数是我个人的最爱,并将该数字用作加密 shellcode 的(一部分)密钥。

4.导入表混淆

你希望避免可疑的 Windows API (WINAPI) 出现在我们的 IAT(导入地址表)中。此表包含你的二进制文件从其他系统库导入的所有 Windows API 的概述。可以在此处找到可疑 API 列表(因此通常由 EDR 解决方案检查)。通常,这些是 VirtualAlloc、VirtualProtect、WriteProcessMemory、CreateRemoteThread、SetThreadContext 等。运行 dumpbin /exports

我们添加 WINAPI 调用的函数签名,在 ntdll.dll 中获取 WINAPI 的地址,然后创建指向该地址的函数指针:

2.png

使用字符数组混淆字符串会将字符串分割成更小的部分,使它们更难从二进制文件中提取

该调用仍将针对 ntdll.dll WINAPI,并且不会绕过 ntdll.dll 中 WINAPI 中的任何挂钩,但纯粹是为了从 IAT 中删除可疑函数。

5. 禁用 Windows 事件跟踪 (ETW)

许多 EDR 解决方案广泛利用 Windows 事件跟踪 (ETW),特别是 Microsoft Defender for Endpoint(以前称为 Microsoft ATP)。 ETW 允许对进程的功能和 WINAPI 调用进行广泛的检测和跟踪。ETW在内核中有一些组件,主要用于注册系统调用和其他内核操作的回调函数,但也包含一个用户域组件,它是ntdll.dll (ETW深度攻击向量)的一部分。。由于ntdll.dll是一个加载到二进制文件进程中的DLL,因此我们可以完全控制该DLL,从而控制ETW功能。在用户空间中,ETW有很多不同的绕过方法,但最常见的是为EtwEventWrite函数打补丁,它被调用来写入/记录ETW事件。我们在ntdll.dll中获取它的地址,并用返回0 (SUCCESS)的指令替换它的第一个指令。

3.png

我发现上述方法仍然适用于两个测试的 EDR,但这是一个嘈杂的 ETW 补丁

6. 规避常见的恶意 API 调用模式

大多数行为检测最终都是基于检测恶意模式。其中一种模式是在短时间内特定的WINAPI调用的顺序。第 4 部分中简要提到的可疑 WINAPI 调用通常用于执行 shellcode,因此受到严格监控。然而,这些调用也用于良性活动(VirtualAlloc、WriteProcess、CreateThread 模式结合内存分配和写入大约 250KB 的 shellcode),因此使用 EDR 解决方案的挑战是区分良性调用和恶意调用。 你可以参考Filip Olszak 的一篇文章,其中提到利用延迟和较小的分配和写入内存块来融入良性 WINAPI 调用行为。简而言之,他的方法调整了典型 shellcode 加载程序的以下行为:

6.1 与其分配一大块内存并直接将大约 250KB 的植入 shellcode 写入该内存,不如分配小的连续块,例如小于64KB 内存并将它们标记为 NO_ACCESS。然后以类似的块大小将 shellcode 写入分配的内存页面。

6.2 在上述每个操作之间引入延迟。这将增加执行 shellcode 所需的时间,但也会使连续执行模式变得不那么突出。

使用这种技术的一个问题是,要确保在连续的内存页中找到一个可以容纳整个 shellcode 的内存位置。 Filip 的 DripLoader 实现了这个概念。

我构建的加载程序不会将 shellcode 注入另一个进程,而是使用 NtCreateThread 在自己的进程空间中的线程中启动 shellcode。未知进程(我们的二进制文件实际上流行率很低)进入其他进程(通常是 Windows 原生进程)是突出的可疑活动。当我们在加载程序进程空间的线程中运行 shellcode 时,更容易混入进程中良性线程执行和内存操作的噪音。然而,不利的一面是任何崩溃的开发后模块也会导致加载程序的进程崩溃,从而导致植入程序崩溃。持久性技术以及运行稳定可靠的 BOF 可以帮助克服这一缺点。

7. 直接系统调用和回避“系统调用标记”

加载程序利用直接系统调用绕过 EDR 放入 ntdll.dll 的任何挂钩。我不想在此过多地讨论直接系统调用的话题。

简而言之,直接系统调用是直接对内核系统调用等效的 WINAPI 调用。我们不调用 ntdll.dll VirtualAlloc,而是调用它在 Windows 内核中定义的内核等效 NtAlocateVirtualMemory。我们可以使用这种办法绕过任何用于监视调用(在本例中)ntdll.dll中定义的VirtualAlloc的EDR挂钩。

为了直接调用系统调用,我们从 ntdll.dll 中获取我们要调用的系统调用的 syscall ID,使用函数签名将函数参数的正确顺序和类型推送到堆栈,然后调用 syscall < id>指令。在此推荐两个工具,SysWhispers2 和 SysWhisper3 就是两个很好的例子。从规避的角度来看,调用直接系统调用有两个问题:

7.1 你的二进制文件最终具有 syscall 指令,该指令很容易静态检测。

7.2 与通过等效的 ntdll.dll 调用的系统调用的良性使用不同,系统调用的返回地址不指向 ntdll.dll。相反,它指向我们调用系统调用的代码,它驻留在 ntdll.dll 之外的内存区域中。这是一个未通过ntdll.dll调用的系统调用的指标,非常可疑。

为了克服这些问题,我们可以做以下工作:

7.3 实现一个寻蛋机制。将系统调用指令替换为egg(一些随机的唯一可识别模式),在运行时,在内存中搜索这个egg,并使用ReadProcessMemory和WriteProcessMemory的WINAPI调用将其替换为系统调用指令。然后,我们可以正常地使用直接系统调用。该技术已由klezVirus实现。

7.4我们不是从我们自己的代码中调用 syscall 指令,而是在 ntdll.dll 中搜索 syscall 指令,并在我们准备好调用系统调用的堆栈后跳转到该内存地址。这将导致 RIP 中的返回地址指向 ntdll.dll 内存区域。

这两种技术都是 SysWhisper3 的一部分。

8.删除ntdll.dll中的挂钩

逃避 ntdll.dll 中的 EDR 挂钩的另一个好方法是用来自 ntdll.dll 的新副本覆盖默认加载(并由 EDR 挂钩)加载的 ntdll.dll。 ntdll.dll 是任何 Windows 进程加载的第一个 DLL。 EDR 解决方案确保他们的 DLL 在不久之后被加载,这在我们自己的代码执行之前将所有挂钩放在加载的 ntdll.dll 中。如果我们的代码之后在内存中加载一个新的 ntdll.dll 副本,那些 EDR 挂钩将被覆盖。 RefleXXion 是一个 C++ 库,它实现了 MDSec 对该技术所做的研究。 RelfeXXion 使用直接系统调用 NtOpenSection 和 NtMapViewOfSection 来获取 \KnownDlls\ntdll.dll(具有先前加载的 DLL 的注册表路径)中干净 ntdll.dll 的句柄。然后它会覆盖加载的 ntdll.dll 的 .TEXT 部分,从而清除 EDR 挂钩。

我建议使用与第7部分中描述的相同的方法来调整RefleXXion库。

9. 欺骗线程调用栈

接下来的两部分将介绍两种技术,它们可以避免在内存中检测我们的shellcode。由于植入程序的信标行为,大部分时间植入程序都处于休眠状态,等待其操作员的传入任务。在此期间,植入程序容易受到来自 EDR 的内存扫描技术的攻击。本文中描述的两种规避方法中的第一种就是欺骗线程调用堆栈。

当植入程序处于休眠状态时,它的线程返回地址指向我们驻留在内存中的 shellcode。通过检查可疑进程中线程的返回地址,可以很容易地识别出我们的植入 shellcode。为了避免这种情况,想打破返回地址和shellcode之间的这种联系。我们可以通过挂钩 Sleep() 函数来做到这一点。当该挂钩被调用(通过植入/信标 shellcode)时,我们用 0x0 覆盖返回地址并调用原始的 Sleep() 函数。当 Sleep() 返回时,我们将原始返回地址放回原处,以便线程返回到正确的地址以继续执行。 Mariusz Banach 在他的 ThreadStackSpoofer 项目中实现了这种技术。

在下面的两个屏幕截图中,我们可以观察到欺骗线程调用堆栈的结果,其中非欺骗的调用堆栈指向非备份内存位置,而欺骗的线程调用堆栈指向挂钩的Sleep (MySleep)函数,并“切断”调用堆栈的其余部分。

4.png

默认信标线程调用堆栈

5.png

欺骗信标线程调用堆栈

10.信标内存加密

另一个规避内存检测的方法是在休眠时加密植入程序的可执行内存区域。使用与上一节中描述的相同的 sleep 挂钩,我们可以通过检查调用者地址(调用 Sleep() 的信标代码以及我们的 MySleep() 挂钩)来获取 shellcode 内存段。如果调用者内存区域是 MEM_PRIVATE 和 EXECUTABLE ,并且大小与我们的shellcode差不多,那么内存段将使用 XOR 函数加密并调用 Sleep()。然后 Sleep() 返回,它解密内存段并返回给它。

另一种技术是注册一个vector Exception Handler (VEH),它处理NO_ACCESS违规异常,解密内存段并更改RX的权限。然后就在休眠之前,将内存段标记为NO_ACCESS,这样当Sleep()返回时,就会出现内存访问冲突异常。因为我们注册了一个 VEH,所以异常是在该线程上下文中处理的,并且可以在引发异常的完全相同的位置恢复。 VEH 可以简单地解密并将权限更改回 RX,并且植入程序可以继续执行。这种技术可以防止在植入程序处于睡眠状态时出现可检测的 Sleep() 挂钩。

Mariusz Banach 也在 ShellcodeFluctuation 中实现了这种技术。

11.自定义反射加载程序

我们在这个加载程序中执行的信标 shellcode 最终是一个需要在内存中执行的 DLL。许多 C2 框架利用 Stephen Fewer 的 ReflectiveLoader。关于反射性 DLL 加载程序的工作原理有很多书面解释,Stephen Fewer 的代码也有很好的文档记录,但简而言之,反射式加载程序执行以下操作:

11.1将地址解析为加载 DLL 所需的必要 kernel32.dll WINAPI(例如 VirtualAlloc、LoadLibraryA 等);

11.2将 DLL 及其部分写入内存;

11.3建立 DLL 导入表,使 DLL 可以调用 ntdll.dll 和 kernel32.dll WINAPI;

11.4加载任何其他库并解析它们各自的导入函数地址;

11.5调用 DLL 入口点;

Cobalt Strike 添加了对在内存中反射加载 DLL 的自定义方式的支持,允许攻击人员自定义加载信标 DLL 的方式并添加规避技术。 Bobby Cooke 和 Santiago P 使用我在装载机中使用的 Cobalt Strike 的 UDRL 构建了一个隐形装载机 (BokuLoader)。 BokuLoader 实现了几种规避技术:

11.6 限制对 GetProcAddress() 的调用(通常是 EDR 挂钩 WINAPI 调用来解析函数地址,就像我们在第 4 部分中所做的那样);

11.7 AMSI和ETW绕过;

11.8  仅使用直接系统调用;

11.9 仅使用 RW 或 RX,没有 RWX (EXECUTE_READWRITE) 权限;

11.10 从内存中删除信标 DLL 标头;

请确保取消这两个定义的注释,以利用通过HellsGate和HalosGate的直接系统调用,并绕过ETW和AMSI(其实没有必要,因为我们已经禁用了ETW,并且没有将加载程序注入到另一个进程中)。

12. Malleable 配置文件中的 OpSec 配置

在你的 Malleable C2 配置文件中,确保配置了以下选项,这些选项限制了RWX标记内存的使用(可疑且容易检测),并在信标启动后清理 shellcode。

6.png

总结

结合这些技术,你可以绕过Microsoft Defender for Endpoint 和 CrowdStrike Falcon,且不会被检测到。

7.png

CrowdStrike Falcon 上的测试

本文翻译自:https://vanmieghem.io/blueprint-for-evading-edr-in-2022/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/lEZ6
如有侵权请联系:admin#unsafe.sh