Cobalt Strike Postex Kit
CS 4.10 更新引入了 Postex Kit。这一点被同样在 4.10 中添加的 BeaconGate 盖住了风头(我在我的上一篇文章[1] 中写过这个)。这篇文章的目的是强调这个工具包的内容以及如何使用它。
如您所知,CS 中的后执行(post-ex)执行分为两大类——内联(inline)和_分叉运行_(fork & run)。内联执行是通过 Beacon 对象文件(BOFs)来完成的,而分叉运行则是通过反射 DLL(rDLLs)来完成的。在大多数情况下,Beacon 是一个单线程应用程序。BOFs 在该单线程上执行,因此在完成之前会阻止 Beacon 做其他任何事情(如检查、进入休眠等)。这对于您希望在后台运行的长时间任务(例如键盘记录器)来说是不可取的。分叉运行通过将 rDLL 注入到另一个进程中并在其自己的线程下执行来工作。这使得 Beacon 在任务运行时可以自由地执行其他操作。
在 CS 的术语中,分叉运行有两个变体,分别称为spawn
和explicit
。spawn 变体指示 Beacon 启动一个新的临时进程并将 rDLL 注入其中。explicit 变体指示 Beacon 将 rDLL 注入到一个已经在运行的进程中。在这两种情况下,Beacon 和 rDLL 之间的通信是通过命名管道进行的。rDLL 将启动一个命名管道服务器,Beacon 在执行注入后会很快连接到它。
Postex Kit 是一个 Visual Studio 项目,提供了构建您自己的后执行 rDLL 的模板,将其插入到 CS 的作业架构中,甚至在运行时与它们进行通信。这些自定义 rDLL 也与 CS 的其他规避功能完全兼容,例如后执行 UDRL 和进程注入工具包。
DllMain
我们不需要在 DllMain 内部进行修改,因为正确引导 rDLL 的工作已经为我们完成。我只想强调,当入口点以DLL_POSTEX_ATTACH
的“原因”被调用时,DllMain 会调用PostexMain
,这是我们放置任务逻辑的地方。当这个函数返回时,DllMain 会调用PostexExit
,这将关闭并清理命名管道,然后根据使用的分叉运行变体调用ExitThread
或ExitProcess
。
PostexMain
PostexMain
接受一个指向POSTEX_DATA
的指针。
voidPostexMain(PPOSTEX_DATA postexData) {
RETURN_ON_NULL(postexData);
// do my stuff
return;
}
// postexmain.cpp
该结构包含用户可定义的后执行数据,例如操作员传递的任何参数。这些参数在客户端使用bof_pack
Aggressor 函数进行打包。后执行模板提供访问相同 BOF API 的功能,以解包这些参数。
datap parser;
BeaconDataParse(&parser,
postexData->UserArgumentInfo.Buffer,
postexData->UserArgumentInfo.Size);
// postexmain.cpp
我们同样可以访问相同的格式和输出 API,例如BeaconFormatAlloc
、BeaconFormatAppend
、BeaconFormatPrintf
、BeaconPrintf
等。在底层,BeaconPrintf
是对BeaconOutput
的封装。在调试模式下,它使用WriteConsoleA
将数据打印到控制台。在发布模式下,它会正确地将数据分块并写入命名管道,以供 Beacon 读取。
char* name = BeaconDataExtract(&parser, NULL);
BeaconPrintf(CALLBACK_OUTPUT, "Hello %sn", name);
模拟参数
与 C++ BOF 和 UDRL Visual Studio 模板一样,我们可以在调试模式下提供模拟参数。
voidmain() {
BOOL startPipeServer = false;
PostexDataPacker userArguments;
userArguments.pack<constchar*>("Rasta");
DebugEntryPoint(userArguments.getData(), userArguments.size(), startPipeServer);
}
// postexmain.cpp
管道
由于操作员可以在运行时向后执行 DLL 发送消息,因此该模板还提供了一些关于从命名管道读取的抽象。BeaconInputAvailable
返回可读取的字节数,而BeaconInputRead
则从管道中读取指定数量的字节。
DWORD bytesAvailable = BeaconInputAvailable();
if (bytesAvailable > 0) {
char* input = newchar[bytesAvailable];
BeaconInputRead(input, bytesAvailable);
// do something with input
}
// postexmain.cpp
无限循环
长时间运行任务的常见场景是它会无限运行,直到操作员通过jobkill
命令取消。然而,我在使用该工具包时发现,客户端终止作业并不会强制结束 DLL。因此,如果在PostexMain
中有一个while (true)
(或类似的)循环,Beacon 将会与命名管道断开连接,您将不再在客户端控制台上看到任何输出,但 DLL 本身将永远不会停止。
我认为解决此问题的最简单方法是从循环内部监控命名管道的状态。当 postex DLL 启动命名管道服务器时,它会传递一个值1
作为最大实例数,并将句柄存储在一个名为gPipeHandle
的全局变量中。
gPipeHandle = CreateNamedPipeA(const_cast<LPCSTR>(gPipeName),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
1, // nMaxInstances
BUFFER_SIZE,
BUFFER_SIZE,
0,
NULL);
// pipes.cpp
像PeekNamedPipe
这样的 API 可用于检查管道的状态。如果调用失败并返回ERROR_BROKEN_PIPE
状态,我们可以推测 Beacon 已经断开连接。我们只需从循环中跳出,以便PostexMain
可以正常返回。
BOOL ClientConnected() {
if (!PeekNamedPipe(gPipeHandle, NULL, 0, NULL, NULL, 0))
if (GetLastError() == ERROR_BROKEN_PIPE)
returnFALSE;
returnTRUE;
}
void PostexMain(PPOSTEX_DATA postexData) {
int counter = 1;
while (true) {
BeaconPrintf(CALLBACK_OUTPUT, "Counter: %d", counter);
counter++;
Sleep(3000);
if (!ClientConnected())
break;
}
return;
}
// postexmain.cpp
execute-dll
新的execute-dll
命令将注入一个 postex DLL,使用 spawn 或 explicit fork & run 变体,并创建一个新作业。
beacon> help execute-dll
Use: execute-dll [pid] [/path/to/postex.dll] [args]
execute-dll [/path/to/postex.dll] [args]
Inject the provided postex DLL into the specified process. This DLL must be generated by the postex kit in the Arsenal
Kit to make use of Cobalt Strike's existing job architecture. Use execute-dll with no pid to spawn a temporary process
and inject the postex DLL into it.
Utilize the POSTEX_RDLL_GENERATE hook to specify a UDRL and/or use PROCESS_INJECT to specify the injection method.
beacon>execute-dll C:Toolscobaltstrikearsenal-kitkitspostexx64Releasedemo.dll
[*] Tasked beacon toexecute a User-Defined Postex Task
[+] host called home, sent: 138560 bytes
[+] job registered with id 4
[+] [job 4] received output:
Counter: 1
[+] [job 4] received output:
Counter: 2
[+] [job 4] received output:
Counter: 3
[+] [job 4] received output:
Counter: 4
[+] [job 4] received output:
Counter: 5
beacon> jobkill 4
[*] Tasked beacon to kill job 4
[+] host called home, sent: 10 bytes
[+] job 4 completed
beacon_execute_postex_job
beacon_execute_postex_job
Aggressor 函数提供了更大的灵活性,因为它允许您注册一个自定义回调函数,该函数将在每次作业更新时触发。
$callback= lambda({
local('$bid$data$result%info $type');
this('$jid');
# get arguments passed to lambda
($bid, $result, %info) =;
# get the job status/type
# can be job_registered, output, error, or job_completed.
$type=%info["type"];
# get the job id
$jid=%info['jid'];
# do something based on status
});
# run the postex task...
beacon_execute_postex_job($1, $null, $postex_dll, $null, $callback);
使用自定义回调时,您必须自行记录输出和错误,分别使用bjoblog
和bjoberror
。
if ($type eq 'output') {
bjoblog($bid, $jid, $result);
} elseif ($type eq 'error') {
bjoberror($bid, $jid, $result);
}
您还可以在此处调用其他 Aggressor 函数与 Beacon 或各种数据模型进行交互。例如,如果您有一个执行密码抓取的 postex DLL,您可以解析输出并调用credential_add
将其添加到凭据数据模型中。
bjob_send_data
函数可用于通过命名管道将数据发送到 post-ex DLL。
bjob_send_data($bid, $jid, $data);
这可以放在回调中,但也可以包装在一个单独的别名中,以将数据发送到任意的作业 ID。
aliastest {
bjob_send_data($1, $2, $3);
}
beacon_job_hide_output
函数指示客户端不要在主 Beacon 控制台中打印任务的输出,而是将其限制在新的作业控制台视图中。
if ($type eq 'job_registered') {
beacon_job_hide_output($bid, $jid, 1);
}
结论
这几乎涵盖了我迄今为止对 postex 工具包的所有了解(如果我遗漏了什么重要内容,请告诉我)。希望这些信息对您有所帮助,并能激励您尝试编写自己的自定义 postex DLL。
参考资料
上一篇文章:https://rastamouse.me/udrl-sleepmask-and-beacongate/
原文始发于微信公众号(securitainment):Cobalt Strike Postex Kit 套件
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论