[翻译]CobaltStrike 使用Beacon Object Files(BOF)进行 Direct Syscalls

> 原文链接:https://outflank.nl/blog/2020/12/26/direct-syscalls-in-beacon-object-files/
> 原文作者:Cornelis de Plaa

在这篇文章中，我们将探讨在Cobalt Strike使用BOF进行直接系统调用(direct system calls)。具体来说：

1. 说明如何在Cobalt Strike BOF中使用直接系统调用来规避典型的AV和EDR检测。
2. 发布InlineWhispers：一个脚本，使在BOF代码中使用Direct Syscalls更加容易。
3. 提供概念验证BOF代码，该代码可用于通过修补LSASS进程内存来启用WDigest凭据缓存和规避凭据保护。

PoC的源代码可以在这里找到：https://github.com/outflanknl/WdToggle

InlineWhispers的源代码可以在这里找到：https://github.com/outflanknl/InlineWhispers

# Beacon Object Files
Cobalt Strike最近引入了一个新的代码执行概念，叫做 [Beacon Object Files](https://www.cobaltstrike.com/help-beacon-object-files)（以下简称为BOF）。这使Cobalt Strike可以在Beacon进程中执行一小段已编译的C代码。

这有什么好处？最重要的是，我们摆脱了名为`fork＆run`的概念。在BOF出来之前，`fork＆run`是在Cobalt Strike中运行作业的默认机制。这意味着为了执行大多数开发后功能，将启动一个牺牲进程（使用spawnto参数指定），随后将攻击的payload作为反射DLL注入该过程。从AV、EDR的角度来看，它具有可以检测到的各种特征，例如进程产生，进程注入和内存中的反射DLL。在许多现代环境中，`fork＆run`很容易演变成OPSEC(行动安全Operations security)的灾难。使用BOF，我们可以在Beacon当前过程的上下文中运行与位置无关的已编译代码，这更加隐蔽。

尽管BOF概念的引入对于在后渗透开发中绕过AV、EDR迈出了重大一步，但我们仍然需要面对AV、EDR产品HOOK系统API的问题。在2019年6月，我们发布了[有关Direct Syscalls的博客文章](https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/)，并展示了一个如何使用它绕过AV、EDR软件的示例。到目前为止，我们还没有看到Direct Syscalls在BOF的利用，因此我们决定编写自己的实现并在此博客文章中分享我们的经验。

# Direct syscalls和BOF的实用性
现在很多红队都熟悉关于Direct Syscalls对绕过API HOOK的概念。在我们以前对[Direct Syscalls](https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/)的文章中，我们展示了如何在Visual Studio中利用Microsoft汇编程序（MASM）在C/C++项目中包含 Direct Syscalls。当我们构建一个包含汇编代码的Visual Studio项目时，它将使用汇编器和C编译器生成两个目标文件，并将所有片段链接在一起以形成一个可执行文件。

要创建BOF文件，我们使用C编译器来生成单个目标文件。如果要在BOF项目中包含汇编代码，则需要进行内联汇编以生成单个目标文件。不幸的是，Visual Studio不支持x64处理器的内联汇编，因此我们需要另一个C编译器，它支持x64处理器的内联汇编。

# Mingw-w64和内联ASM
Mingw-w64 是GCC编译器的Windows版本，可用于创建32位和64位Windows应用程序。它可以在Windows，Linux或任何其他基于Unix的操作系统上运行。最重要的是，它甚至对于x64处理器也支持内联汇编。因此，现在我们需要了解如何在BOF源代码中包含汇编代码。

如果我们看一下Mingw-w64或GCC编译器的手册页，我们会注意到它支持使用`-masm=dialect`语法进行汇编。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/ed963295-0b0f-9700-78b1-b83ff60983bb.png)

使用intel语言，我们能够像在Visual Studio中使用Microsoft Assembler一样通过相同的语言编写汇编代码。要将内联汇编包含在我们的代码中，我们可以简单地使用以下汇编器模板语法：

```
        asm("nop \n  "
            "nop \n  "
            "nop")
```

1. 起始asm关键字是`asm`或`__asm__`
2. 指令必须用换行符分隔。

有关GCC汇编程序语法的更多信息，请参见以下指南：https://www.felixcloutier.com/documents/gcc-asm.html#assembler-template

# 从`__asm__`到BOF
下面的示例显示使用内联汇编(inline-assembly)的`NtCurrentTeb()`的自定义版本。这个例子可用于返回指向当前线程的Thread Environment Block (TEB)的指针，然后可将其用于解析为指向ProcessEnvironmentBlock（PEB）的指针：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/3683a240-9b74-21b1-fe6a-ade2ed6a04ac.png)

为了使此汇编函数在我们的C代码中可用并声明其名称，返回类型和参数，我们使用`EXTERN_C`关键字。该预处理程序宏指定该函数在其他位置定义，具有C链接并使用C语言调用约定。这种方法还可以用于在我们的代码中包括汇编系统调用函数。只需将以汇编形式编写的系统调用转换为汇编器模板语法，使用EXTERN_C关键字添加函数定义，然后将其保存在头文件中即可，该文件可以包含在我们的项目中。

