AtomicBOFs:BOF执行的原子化测试框架

admin 2026-05-01 05:03:46 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档介绍了AtomicBOFs框架,这是一种基于BOFInversions和BOFCocktails设计理念的原子化测试方案,通过将API实现和规避技术直接嵌入BOF文件,使其成为不依赖C2代理的自包含单元。项目核心提供BOF加载器harness和配置规范,支持检测工程师直接运行第三方BOF收集遥测数据,并可通过挂钩技术修改默认行为,为构建和验证检测规则提供便捷测试环境。 综合评分: 82 文章分类: 红队,安全工具,渗透测试,安全开发


cover_image

Atomic BOFs:BOF 执行的原子化测试框架

securitainment

2026年4月29日 17:48 中国香港

在小说阅读器读本章

去阅读

| 原文链接 | 作者 | | — | — | | https://rastamouse.me/atomic-bofs/ | crystal-palace |

tl;dr

受 Red Canary 的 Atomic Red Team 启发,’Atomic BOFs’ 是我对一种实现模式的探索,旨在简化 Beacon Object Files 的检测工程工作。

(https://github.com/rasta-mouse/atomic-bofs)

本项目基于两个设计理念。

BOF Inversions

BOF 是一种对象(COFF)文件,由 C2(如 Cobalt Strike)加载,并链接至 Win32 API 及部分内部 Beacon API。这些内部 API 的实现位于 Beacon 代理内部,因此 BOF 的执行依赖于该代理。目前已有的 COFF/BOF 加载器项目几乎无一例外——例如 TrustedSec 的 COFFLoader——都包含一个 兼容层,负责实现上述函数以供 BOF 调用,替代 Beacon 代理中的原始实现。BOF Inversions 颠覆了这一模式,将 API 实现直接内嵌至 BOF 本身,而非保留在加载器或 C2 代理中。

BOF Cocktails

Cobalt Strike 的 BOF API 包含 BeaconVirtualAlloc等函数,其设计初衷是将 Beacon 的规避能力(如系统调用)开放给 BOF 使用。然而,如上所述,这种方式使 BOF 在规避层面依赖于 C2 代理。BOF Cocktails 将规避技法直接整合进 BOF 本身,扭转了这一依赖格局。

那又怎样?

这两种思路带来的结果是:BOF 突然成为了”自包含”单元,执行等效功能时无需依赖 C2 代理。进而,自包含单元作为测试用例也便捷得多。此方案的核心目标,是为检测工程师提供一种直接在测试/分析环境中执行 BOF 的途径,省去搭建完整 C2 环境的开销。

Harness 框架

该项目的核心组件是一个简单的 BOF 加载器,我将其称为 “harness”。它负责将 COFF 加载到内存中、调用其入口点并传递打包的参数。

#include<windows.h>
#include"loader.h"
#include"tcg.h"

DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc &nbsp; ( LPVOID, SIZE_T, DWORD, DWORD );
DECLSPEC_IMPORT BOOL &nbsp; WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD );

char&nbsp;_COFF_ [&nbsp;0&nbsp;] __attribute__ ( ( section (&nbsp;"coff"&nbsp;) ) );
char&nbsp;_ARGS_ [&nbsp;0&nbsp;] __attribute__ ( ( section (&nbsp;"args"&nbsp;) ) );

voidgo&nbsp;( )
{
&nbsp; &nbsp; RESOURCE * src &nbsp;=&nbsp;GETRESOURCE&nbsp;( _COFF_ );

char&nbsp;* code = KERNEL32$VirtualAlloc&nbsp;(&nbsp;NULL,&nbsp;PicoCodeSize&nbsp;( src->val ), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE );
char&nbsp;* data = KERNEL32$VirtualAlloc&nbsp;(&nbsp;NULL,&nbsp;PicoDataSize&nbsp;( src->val ), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE );

&nbsp; &nbsp; IMPORTFUNCS funcs;
&nbsp; &nbsp; funcs.GetProcAddress&nbsp;= GetProcAddress;
&nbsp; &nbsp; funcs.LoadLibraryA&nbsp; &nbsp;= LoadLibraryA;

PicoLoad&nbsp;( ( IMPORTFUNCS * ) &funcs, src->val, code, data );

&nbsp; &nbsp; RESOURCE * args =&nbsp;GETRESOURCE&nbsp;( _ARGS_ );

&nbsp; &nbsp; ( ( BOFMAIN )&nbsp;PicoEntryPoint&nbsp;( src->val, code ) ) ( args->val, args->len );
}

loader.c

其对应的 spec 文件期望接收 $COFF和 $ARGS变量,其中 $COFF为 COFF 文件的字节数据,$ARGS为打包的参数。此外还有一个名为 %entrypoint的变量,用于存储 BOF 的入口点(通常为 go)。

x64:
&nbsp; &nbsp; load&nbsp;"bin/loader.x64.o"
&nbsp; &nbsp; &nbsp; &nbsp; make pic +gofirst +optimize
&nbsp; &nbsp; &nbsp; &nbsp; run&nbsp;"services.spec"
&nbsp; &nbsp; &nbsp; &nbsp; mergelib&nbsp;"libtcg.x64.zip"

push$COFF
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; make object +optimize
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; run&nbsp;"bof.spec"%entrypoint
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; export
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; preplen
link"coff"

push$ARGS
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; preplen
link"args"

&nbsp; &nbsp; export

BOF Inversion 的魔法发生在 bof.spec中。首先,bofapi.c实现了 BOF API,例如 BeaconDataParse

void&nbsp;BeaconDataParse&nbsp;( datap&nbsp;*&nbsp;parser,&nbsp;char*&nbsp;buffer, int size )
{
&nbsp; &nbsp; parser->original&nbsp;=&nbsp;buffer;
&nbsp; &nbsp; parser->buffer &nbsp;&nbsp;=&nbsp;buffer;
&nbsp; &nbsp; parser->length &nbsp;&nbsp;=&nbsp;size;
&nbsp; &nbsp; parser->size &nbsp; &nbsp;&nbsp;=&nbsp;size;
}

... etc ...

bofapi.c

spec 文件将这些函数合并到 BOF 中,并将对它们的调用重定向。

x64:
&nbsp; &nbsp; entry&nbsp;%1

&nbsp; &nbsp; load&nbsp;"bin/bofapi.x64.o"
&nbsp; &nbsp; &nbsp; &nbsp; merge

&nbsp; &nbsp; attach&nbsp;"$BeaconDataExtract""BeaconDataExtract"
&nbsp; &nbsp; attach&nbsp;"$BeaconDataLength""BeaconDataLength"
&nbsp; &nbsp; attach&nbsp;"$BeaconDataParse""BeaconDataParse"
&nbsp; &nbsp; attach&nbsp;"$BeaconDataPtr""BeaconDataPtr"
&nbsp; &nbsp; attach&nbsp;"$BeaconDataInt""BeaconDataInt"
&nbsp; &nbsp; attach&nbsp;"$BeaconDataShort""BeaconDataShort"

&nbsp; &nbsp; attach&nbsp;"$BeaconFormatAlloc""BeaconFormatAlloc"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatReset""BeaconFormatReset"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatAppend""BeaconFormatAppend"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatPrintf""BeaconFormatPrintf"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatToString""BeaconFormatToString"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatFree""BeaconFormatFree"
&nbsp; &nbsp; attach&nbsp;"$BeaconFormatInt""BeaconFormatInt"
&nbsp; &nbsp; attach&nbsp;"$BeaconPrintf""BeaconPrintf"
&nbsp; &nbsp; attach&nbsp;"$BeaconOutput""BeaconOutput"

bof.spec

我的设计意图是:项目的使用者无需修改与 harness 相关的任何内容,任何变更均可在其他 spec 文件中进行(如下文所述)。

测试 BOF

作为简单的测试,我加入了一小段 C 代码,使用 BeaconPrintf打印一条消息。

#include<windows.h>
#include"beacon.h"

voidgo&nbsp;(&nbsp;char&nbsp;* args,&nbsp;int&nbsp;len )
{
&nbsp; &nbsp; datap parser;
BeaconDataParse&nbsp;( &parser, args, len );

char&nbsp;* message =&nbsp;BeaconDataExtract&nbsp;( &parser,&nbsp;NULL&nbsp;);

BeaconPrintf&nbsp;( CALLBACK_OUTPUT,&nbsp;"%s\n", message );
}

test.c

config.spec文件用于配置 harness,需要在此处设置 $COFF$ARGS和 %entrypoint变量。

x64:
&nbsp; &nbsp; # load your coff
&nbsp; &nbsp; load&nbsp;$COFF"bin/test.x64.o"

&nbsp; &nbsp; #&nbsp;pack&nbsp;args
pack$ARGS"iz""16""Hello World x64"

&nbsp; &nbsp; #&nbsp;if&nbsp;none,&nbsp;pack&nbsp;0
&nbsp; &nbsp; #&nbsp;pack$ARGS"b""0x0"

&nbsp; &nbsp; # set desired entry point
&nbsp; &nbsp; setg&nbsp;"%entrypoint""go"

config.spec

要生成最终的 PIC,使用 Crystal Palace 的 piclink工具:

atomic-bofs$ ./piclink harness/loader.spec x64 test.x64.bin @test/config.spec

我们将框架的 loader.spec作为主 spec 文件,同时使用”测试”用的 config.spec文件向其中追加配置。输出结果为一个 .bin文件,可用任意 shellcode 执行器运行。

atomic-bofs$&nbsp;/mnt/c/Tools/cpl/demo/run.x64.exe test.x64.bin
Allocated&nbsp;0x00000199df9f0000 (1611bytes)&nbsp;forPIC
Read&nbsp;1611bytesfrom&nbsp;test.x64.bin. Press&nbsp;'enter'&nbsp;to&nbsp;continue.

BeaconOutput[0]: Hello World x64

第三方 BOFs

项目中的另一个示例使用了来自 TrustedSec 的 CS-Situational-Awareness-BOF 仓库中的 whoami BOF,展示了如何通过 BOF Cocktails 修改 BOF 的默认行为。

hooks.c包含一个针对 KERNEL32$GetCurrentProcess的 hook:

#include<windows.h>

HANDLE&nbsp;_GetCurrentProcess&nbsp;( )
{
/* return pseudo handle directly */
return&nbsp;( HANDLE ) ( -1&nbsp;);
}

hooks.c

hooks.spec文件将该函数合并至 COFF,并对 Win32 API 调用进行挂钩。

x64:
&nbsp; &nbsp; load&nbsp;"../bin/hooks.x64.o"
&nbsp; &nbsp; &nbsp; &nbsp; merge

&nbsp; &nbsp; attach&nbsp;"KERNEL32$GetCurrentProcess""_GetCurrentProcess"

hooks.spec

最后,config.spec文件使用了 Crystal Palace 的 setgresolve和 before命令。

x64:
&nbsp; &nbsp; load&nbsp;$COFF"whoami.x64.o"
pack$ARGS"b""0x0"
&nbsp; &nbsp; setg&nbsp;"%entrypoint""go"

&nbsp; &nbsp; setg&nbsp;"%hooks""hooks.spec"
&nbsp; &nbsp; resolve&nbsp;"%hooks"

&nbsp; &nbsp; before&nbsp;"export": run&nbsp;%hooks

config.spec

本质上,我们挂钩了 Crystal Palace 的 export命令,在最终导出执行前强制处理 hooks.spec。这是一个非常不错的解决方案——无需对框架的核心 spec 文件做任何修改。

若需运行多个 spec 文件,也可以叠加多条 before命令。

setg&nbsp;"%spec1""spec1.spec"
setg&nbsp;"%spec2""spec2.spec"

resolve&nbsp;"%spec1"
resolve&nbsp;"%spec2"

before&nbsp;"export": run&nbsp;%spec1
before&nbsp;"export": run&nbsp;%spec2

💡

Raffi 向我演示了一种将 before与 foreach结合的技巧,但实现稍显复杂,相比之下这种叠加方式更便于理解。

结论

这是我初次尝试为 BOF 执行提供可重复的原子测试单元。目标是提供一种在 C2 框架之外运行 BOF 的简便方式,同时保持完整功能。

检测工程师可以将”原版 BOF”作为基准运行,收集遥测数据以构建检测规则;随后融入规避技术来模拟高级威胁,以验证并改进这些检测规则。

当然,我并不是蓝队成员,所以这一切也许都是白费功夫 😄


免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:securitainment 《Atomic BOFs:BOF 执行的原子化测试框架》

评论:0   参与:  0