Beacon Object File(BOF) cs 4.1后添加的新功能,
Beacon在接收执行obj前,Cobalt Strike会先对这个obj文件进行一些处理,比如解析obj文件中一些需要的段.text,.data,在处理一些表比如IMAGE_RELOCATION,IMAGE_SYMBOL等等,然后在经过一系列的处理后,会把需要的部分按照一定格式打包起来随后在发送给Beacon,这时Beacon接收到的是Cobalt Strike已经解析处理过的obj文件数据,并非是原本的obj文件,所以Beacon主要做的是必须是在进程内才能确定并完成的事情比如处理重定位,填充函数指针等等,最后去执行go入口点
obj 目标文件就是源代码编译之后但是未进行链接的那些中间文件(Windows 下的 .obj 和 Linux 下的 .o,本文主要指windows 下的 obj )
使用BOF框架开发
很多大佬在 GitHub 发了一些模板,可以去参照
- bof的visual studio模板:https://github.com/securifybv/Visual-Studio-BOF-template
- bof所需的头文件:https://github.com/trustedsec/CS-Situational-Awareness-BOF
- 整理好的,通过DLL名称将WindowsApi函数进行了归类:https://github.com/evilashz/Visual-Studio-BOF-template
将下载的模板文件解压至visualstudio的模板目录(%UserProfile%\Documents\Visual Studio 2022\Templates\ProjectTemplates
),随后重启VisualStuido
在创建项目时选择类型为Beacon Object File
的项目
在头文件列表可以看到beacon.h
和bofdefs.h
build->batch build 打开项目的 Batch 生成,勾选上 BOF 配置
配置管理器的编译环境也需设置为BOF
一个简单的 bof 项目,用于实现向控制台输出字符串
- BOF 入口:代码定义了 BOF 的入口函数 go,当你在 Cobalt Strike 中使用 inline-execute 命令加载并执行你的 BOF时,这个函数将被调用。你可以在这个函数中添加你的 BOF 代码
- 非 BOF 入口:这部分代码定义了非 BOF 的入口函数 main。当你在非 Cobalt Strike 环境中运行你的代码时,这个函数将被调用。你可以在这个函数中添加你的非 BOF 代码
#include "bofdefs.h"
extern "C" {
#ifdef BOF
void go(char* buff, int len) {
BeaconPrintf(CALLBACK_OUTPUT, "Hello, World!");
#endif
}
}
#ifndef BOF
void main(int argc, char* argv[]) {
go(NULL, 0);
}
#endif
项目生成后会生成一个.obj
文件,也就是编译未链接的目标文件,在CobaltStrike中你可以使用inline-execute
命令来加载并执行你的.obj文件,此命令将你的 .obj
文件加载到 Beacon 的内存中,然后调用你的 go
函数,命令格式如下所示
beacon> inline-execute your_bof.obj
修改为BOF格式
在原有利用代码基础上修改为 BOF 代码步骤
- 引入 beacon.h 头文件,beacon.h 定义了与 Cobalt Strike Beacon 交互所需要的各种数据类型和函数
- 把所有字符串和函数改成 ascii 的
- 把所有函数改成 beacon.h 定义的编写约定
- 入口函数由 main 修改为 go
- 生成 bof 文件
将 get-computer-installed-software 修改为 bof 加载,代码通过查询注册表获取当前机器安装的程序,这种方式仅对完整安装的软件有效,如果是绿色版的软件则只能通过手工或自动化搜索的方式查找,如果是x64位系统则需要对32位程序也进行遍历(x64系统存在注册表重定位)
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
BOOL EnumInstalledSoft(TCHAR* subKey, TCHAR* subKeyName) {
HKEY hKey = NULL;
HKEY hSubKey = NULL;
DWORD dwIndexs = 0;
TCHAR keyName[MAX_PATH] = { 0 };
DWORD dwLength = MAX_PATH; // 修改为合适的长度
TCHAR subKeyValue[MAX_PATH] = { 0 };
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
while (RegEnumKeyEx(hKey, dwIndexs, keyName, &dwLength, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
RegOpenKey(hKey, keyName, &hSubKey);
if (RegQueryValueEx(hSubKey, subKeyName, NULL, NULL, (LPBYTE)subKeyValue, &dwLength) == ERROR_SUCCESS)
{
_tprintf(_T("%s : %s \n"), keyName, subKeyValue);
}
RegCloseKey(hSubKey);
hSubKey = NULL;
++dwIndexs;
dwLength = MAX_PATH; // 重置为合适的长度
}
RegCloseKey(hKey);
return TRUE;
}
else
{
return FALSE;
}
}
int main()
{
EnumInstalledSoft((TCHAR*)_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"), (TCHAR*)_T("DisplayName"));
EnumInstalledSoft((TCHAR*)_T("Software\\Classes\\Installer\\Products"), (TCHAR*)_T("ProductName"));
system("pause");
return 0;
}
接下来导入 beacon.h 和替换 BOF 约定的写法,函数原型使用 bof_helper 自动化帮我们生成好bof约定的函数原型和写法,如把 GetProcAddress 换成 KERNEL32$GetProcAddress 的写法,这里直接使用工具,同时也需要把输出函数换成 beacon 导出的函数
BOF 格式代码
#include <stdio.h>
#include <windows.h>
// 1.添加 beacon.h 头
#include "beacon.h"
// 2.添加函数引入约定,可以去找找其他项目中有没有使用相同的函数
DECLSPEC_IMPORT WINADVAPI LONG WINAPI ADVAPI32$RegOpenKeyExA(HKEY, LPCWSTR, DWORD, REGSAM, PHKEY);
DECLSPEC_IMPORT WINADVAPI LONG WINAPI ADVAPI32$RegOpenKeyA(HKEY, LPCWSTR, PHKEY);
DECLSPEC_IMPORT WINADVAPI LONG WINAPI ADVAPI32$RegCloseKey(HKEY);
DECLSPEC_IMPORT WINADVAPI LONG WINAPI ADVAPI32$RegEnumKeyExA(
HKEY,
DWORD,
LPWSTR,
LPDWORD,
LPDWORD,
LPWSTR,
LPDWORD,
PFILETIME
);
DECLSPEC_IMPORT WINADVAPI LONG WINAPI ADVAPI32$RegQueryValueExA(
HKEY,
LPCWSTR,
LPDWORD,
LPDWORD,
LPBYTE,
LPDWORD
);
BOOL EnumInstalledSoft(CHAR* subKey, CHAR* subKeyName) {
HKEY hKey = NULL;
HKEY hSubKey = NULL;
DWORD dwIndexs = 0;
CHAR keyName[MAX_PATH] = { 0 };
DWORD dwLength = 256;
CHAR subKeyValue[MAX_PATH] = { 0 };
if (ADVAPI32$RegOpenKeyExA(HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
while (ADVAPI32$RegEnumKeyExA(hKey, dwIndexs, keyName, &dwLength, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
ADVAPI32$RegOpenKeyA(hKey, keyName, &hSubKey);
ADVAPI32$RegQueryValueExA(hSubKey,
subKeyName,
NULL,
NULL,
(LPBYTE)subKeyValue,
&dwLength);
BeaconPrintf(CALLBACK_OUTPUT, "%s : %s \n", keyName, subKeyValue);
ADVAPI32$RegCloseKey(hSubKey);
hSubKey = 0;
++dwIndexs;
dwLength = 256;
}
}
else
{
return FALSE;
}
if (hKey != NULL)
{
ADVAPI32$RegCloseKey(hKey);
return TRUE;
}
}
int go()
{
EnumInstalledSoft((CHAR*)"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", (CHAR*)"DisplayName");
EnumInstalledSoft((CHAR*)"Software\\Classes\\Installer\\Products", (CHAR*)"ProductName");
return 0;
}
其余的 bof 格式差异
-
参数传入
目前代码没有传入参数,如果有参数传入,需要先声明参数
void go(char* args,int length){ datap parser; char* str_arg; int num_arg; str_arg = BeaconDataExtract(&parser,NULL); }
-
输出
使用 BeaconPrintf 替换 printf
BeaconPrintf(CALLBACK_ERROR,"ERROR");
编译
使用 gcc 进行编译,需要提前安装
gcc -c 源文件.c -o 输出文件.o
ps:注意需要安装 64 位的程序,才能编译 64 位的 obj 文件,MinGW-w64安装教程
inline-execute 在 cs 中直接使用 bof 文件
bof 缺点
- 似乎无法使用初始化为0的全局变量
- 不太适合跑驻留型的任务,跑大量循环会崩溃
- 一旦引发崩溃整个beacon就会崩掉
- 不易调试
- 似乎输出不能使用unicode
参考文章
- Beacon Object File(BOF实现原理)
- 内网渗透神器CobaltStrike之BOF编写(十一)
- Cobalt Strike BOF原理分析