尽管在头文件中实现函数是完全有效的，但这并不是最佳实践。然而，使用`-o`选项编译目标文件只允许我们使用一个源文件，因此为了不使我们的主源文件带有汇编函数，我们将它们放在单独的头文件中。

要编译包含内联汇编的BOF源代码，我们使用以下编译器语法：

```
x86_64-w64-mingw32-gcc -o bof.o -c bof.c -masm=intel 
```
# WdToggle
为了演示整个概念，我们编写了概念验证代码，其中包括使用内联汇编的Direct Syscalls，并且可以编译为BOF文件。

此代码显示了如何通过在Lsass进程（wdigest.dll模块）中将g_fParameter_UseLogonCredential全局参数切换为1来启用WDigest凭据缓存。此外，可以通过在lsass进程中将g_IsCredGuardEnabled变量切换为0来绕过凭据保护（如果启用）。

这两个技巧都使我们能够在LSASS中使纯文本密码再次可见，因此可以使用Mimikatz抓取它们。使用 `UseLogonCredential`补丁后，您只需要用户锁定和解锁其会话即可使明文密码凭据再次可用。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/8b0a792e-d5c6-6d00-fc7f-1403acee476f.png)

此PoC基于Hydra团队`@_xpn_`和`@N4kedTurtle`的以下精彩博客文章。这些博客是必读且包含所有必要的详细信息：

1. [探索Mimikatz –第1部分– WDigest](https://blog.xpnsec.com/exploring-mimikatz-part-1/)
2. [绕过凭据守卫](https://teamhydra.blog/2020/08/25/bypassing-credential-guard/)

这两个博文都包含用于patch LSASS的PoC代码，因此从这个角度来看，我们的代码并不是什么新东西。我们的PoC以此工作为基础，并且仅演示了如何利用BOF中的Direct Syscalls来提供一种更安全的与LSASS流程进行交互并绕过Cobalt Strike的API hook的OPSEC安全方式。

# 补丁程序限制
此POC是内存补丁，因此重启后必须重新运行代码。此外，wdigest.dll模块中`g_fParameter_UseLogonCredential`和` g_IsCredGuardEnabled`全局变量的内存偏移量可能会在Windows版本和修订版之间发生变化。我们为代码中的不同内部版本提供了一些偏移，但是这些偏移会在将来的版本中更改。您可以添加自己的版本偏移量，可以使用Windows调试器工具找到该偏移量。（译者注：不同版本的Windows需要patch的偏移量不同）

# 防御
为了通过LSASS内存访问来检测凭证盗窃，我们可以使用Sysmon之类的工具来监视打开LSASS句柄的处理。我们可以监视访问LSASS进程的可疑进程，从而创建遥测来检测可能的凭证转储活动。

当然，还有更多选项可用于检测凭据盗用，例如，使用Windows Defender ATP之类的高级检测平台。但是，如果您没有使用这些新颖平台的预算，那么Sysmon是可以帮助填补空白的免费工具。

# InlineWhispers
在发布直接系统调用博客文章几个月后，@Jackson_T发布了一个名为SysWhispers的出色工具。

> “SysWhispers helps with evasion by generating header/ASM files implants can use to make direct system calls”.
> “SysWhispers通过生成植入程序可用来进行直接系统调用的header/ASM文件来帮助规避AV”。

这是一个很棒的工具，它可以自动为任何系统调用生成标头/ ASM对，然后可以在定制的Red Teaming工具中使用它。

该工具生成的.asm输出文件可以在Microsoft Studio中使用Microsoft宏汇编器使用。 如果要在BOF项目中使用从SysWhispers输出生成的Direct Syscalls，则需要某种转换，以便它们与汇编器模板语法匹配。 

我们的同事@_DaWouw 编写了一个Python脚本，可用于将SysWhispers生成的.asm输出文件转换为适合BOF项目的输出文件。

它转换输出以匹配汇编模板语法，因此可以从您的BOF代码中使用这些函数。我们可以手动输入在BOF中使用了哪些系统调用，以防止包括未使用的系统功能。该脚本可在Github页面上的InlineWhispers存储库中找到：

https://github.com/outflanknl/InlineWhispers

# 概要
在此博客中，我们展示了如何在Cobalt Strike BOF中使用`Direct Syscalls`。要使用 `Direct Syscalls`，我们需要使用汇编程序模板语法编写汇编程序，因此我们可以将汇编函数包括在内联汇编中。Visual Studio不支持x64处理器的内联汇编，但是幸运的是Mingw-w64支持。

为了演示在BOF中进行Direct Syscalls的用法，我们编写了概念验证代码，可用于启用WDigest凭据缓存。此外，我们编写了一个名为InlineWhispers的脚本，该脚本可用于将SysWhispers生成的.asm输出转换为适合BOF项目的内联汇编头文件。

我们希望该博文有助于理解如何在BOF项目中实施Direct Syscalls以提高OPSEC安全性。