0x00 Abstract

Over the past few months I have been using Cobalt Strike (CS) quite extensively, both during Simulated Attack engagements and for R&D and offensive security projects. I subsequently used more than what I expected the famous Aggressor script engine.

Throughout the different versions of CS, Raphael Mudge developed multiple features that allow operators to extend the standard capabilities of CS:

  • C2 malleable profile, to modify the behaviours of the implant (e.g. SMB name pipe, HTTP/S URI, process spawned for shellcode injection, etc…);
  • Aggressor Scripts to modify the CS client (e.g. adding new beacon commands, creating new popups, handling events, etc …); and
  • More recently, beacon object files to execute non-linked C code within a running beacon.

The aim of this small post is not to explain how all these amazing features works, even if this would be super interesting. Instead, I will discuss how I managed to load aggressor scripts from an aggressor script. Hence the scripts-ception title. This may sound completely stupid, but this is something that I was looking for, for a long time. Being able to have a “main” aggressor script that will load all my other scripts.

0x01 Cobalt Strike Internal Java Object

First things to know is that aggressor scripts are written with the Sleep scripting languages, which has been developed by Raphale Mudge. One of the interesting feature of Sleep is that it is possible to create, access, and query Java objects through Object expressions, as mentioned here. In other words, this means that it is possible to interact with the internal Java objects, classes, and methods of CS from an aggressor script instead of solely relying on the function exposed by the script engine.

After some basic reverse engineering of the CS JAR file with JD-GUI, the following class can be identified as the class used for the Script Manger UI interface: aggressor.windows.ScriptManager. One interesting method that is implement is: actionPerformed(ActionEvent paramActionEvent):

public void actionPerformed(ActionEvent paramActionEvent) {
    if ("Load".equals(paramActionEvent.getActionCommand())) {
        SafeDialogs.openFile("Load a script", null, null, false, false, this);
    } else if ("Unload".equals(paramActionEvent.getActionCommand())) {
        String str = this.model.getSelectedValue((JTable)this.table) + "";
        for (Cortana cortana : Aggressor.getFrame().getScriptEngines())
            cortana.unloadScript(str); 
        List list = Prefs.getPreferences().getList("cortana.scripts");
        list.remove(str);
        Prefs.getPreferences().setList("cortana.scripts", list);
        Prefs.getPreferences().save();
        refresh();
    } else if ("Reload".equals(paramActionEvent.getActionCommand())) {
        String str = this.model.getSelectedValue((JTable)this.table) + "";
        try {
            this.client.getScriptEngine().unloadScript(str);
            this.client.getScriptEngine().loadScript(str);
            DialogUtils.showInfo("Reloaded " + str);
        } catch (YourCodeSucksException yourCodeSucksException) {
            MudgeSanity.logException("Load " + str, (Throwable)yourCodeSucksException, true);
            DialogUtils.showError("Could not load " + str + ":\n\n" + yourCodeSucksException.formatErrors());
        } catch (Exception exception) {
            MudgeSanity.logException("Load " + str, exception, false);
            DialogUtils.showError("Could not load " + str + "\n" + exception.getMessage());
        } 
        try {
            for (Cortana cortana : Aggressor.getFrame().getOtherScriptEngines(this.client)) {
                cortana.unloadScript(str);
                cortana.loadScript(str);
            } 
        } catch (Exception exception) {
            MudgeSanity.logException("Load " + str, exception, false);
        } 
        refresh();
    } 
}

As shown and as expected from an object used for UI components, the logic that actually do the work (i.e. load/unload/reload scripts) is not implemented here. However, the method calls the getScriptEngine() method from the client property. This property is a protected aggressor.AggressorClient object.

The getScriptEngine method is actually pretty simple because this is a “getter” to the engine property of the object. The property being a protected cortana.Cortana object.

public Cortana getScriptEngine() {
	return this.engine;
}

This is perfect because the Cortana object is doing what we want via the following methods: findScript(String paramString), unloadScript(String paramString) and loadScript(String paramString). These methods can be used to respectively, identify whether a script has been already loaded, to unload a loaded script and to load a new script.

public String findScript(String paramString) {
    Iterator<E> iterator = this.scripts.keySet().iterator();
    while (iterator.hasNext()) {
        String str = iterator.next().toString();
        File file = new File(str);
        if (paramString.equals(file.getName()))
        return str; 
    } 
    return null;
}

public void unloadScript(String paramString) {
    Loader loader = (Loader)this.scripts.get(paramString);
    if (loader == null)
        return; 
    this.scripts.remove(paramString);
    loader.unload();
}

public void loadScript(String paramString, InputStream paramInputStream) throws YourCodeSucksException, IOException {
    Loader loader = new Loader(this);
    if (this.scripts.containsKey(paramString))
        throw new RuntimeException(paramString + " is already loaded"); 
    loader.getScriptLoader().addGlobalBridge(this.events.getBridge());
    loader.getScriptLoader().addGlobalBridge(this.formats.getBridge());
    loader.getScriptLoader().addGlobalBridge(this.myinterface.getBridge());
    loader.getScriptLoader().addGlobalBridge(this.utils);
    loader.getScriptLoader().addGlobalBridge(this);
    loader.getScriptLoader().addGlobalBridge(this.keys);
    loader.getScriptLoader().addGlobalBridge(this.menus.getBridge());
    for (Loadable loadable : this.bridges)
        loader.getScriptLoader().addGlobalBridge(loadable); 
    if (paramInputStream != null) {
        loader.loadScript(paramString, paramInputStream);
    } else {
        loader.loadScript(paramString);
    } 
    this.scripts.put(paramString, loader);
}

At this point we do not need to go much more in depth because we have all we need. Feel free to dig deeper into the internals of the cortana.Loader object if you are interested in how CS is loading/unloading the scripts.

Last thing that is interesting is the aggressor.Prefs object that as the name implies stores the settings/preferences of CS. In our case we can use the getList(String paramString) and setList(String paramString, List<?> paramList) methods to update the scripts that have been loaded/unloaded. These data will be used by the UI.

0x02 Load Aggressor Scripts

Based on the information mentioned hereinabove, the following aggressor script can be written:

# Java packages
import aggressor.windows.ScriptManager;
import aggressor.AggressorClient;
import aggressor.Prefs;
import cortana.Cortana;
import java.util.List;

# $1 - array of scripts to load 
sub load_aggressor_script  {
        this('$script $client $cortana $prefs $list');
        $script = [new ScriptManager: getAggressorClient()];
        $client = [$script client];
        $cortana = [$client engine];

        # Get preferences
        $prefs = [Prefs getPreferences];
        $list = [$prefs getList: 'cortana.scripts'];
        
        # Load/Reload scripts
        foreach $value ($1) {
                println("\c2[+]\c0 Loading: " . $value);

                # Unload script if alread loaded
               if ([[$cortana scripts] containsKey: $value]) {
                    [$cortana unloadScript: $value];
                    [$list remove: $value];
                }

                # Load script
                [$cortana loadScript: $value];
                [$list add: $value];
        }

        # Refresh UI
        [$prefs setList: 'cortana.scripts', $list];
        [$prefs save];
        [$script refresh];
}

# Banner
println('');
println('Cobalt Strike Aggressor Script Utility for Loading Aggressor Scripts');
println('Copyright (C) 2020 Paul L. (@am0nsec)');
println('https://amonsec.net/');
println('');

# Scripts to load
@scripts_to_load = @(
        "C:\\Users\\redacted\\Desktop\\script1.cna",
        "C:\\Users\\redacted\\Desktop\\script2.cna"
);
load_aggressor_script(@scripts_to_load);