一个正常C程序,如何转换成bof文件,这里记录下过程
bof的原理实现就不多讲了,简单来说就是编译成一个object文件(COFF格式,exe是PE格式),然后传输给beacon后在终端上完成链接过程,这也就导致bof文件非常小,一般只有几K。
参考
https://wbglil.gitbook.io/cobalt-strike/cobalt-strike-yuan-li-jie-shao/untitled-3
编写bof代码
在github有几个不错的开源项目,可以以此为模板进一步开发,而不需要完全重头开始。
https://github.com/trustedsec/CS-Situational-Awareness-BOF
https://github.com/securifybv/Visual-Studio-BOF-template
我是基于第一个项目开发,这个项目有很多已实现的案例,可作为参考。
SA目录就是编译成object的文件,用于直接提供cs使用
src是源码目录,除了已实现功能的源码,还提供了一个base_template作为模板。
将base_template拷贝一份到src/SA下,改成如useradd,BOFNAME也修改一下,方便发布。
然后看下核心代码部分,这里其实考虑到了两种使用场景了,bof提供cs使用,exe供独立使用,也方便调试功能是否正常,编译的时候只需要带上-DBOF即可编译成bof。
bof和exe入口区别如下,exe固定是main函数入口,bof会由加载器扫描go函数的地址作为入口,当然也可以是其他名字,默认是go。
bof编写,一般会导入一个beacon.h,有一些内置的函数提供,比如我要打印内容给teamsever显示,就可以调用BeaconPrintf,其他函数和使用可参考这个官方链接
https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm
接着就是编写功能了,比如添加用户,这里有个C项目,有现成代码。
https://github.com/newsoft/adduser
代码比较简单,调用NetUserAdd添加用户,然后调用NetLocalGroupAddMembers
加administrators组
但这些都是直接调用的win32api,如果看了上面提供的bof官方文档,就会知道这些api需要动态加载调用,不像是PE文件有导入表,那官方提供了两种方法,一是调用loadlibrary等函数动态加载,二是动态函数解析(DFR),我这里选择后者,这个方便些。
在src\common\bofdefs.h添加宏定义,用于动态函数解析
1 2 | #define NETAPI32$NetUserAdd NetUserAdd |
并添加函数声明,用于外部调用
1 2 | WINBASEAPI DWORD WINAPI NETAPI32$NetUserAdd(LPCWSTR,DWORD,PBYTE,PDWORD); |
如上操作即可完成动态函数解析。然后在我们要编写的代码里替换成带有lib名称的函数即可。
繁琐的地方主要在于要查微软官方文档,然后才知道函数签名。
这个项目自动完成这个过程了,通过一个本地函数库获取函数签名,再通过微软在线查询该函数对应的lib文件名是什么,拼接实现上面声明代码。
https://github.com/dtmsecurity/bof_helper
效果如下,然后最后一行复制到bofdefs.h里即可
把需要打印回显的位置替换成BeaconPrintf,bof的项目还做了一个封装,更方便使用,internal_printf
使用make bof尝试编译是否正常
然后用cs调用,可以正常加载,但有个问题,这样是无法传参的。
1 2 | inline-execute [dirpath]\useradd.x64.o |
比如这样是无法传参的
1 2 | inline-execute [dirpath]\useradd.x64.o user pass |
参数解析问题
看下go的入口解析参数代码,这个是参考其他功能的代码,cs内置一个datap数据类型,用于数据解析,先调用BeaconDataParse将缓存放入parser,再通过BeaconDataExtract提取。
参看官方手册,提供了五种函数用于数据提取,也就是说数据类型也5种,int/char/wchar_t/short等,那如果是命令行传递是不可能知道数据类型的。仔细看文档第一行,提示了,需要结合cna脚本的bof_pack函数来pack格式化参数传递,所以需要编写cna脚本了。
cna脚本编写
这个bof项目也提供了现成的cna脚本,所以只需要参考改一下即可
代码如下,很简单,添加一个函数useradd,然后通过beacon_command_register
注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | beacon_command_register ( "useradd", "useradd [username] [passord]" "example: useradd audit Test123456789!" ); alias useradd { local('$user $pass $args') $user = $2; $pass = $3; if ($user eq "" or $pass eq "") { berror($1, "help useradd"); return; } $args = bof_pack($1, "ZZ", $user, $pass); beacon_inline_execute($1, readbof($1, "useradd"), "go", $args); } |
PS: readbof是这个项目自定义的函数,用于读取bof完整文件名。
bof_pack参考
https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#bof_pack
这里也和bof里的函数关联起来了,你怎么格式化,bof里就怎么解析。
bof_pack
$1: beacon id
$2: format string for the packed data
...: args
格式化参考
Type | Description | Unpack With (C) |
---|---|---|
b | binary data | BeaconDataExtract |
i | 4-byte integer | BeaconDataInt |
s | 2-byte short integer | BeaconDataShort |
z | zero-terminated+encoded string | BeaconDataExtract |
Z | zero-terminated wide-char string | (wchar_t *)BeaconDataExtract |
bof解析参数时注意事项
- 如果是BeaconDataExtract赋值给某个char变量 ,一定要是const char,否则会crash。
- 如果调用了bofstart,解析一定要在这后面,否则也会crash
beacon_inline_execute
$1 - the id for the Beacon
$2 - a string containing the BOF file
$3 - the entry point to call
$4 - packed arguments to pass to the BOF file
重新加载cna脚本
bof文件大小写和PE文件相比,这个demo中差了近百倍。
参考
https://www.cobaltstrike.com/help-beacon-object-files
https://gitee.com/sh3llsas/CobaltStrike-BOF
https://wbglil.gitbook.io/cobalt-strike/cobalt-strike-yuan-li-jie-shao/untitled-3
https://github.com/ajpc500/BOFs/
https://github.com/boku7/spawn
cna参考链接
https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm