前言
Beacon Object File(BOF) 从Cobalt Strike4.1开始所添加的新功能,它允许你使用C语言编写扩展来扩展Beacon的功能。这些扩展可以在运行时直接加载到Beacon的内存中并执行,无需在目标机器的磁盘上创建任何文件
BOF的一个关键特性是它的运行时环境非常有限。它不能直接调用Windows API,而只能通过Cobalt Strike提供的一组函数来与操作系统进行交互。这样做的原因是为了防止BOF在运行时出错导致Beacon崩溃。然而,尽管这种限制使得编写BOF相对复杂一些,但BOF仍然是一个非常强大的工具,可以用来添加各种自定义功能到Beacon中
一旦BOF编译完成,你可以通过Beacon的"inline-execute"命令来加载并执行BOF。这个命令会将BOF上传到Beacon的内存中并立即执行它,无需将BOF写入到磁盘
环境准备
Github上有很多开发BOF的项目,这里我推荐以下两个项目,前者是开发bof的VisualStuido项目模板,此模板对BOF函数进行了宏定义,这样我们可以像使用C语言一样来开发BOF项目;后者是开发BOF项目所需的头文件,如果此项目更新了,我们可以将此项目的头文件替换掉模板项目的头文件
-
bof的visual studio模板:https://github.com/securifybv/Visual-Studio-BOF-template
-
bof所需的头文件:https://github.com/trustedsec/CS-Situational-Awareness-BOF
不过我更加推荐使用evilashz师傅整理好的模板:https://github.com/evilashz/Visual-Studio-BOF-template
此模板通过DLL名称将WindowsApi函数进行了归类,例如kernel32.dll的导出函数定义就写在kernel32.h里
在函数定义里可以发现有加上KERNEL32$
这种前缀,为何CobaltStrike的Bof要使用这种前缀呢,个人推测原因有以下两点:
- 直接调用: 通过这种方式,BOF可以直接调用DLL中的原生函数,而不需要在BOF的导入表中声明它们。这有助于BOF保持小型和轻量级。
- 隐蔽性: 使用这种前缀来直接调用API函数可以增加对某些安全解决方案的隐蔽性,因为它们可能不会检测到这些不常见的调用模式
beacon.h定义了与Cobalt Strike Beacon交互所需要的各种数据类型和函数
具体来说,这个文件包含了以下部分:
- 数据API:这部分定义了一个数据结构(
datap
),以及一些函数,这些函数用于解析和操作这种数据结构。datap
结构包含原始缓冲区的指针,当前缓冲区的指针,剩余的数据长度,以及缓冲区的总大小。 - 格式API:这部分定义了一个格式化数据的结构(
formatp
),以及一些函数,这些函数用于分配、重置、释放和操作这种数据结构。formatp
结构与datap
结构非常相似,但它是用于格式化数据,而不是解析数据。 - 输出函数:这部分定义了一些宏和函数,用于向 Beacon 的输出流中输出数据。函数
BeaconPrintf
和BeaconOutput
可以分别用于格式化输出和二进制输出。 - 令牌函数:这部分定义了一些函数,用于在 Beacon 中使用和操作 Windows 访问令牌。这些函数包括
BeaconUseToken
(使用指定的令牌)、BeaconRevertToken
(恢复到之前的令牌)、BeaconIsAdmin
(检查当前令牌是否具有管理员权限)。 - 注入函数:这部分定义了一些函数,用于在新的或已经存在的进程中注入 payload。这些函数包括
BeaconGetSpawnTo
(获取 Beacon 的 SpawnTo 设置)、BeaconInjectProcess
(在指定进程中注入 payload)、BeaconInjectTemporaryProcess
(在临时进程中注入 payload)、BeaconCleanupProcess
(清理进程信息)。 - 实用函数:这部分定义了一些实用函数,例如
toWideChar
(将一个字符串转换为宽字符字符串)。
bofdefs.h文件定义了许多函数,这些函数实际上是 Windows API 的宏定义,它们使得开发者能够在 Beacon Object File(BOF)中方便地调用 Windows API,就像使用C语言一样
使用步骤
将下载的模板文件解压至visualstudio的模板目录(%UserProfile%\Documents\Visual Studio 2022\Templates\ProjectTemplates
),随后重启VisualStuido
在创建项目时选择类型为Beacon Object File
的项目
在头文件列表可以看到beacon.h
和bofdefs.h
打开项目的Batch生成,勾选上BOF配置。配置管理器的编译环境也需设置为BOF
以下是一个简单的bof项目,用于实现向控制台输出字符串
- BOF入口:代码定义了BOF的入口函数
go
,当你在Cobalt Strike中使用inline-execute
命令加载并执行你的BOF时,这个函数将被调用。你可以在这个函数中添加你的BOF代码 - 非BOF入口:这部分代码定义了非BOF的入口函数
main
。当你在非Cobalt Strike环境中运行你的代码时,这个函数将被调用。你可以在这个函数中添加你的非BOF代码
include <windows.h>
include <stdio.h>
include "bofdefs.h"
pragma region error_handling
define print_error(msg, hr) _print_error(__FUNCTION__, __LINE__, msg, hr)
BOOL _print_error(char* func, int line, char* msg, HRESULT hr) {
ifdef BOF
BeaconPrintf(CALLBACK_ERROR, "(%s at %d): %s 0x%08lx", func, line, msg, hr);
else
printf("[-] (%s at %d): %s 0x%08lx", func, line, msg, hr);
endif // BOF
return FALSE;
}
pragma endregion
ifdef BOF
void go(char* buff, int len) {
BeaconPrintf(CALLBACK_OUTPUT, "Hello, World!");
}
else
void main(int argc, char* argv[]) {
}
endif
项目生成后会生成一个.obj
文件,也就是编译未链接的目标文件,在CobaltStrike中你可以使用inline-execute
命令来加载并执行你的.obj文件,此命令将你的 .obj
文件加载到 Beacon 的内存中,然后调用你的 go
函数,命令格式如下所示
beacon> inline-execute your_bof.obj
踩坑记录
1.bof应尽量使用C语言
编写bof时应该尽量使用C语言来编写,如果你使用C++来编写很可能会出现如下图所示的错误:Could not resolve API
这是因为C++为了支持函数重载,会对函数名进行修饰,从而导致函数的实际名称与你在代码中看到的名称不同,因此当你尝试在BOF解析某个函数时,可能会找不到它
2.inline-execute无法接收参数?
如下是一个简单的bof项目源码,用于在beacon命令行输出bof接收的参数
void go(char* buff, int len) {
// datap是BOF框架中定义的结构体,用于解析从Beacon接收到的数据
// 当你在Beacon执行BOF时,可以向BOF传递一些参数,这些参数会被打包成一个字节流
// BOF会使用datap结构体来解析这个字节流,从而获取到传递的参数
datap parser;
wchar_t* username;
wchar_t* password;
// 初始化datap结构体变量(parser),用于解析从Beacon接收到的字节流(buff)
BeaconDataParse(&parser, buff, len);
username = (wchar_t*)BeaconDataExtract(&parser, NULL);
password = (wchar_t*)BeaconDataExtract(&parser, NULL);
BeaconPrintf(CALLBACK_OUTPUT, "Extracted username: %S", username);
BeaconPrintf(CALLBACK_OUTPUT, "Extracted password: %S", password);
}
但是从输出结果来看,bof并没有接收到参数,而是显示(null)。所以说,如果要给BOF传递参数,还是得使用CNA脚本的bof_pack
函数来打包参数,然后使用beacon_inline_execute
函数来执行BOF
3.报错:Unknown symbol ‘__chkstk’
当你执行bof项目时可能会遇到如下图所示的错误,其根本原因出在你bof的源码上,当你的源代码使用大量的局部变量或数组时,编译器会插入一个__chkstk
调用来确保有足够的堆栈空间。而在BOF中,由于我们没有完整的C运行时环境,所以这个函数是不存在的
当时我的bof源码定义了个特别大的局部数组,如下代码所示
WCHAR output[4096] = {0};
为了解决这个问题,应该减少局部变量的大小,使用动态内存分配来创建所需的数组,比如HeapAlloc
和HeapFree
,更正后的代码如下:
WCHAR *output = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096 * sizeof(WCHAR));
参考链接
- http://mp.weixin.qq.com/s?__biz=MzI5NzU0MTc5Mg==&mid=2247484673&idx=1&sn=d1628ed1f77c638ba414185bfeabf8ee&chksm=ecb2cccedbc545d89bd098632be7fb23c273e585a4f2167089b39d483c42deac13b6078ba3b5&scene=126&sessionid=1658057738&key=ffca75888bc216b14d1a0902587e61caf0fefe2cfd31cfccaef08baab2efb4a9952f320749796d966ba2829cd33e4a301cc25916b74879ea9c6bd6de9d2f06ca9beb159028a59e4ce006bc8276b59ee20d18fba2db40dbeccec3351295fad8192f098aee34bfed114a7c0c700ba46bc16f0a9a660f7801c96a668a64fd5a1a7c&ascene=15&uin=MTA3Mzc3OTIzNQ%3D%3D&devicetype=Windows+Server+2016+x64&version=6307001e&lang=zh_CN&session_us=gh_5b8184332bc1&exportkey=AaYcTeWdxPQAKdPy7RMIrxs%3D&acctmode=0&pass_ticket=H5DatfK1H7UD%2FQIL%2B8Md2%2BlWZOBY12u%2F%2FD4TQgyDk5zYF8C5%2BgX4U3zhXOnsq%2BtU&wx_header=0&fontgear=2