内网渗透神器CobaltStrike之BOF编写(十一)

前言

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里

image-20230818102012570

在函数定义里可以发现有加上KERNEL32$这种前缀,为何CobaltStrike的Bof要使用这种前缀呢,个人推测原因有以下两点:

  • 直接调用: 通过这种方式,BOF可以直接调用DLL中的原生函数,而不需要在BOF的导入表中声明它们。这有助于BOF保持小型和轻量级。
  • 隐蔽性: 使用这种前缀来直接调用API函数可以增加对某些安全解决方案的隐蔽性,因为它们可能不会检测到这些不常见的调用模式

beacon.h定义了与Cobalt Strike Beacon交互所需要的各种数据类型和函数

image-20230818102755777

具体来说,这个文件包含了以下部分:

  • 数据API:这部分定义了一个数据结构(datap),以及一些函数,这些函数用于解析和操作这种数据结构。datap 结构包含原始缓冲区的指针,当前缓冲区的指针,剩余的数据长度,以及缓冲区的总大小。
  • 格式API:这部分定义了一个格式化数据的结构(formatp),以及一些函数,这些函数用于分配、重置、释放和操作这种数据结构。formatp 结构与 datap 结构非常相似,但它是用于格式化数据,而不是解析数据。
  • 输出函数:这部分定义了一些宏和函数,用于向 Beacon 的输出流中输出数据。函数 BeaconPrintfBeaconOutput 可以分别用于格式化输出和二进制输出。
  • 令牌函数:这部分定义了一些函数,用于在 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语言一样

image-20230818103047212

使用步骤

将下载的模板文件解压至visualstudio的模板目录(%UserProfile%\Documents\Visual Studio 2022\Templates\ProjectTemplates),随后重启VisualStuido

image-20230726225541691

在创建项目时选择类型为Beacon Object File的项目

image-20230726233950177

在头文件列表可以看到beacon.hbofdefs.h

image-20230726234108233

打开项目的Batch生成,勾选上BOF配置。配置管理器的编译环境也需设置为BOF

image-20230726235028577

以下是一个简单的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
image-20230727091013243

踩坑记录

1.bof应尽量使用C语言

编写bof时应该尽量使用C语言来编写,如果你使用C++来编写很可能会出现如下图所示的错误:Could not resolve API

这是因为C++为了支持函数重载,会对函数名进行修饰,从而导致函数的实际名称与你在代码中看到的名称不同,因此当你尝试在BOF解析某个函数时,可能会找不到它

image-20230818094623207

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

image-20230818100321336

3.报错:Unknown symbol ‘__chkstk’

当你执行bof项目时可能会遇到如下图所示的错误,其根本原因出在你bof的源码上,当你的源代码使用大量的局部变量或数组时,编译器会插入一个__chkstk调用来确保有足够的堆栈空间。而在BOF中,由于我们没有完整的C运行时环境,所以这个函数是不存在的

image-20230819221112051

当时我的bof源码定义了个特别大的局部数组,如下代码所示

WCHAR output[4096] = {0};

为了解决这个问题,应该减少局部变量的大小,使用动态内存分配来创建所需的数组,比如HeapAllocHeapFree,更正后的代码如下:

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
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值