Aggressor 101: Unleashing Cobalt Strike for Fun and Profit

I use Cobalt Strike a lot. It’s my team’s go-to tool for compromising Windows environments, and that’s what I find myself doing more often than not during red team engagements. One of the reasons I enjoy it so much is that it abstracts a lot of the common things that we need to do on engagements, giving us more flexibility to focus on our objectives, and how best to accomplish them.

Even so, I’m obsessed with making our workflow even more efficient, and giving us the power to accomplish objectives even faster. One of the best ways to do this is with Aggressor Script, Cobalt Strike’s native scripting language. I should also add a caveat to this post: I am not a software developer. I do not have a formal background in developing, nor do I focus on efficiency or speed when I’m writing Aggressor.

Recently, I was able to attend the (excellent) SpecterOps Adversary Tactics: Red Team Operations course, which gave me the opportunity to talk to various red teamers, getting their perspectives on preferred tools, and how they use them. One of the things that surprised me somewhat was how few people make use of the power that Aggressor Script can provide. A sentiment that I heard a lot was that even red teams already paying for Cobalt Strike haven’t implemented much Aggressor Script into their workflows. I’d like to change that.

An Introduction to Aggressor

Aggressor Script is based on Sleep, a language created by Raphael Mudge (also the author of Cobalt Strike). Sleep is basically a Perl-like language which runs on the JVM. Before you start complaining, I believe that the power that Aggressor Script gives you in Cobalt Strike far outweighs any issues some might have with writing Perl, or dealing with Java.

This post will assume that you’re comfortable using Cobalt Strike, and that you know the various concepts behind operating with it. This will not be an introduction to Cobalt Strike.

With that out of the way, what does Aggressor Script actually look like? I find the syntax to be pretty simple, especially if you’re used to writing Perl, though many concepts will be perfectly understandable if you’ve written code before. Here’s a simple “Hello, world!” script, written in Aggressor:

# helloworld.cna

# Prints "Hello, world!"

# 001SPARTaN

sub hello {

println("Hello, world!");

}

hello();

Pretty easy. We create a new function named “hello” with sub hello {...} , and print text to the console with println("Hello, world!") . Statements in Aggressor must end with a semicolon, and functions must be defined before they’re called. Because the function doesn’t take any arguments, calling it with hello() will run the code.

Now that we have our first script, how do we actually use it? First, open up Cobalt Strike, and connect to your teamserver. Once your client is connected, go to View->Script Console, and type load /path/to/helloworld.cna (the full path is required). With that, you should see something like the following:

Hello, world!

The cool thing about the script console is that it lets you test stuff out without having to write a full script, too. We can create the same function using the ecommand in the script console to evaluate Aggressor snippets. To do this, we can take the same code, and instead put it on a single line. In the script console, type e sub hello {println("Hello, world!");} , and hit enter. This won’t return anything, but we can then run our hello() function by running e hello() in the script console.

Just like that, we can test out bits of Aggressor. When I’m writing Aggressor scripts, I constantly use the script console to prototype small bits of code, and make sure they’re working before I pull them into a larger script. It’s a very useful tool to debug scripts, or to quickly run snippets that don’t necessarily constitute a full scripts.

Now that we’ve gone over how to run Aggressor scripts, let’s explore how to construct more useful scripts.

The Building Blocks

In order to build useful Aggressor scripts, we’ll need a bit more to go on than just printing stuff to the console. To do that, we need to learn about the various datatypes in Sleep, and how to work with them. The main datatypes in Sleep are strings, arrays, and hashes. Strings are denoted with $ , arrayswith @ , and hashes with %.

If you’ve written code in another language, these datatypes should be familiar. Strings contain characters or strings of characters; arrays can hold multiple strings, arrays, or hashes (you can mix types in an array); and hashes contain multiple key-value pairs. What do these look like in practice?

This is a string with a few words:

$string = "This is a string.";

This is an array with three elements; a string, a number, and the $stringvariable that we defined before:

@array = @("The first element.", 2, $string);

This is a hash with three elements; key1 has a value of "value1", the value for key2 is the array we defined as @array, and key3 contains our $stringvariable.

%hash = %(key1 => "value1", key2 => @array, key3 => $string);

Using the x command in the script console, we can examine what these variables contain:

Strings, arrays, and hashes; the primary datatypes in Sleep

As you can see, Sleep has no issues combining datatypes, or dealing with nested arrays or hashes. In fact, it’s often very useful to nest arrays and hashes to create structures for storing things.

Before we start writing useful Aggressor scripts, I’ll go through how to access data stored in arrays and hashes. These two datatypes behave like they do in other languages. First, data contained in arrays can be accessed by its index, or its position in the array. Let’s define an array:

@array = @("First", "Second", "Third");

The index of the first element in an array is 0. To view this value, we can use @array[0] :

To access the other elements in the array, we can use their indices in the same way:

Fun with arrays!

We can also use these in println statements to directly print the values. The dot operator will concatenate two values as a string:

Hashes work in a similar way, though instead of using the index of an item, you’ll use the key that the value is stored under. Here’s what that looks like:

Fun with hashes!

The key values can be named whatever you’d like, and the value can be any type of data you want. Now that we’ve learned about the datatypes in Aggressor, how do we use this information to do useful things inside of Cobalt Strike?

