CS BOF文件编写/改写

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 发了一些模板,可以去参照

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

image-20240102103237373

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

image-20240102103310526

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

image-20240102103350426

build->batch build 打开项目的 Batch 生成,勾选上 BOF 配置

image-20240102103506658

配置管理器的编译环境也需设置为BOF

image-20240102103532153

一个简单的 bof 项目,用于实现向控制台输出字符串

  • BOF 入口:代码定义了 BOF 的入口函数 go,当你在 Cobalt Strike 中使用 inline-execute 命令加载并执行你的 BOF时,这个函数将被调用。你可以在这个函数中添加你的 BOF 代码
  • 非 BOF 入口:这部分代码定义了非 BOF 的入口函数 main。当你在非 Cobalt Strike 环境中运行你的代码时,这个函数将被调用。你可以在这个函数中添加你的非 BOF 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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 函数,命令格式如下所示

1
beacon> inline-execute your_bof.obj

image-20240102145032414

修改为BOF格式

在原有利用代码基础上修改为 BOF 代码步骤

  1. 引入 beacon.h 头文件,beacon.h 定义了与 Cobalt Strike Beacon 交互所需要的各种数据类型和函数
  2. 把所有字符串和函数改成 ascii 的
  3. 把所有函数改成 beacon.h 定义的编写约定
  4. 入口函数由 main 修改为 go
  5. 生成 bof 文件

get-computer-installed-software 修改为 bof 加载,代码通过查询注册表获取当前机器安装的程序,这种方式仅对完整安装的软件有效,如果是绿色版的软件则只能通过手工或自动化搜索的方式查找,如果是x64位系统则需要对32位程序也进行遍历(x64系统存在注册表重定位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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 格式代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#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 格式差异

  • 参数传入

    目前代码没有传入参数,如果有参数传入,需要先声明参数

    1
    2
    3
    4
    5
    6
    7
    void go(char* args,int length){
    datap parser;
    char* str_arg;
    int num_arg;

    str_arg = BeaconDataExtract(&parser,NULL);
    }
  • 输出

    使用 BeaconPrintf 替换 printf

    1
    BeaconPrintf(CALLBACK_ERROR,"ERROR");

编译

使用 gcc 进行编译,需要提前安装

1
gcc -c 源文件.c -o 输出文件.o

ps:注意需要安装 64 位的程序,才能编译 64 位的 obj 文件,MinGW-w64安装教程

inline-execute 在 cs 中直接使用 bof 文件

bof 缺点

  1. 似乎无法使用初始化为0的全局变量
  2. 不太适合跑驻留型的任务,跑大量循环会崩溃
  3. 一旦引发崩溃整个beacon就会崩掉
  4. 不易调试
  5. 似乎输出不能使用unicode

参考文章