开卷有益 · 不求甚解
几年前,Cobalt Strike 添加了一个新功能,称为“信标对象文件”(BOF)。这些提供了一种使用新功能扩展信标代理的方法,也许是为了响应您在探索环境后发现的条件。从那时起,社区创建了许多 BOF 来涵盖许多常见场景,我们一直在利用其中一些来更密切地模拟对手在目标上的行动。
https ://github.com/nettitude/RunOF
在这样做的同时,我们希望有一种方法可以帮助我们更轻松地调试和测试我们自己的 BOF,并在我们使用的所有工具中使用它们。因此,我们推出了 RunOF——一个允许您在 Cobalt 代理之外以及在PoshC2中运行 BOF 的工具。
该项目的目的是创建一个 .NET 应用程序,该应用程序能够加载任意 BOF、向它们传递参数、执行它们并收集和返回任何输出。此外,.NET 应用程序应该能够在 C2 框架中运行,例如 PoshC2。
整个过程与我们最近发布的RunPE工具所使用的大致相似,因此 RunOF 工具使用了一些相同的技术。高级流程如下:
开发 RunOF 的第一步是详细了解 Beacon 对象文件是什么。为此,我们查看了公开可用的文档,以及社区制作的一些示例 BOF。
BOF 包含一个导出的例程(通常是一个名为“go”的函数——但它可以是任何你喜欢的),以及对例程的调用,例如BeaconPrintf
将数据返回给代理。还有一个约定允许通过调用*DLL_nameVirtualAlloc`.
顾名思义,BOF 是“对象”文件,其中包含一些关于如何导入符号的规范,以便信标加载器可以动态解析它们。目标文件是您在编译代码时最有可能遇到的中间文件,通常带有.o
扩展名。例如,当您开发 C 应用程序时,实际上会发生多个步骤——通常由 Makefile 或您正在使用的其他构建系统抽象出来。首先是预处理和编译;这些是采用人类可读的代码,在将其转换为可由处理器执行的机器代码之前处理#defines 和#includes。第二个是链接:此步骤获取上一步的所有输出并解析它们之间的任何引用,然后构造一个允许操作系统加载和执行二进制文件的可执行文件。
目标文件是第一个预处理和编译阶段的输出,因此它包含未链接的可重定位机器代码,以及调试和其他元数据。在 Windows(我们以 RunOF 为目标)上,对象文件使用 Microsoft 记录为 PE 格式一部分的通用对象文件格式 (COFF) ( https://docs.microsoft.com/en-us/windows/win32/调试/pe格式)。
COFF 文件由包含有关文件本身、符号和字符串表的信息的标头集合以及包含要执行的代码、所需数据以及如何将数据加载到内存中的信息的部分集合组成.
每个部分的内容有点超出本文的范围,但我们需要使用的关键是:
除了节,我们需要解析的文件的一个重要部分是符号表。这给出了我们已实现的函数以及我们期望从其他 DLL 导入的函数在文件中的位置。
例如,在上面的屏幕截图中,我们可以看到 go 符号位于“SECT1”(即**.text**部分)中,而诸如“UNDEF”之类的符号__imp_BeaconPrintf
意味着我们需要提供它们。通常这将由链接器完成,作为我们上面概述的整体构建过程的一部分,但我们必须在加载程序中执行该步骤。
加载过程遵循以下高级步骤:
该过程中最复杂的部分可能是解析重定位条目。编译代码时,编译器不知道应用程序运行时项目(例如函数、变量或数据)将位于内存中的什么位置——这些值可能在其他目标文件中,或者需要从操作系统加载API。因此,编译器有一组体系结构特定规则可供选择,允许它指定在链接时需要“填充”地址。
上图中有一个小子集,完整列表相当大。许多似乎没有在实践中使用(例如,像 Ghidra 这样的工具不支持它们)所以我们只实现了在最常见的编译器中看到的那些。实际上,重定位条目具有三个字段 - 重定位引用的符号、要应用重定位的地址和重定位类型。例如,列表中的最后一个 ( IMAGE_REL_AMD64_REL32
) 意味着加载器必须找到引用符号的地址,计算从重定位位置到该符号的 32 位相对地址,并将值写入重定位地址。
一旦应用了重定位,内存权限设置正确并且位于 BOF 的入口点可以被执行。
我们希望它在 .NET 中运行,以便在我们如何将其用作其他 C2 工具的一部分时给予我们更大的灵活性。这带来了挑战,因为 .NET 是一种解释性语言,因此我们编写的代码将在公共语言运行时 (CLR) 中运行。幸运的是,.NET 提供了使用称为Interop的非托管代码的功能,它允许我们操作本机内存以加载 BOF,然后调用本机 Windows API 函数来执行它。我们使用与为 RunPE 开发的相同技术在新线程中启动代码,并且我们安装了一个异常处理程序以防止任何有缺陷的 BOF 使整个进程崩溃。
我们面临的另一个挑战是将 BOF 产生的任何输出返回到 .NET 父应用程序,以便可以通过 C2 通道返回。Cobalt 代理定义了一组Beacon*
函数(例如BeaconPrintf
),BOF 可以调用这些函数将数据传回植入体。这些需要实现为 BOF 能够调用它们的本机代码,并且我们需要有一种方法在本机代码和 .NET 父级之间传递它们产生的数据。为了实现这一点,我们有一个小的 'beacon_functions' COFF 文件,它首先由 .NET 加载程序加载。这包含Beacon*
将其输出写入缓冲区的函数的实现,该缓冲区已增长以包含 BOF 输出的数据。当实际 BOF 被加载时,已经加载的地址Beacon*
然后可以在符号解析步骤中提供函数。一旦 BOF 执行完成,.NET 父级可以从内存缓冲区中读取以检索生成的任何输出。
最后一个难题是我们如何为 BOF 文件提供参数。在 Cobalt 中,BOF 加载有一个“攻击者”脚本,该脚本允许您将不同类型的参数传递给 BOF 文件,并使用以下定义的数据 API 检索它们beacon.h
:
为了允许 BOF 在 RunOF 中接受参数,我们必须在应用程序的命令行中接受它们,然后以一种加载后可以被原生代码使用的方式提供它们。为此,我们使用自定义类型、长度、值 (TLV) 格式将它们序列化到共享内存缓冲区中。然后,当 BOF 调用我们的数据 API 的内部实现时,可以从该缓冲区中读取:
这种方法有两个重要的警告:
您可以在项目 README 中看到更多详细信息,命令行帮助提供了摘要:
除了运行 BOF,RunOF 项目还可用于帮助开发新的 BOF 能力。项目文件包含一个“调试”构建目标——如果使用它,加载程序将在执行 BOF 之前暂停以允许附加调试器。您还将获得有关加载过程本身的大量信息。
我们希望 RunOF 为红队提供了一种在其他 C2 框架中使用现有 BOF 功能的方法,并帮助开发新的和创新的 BOF 功能。RunOF 项目可以在下面的链接中找到。
https ://github.com/nettitude/RunOF
近期阅读文章
,质量尚可的,大部分较新,但也可能有老文章。开卷有益,不求甚解
,不需面面俱到,能学到一个小技巧就赚了。译文仅供参考
,具体内容表达以及含义, 以原文为准
(译文来自自动翻译)尽量阅读原文
。(点击原文跳转)每日早读
基本自动化发布(不定期删除),这是一项测试
最新动态: Follow Me
微信/微博:
red4blue
公众号/知乎:
blueteams