Scripting Cobalt Strike

At the core of Cobalt Strike is the Cobalt Strike data model. This is where all of the information accessible to operators is stored, and it gives us a large amount of data to use for automating things in Cobalt Strike. The data collected in the data model includes information about the current beacons, credentials gathered from compromised systems, screenshots, downloaded files, and many other things.

For an example of how to use this data, let’s say we have a single beacon on a system. We can get an array of all the currently connected beacons by using the beacons() function, a built-in function in Aggressor. We can view this information with x beacons() in the script console.

If you try this, you’ll get a lot of information about each beacon. Beacons are arranged in an array, and each beacon is stored as a hash in this array. This is what that looks like with a single beacon in the data model:

Beacon data returned with the beacons() function

So what can we do with the information in the data model? Let’s start with a fairly simple task that you might want to do: run a command on all of the beacons that you have connected to a teamserver.

Your First Aggressor Script

Let’s build this script out a piece at a time. First, we’ll start with creating a script to run a command on a single beacon. To do this, we’re going to use the bshell function, a built-in function of Cobalt Strike. Here’s the definition from the official Aggressor Script documentation:

bshell — Ask Beacon to run a command with cmd.exe
Arguments
$1 - the id for the beacon. This may be an array or a single ID.$2 - the command and arguments to run

So how do we write a script to utilize this? We’re going to create an alias, or a command that you can type into any beacon console to execute custom Aggressor code. Here’s what that looks like:

alias shellcmd {

bshell($1, $2);

}

If you haven’t written Aggressor before, you might be curious about the $1and $2 variables we’re using in bshell. Whenever a function takes arguments in Aggressor, those arguments are passed to the function as numbered variables, named in the order in which they’re passed. For aliases, the first argument is the beacon ID that the alias is being used on, and any arguments after that are things you type after the alias when you run it from a beacon console. In this case, if you type shellcmd whoami into a beacon console, $2 will be "whoami". If you want to rename these variables to something that is easier to keep track of, you can assign them to other variables that you create. For more information on the functions available for use in Aggressor, and the arguments they take, check out this page.

To test this new alias out, save the script, and load it into Cobalt Strike (through the script console or through the script manager). You should now be able to type shellcmd <COMMAND> into a beacon console, and it will run the command for you! Note that you will also be able to tab-complete this alias.

Using our custom alias to run whoami

Right now, this isn’t a very impressive script. It does the same thing as the built-in shell command, but you have to type more to use it. How can we make our script run a command on all beacons, not just a single beacon? First, let’s write a bit of Aggressor to run a set command on multiple beacons, and then we’ll modify it until we can run whatever command we want on multiple beacons.

To do this, we’ll first need to iterate through all the beacons connected to the teamserver. Cobalt Strike makes this pretty easy. Remember that beacons()function from earlier? We can use that to iterate through all beacons with a simple foreach loop. Here’s what that looks like:

# Print beacon IDs for all beacons connected to the teamserver

foreach %beacon (beacons()) {

println("Beacon ID: " . %beacon['id']);

}

Here, we’re using a foreachloop to iterate through every beacon returned by the beacons() function. Remember, beacon information returned by this function is stored as a hash for each beacon. From the hash, we’re selecting the id key with %beacon['id'], and printing this ID.

How can we modify this script to instead run a command on every beacon returned by beacons()? Let’s start by rewriting it to run whoami on every beacon.

foreach %beacon (beacons()) {

$bid = %beacon['id'];

bshell($bid, "whoami");

}

Again, this is a pretty simple script, but we need to make it a little more useful. Instead of just running whoami on every beacon, let’s create a beacon alias that will run whatever command we want on every beacon. There are a couple ways to do this, but here’s a good start:

sub runcmd {

$bid = $1;

$cmd = $2;

bshell($bid, $cmd);

}

alias runall {

# Iterate through all beacons in data model

foreach %beacon (beacons()) {

# Call runcmd on each beacon

runcmd(%beacon['id'], $2);

}

}

To run this script, type runall into a beacon console, followed by a command. Due to the way alias works in Aggressor, if you want to run a command with spaces in it, you’ll need to wrap it in double quotes. There are ways to get around this, but I wanted to keep this post as a basic introduction to Aggressor.

Hopefully this post has given you a better idea of how to write basic Aggressor scripts! This is just scratching the surface of what’s possible with Cobalt Strike. You can use events to automatically trigger actions. You can alter the Cobalt Strike GUI with additional menu items or even custom visualizations. Aggressor doesn’t have functionality you want? You can even use inline Java object expressions to incorporate functionality from any Java library!

It might not be the prettiest language, but Aggressor gives you a tremendous amount of power, and allows you to expand Cobalt Strike however you want to. If you’re a Cobalt Strike user and you’re not writing Aggressor, you’re missing out on one of the most powerful tools available to you. In future posts, I’ll go through more of what makes Aggressor so useful. Until then, I hope this has been a useful introduction.

Questions? Comments? Hit me up on Twitter, or come hang out in the BloodHound Slack, especially the #aggressor channel.

Subscribe to get new content! Never miss a security update from the team.

Security news, tips, webinars, and more straight to your inbox.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.