Aggressor Script is the scripting engine in Cobalt Strike 3.0 and later. If you want to learn more about it, I recommend reading the documentation. In this blog post, I’ll provide some history around Aggressor Script so you can better understand it and where it comes from.

The mIRC Factor

mIRC is a popular client for Internet Relay Chat. In the mid-nineties, I was part of a community of enthusiastic computer users who would interact with each other online. Through this community, I had mentors and I was exposed to Linux early on as well. mIRC was more than a GUI client to connect to IRC though. mIRC was also a programming environment. User scripts could create new IRC commands (aliases), respond to events, and even modify the presentation of mIRC’s output. This gave power users a lot of room to make mIRC their own.

How did we use this power? It depends on the user. Some would write theme scripts to express their artistic prowess. mIRC became their canvas, Cp437 characters their brushes. Others would write scripts to task multiple mIRC instances (clones) to send messages to a friend that elicit an automatic response. This friend’s automatic response to all mIRC instances would cause the IRC server to disconnect them for malicious flooding. These flooding games, among friends of course, were a popular use of mIRC scripting.

The jIRCii Factor

Inspired by my use of mIRC, I set out to build a cross-platform IRC client, jIRC. Later, I renamed this project to jiRCii. jIRCii was my first large software project. It was also my longest running project. I worked on variations jIRC/jIRCii from 1999 through my last update in 2011.


jIRCii was also scriptable AND it had an active scripting community for a time too. Users wrote theme scripts, integration with third-party programs, and various utilities to make it easier to manage IRC channels.

When I built jIRCii’s scripting features, I really wanted to capture the elements of mIRC scripting that I felt worked. I also wanted to do away with the elements that didn’t work for me. Aliases (the alias keyword) were a good abstraction to define new commands. This concept worked and it made its way into jIRCii. Events (the on keyword) were a good abstraction to respond to events from the IRC server and the IRC client itself. These made it into jIRCii as well.

mIRC scripters had a heavy desire to theme mIRC and present IRC events in different ways. Arguably, this theme was the identity of the script. Early on, scripts would couple the theme information and the script’s functionality together. This tied a script to one theme though. Later, scripts would feature custom theme files that separated the presentation of output from the script’s other functionality. The early implementations of these themes were aliases (these did double-duty as subroutines in mIRC) that followed a naming convention. For example:

     return < $+ $1 $+ > $2-

Scripts would hook the different mIRC events, execute their normal logic for these events, and finish up by calling the appropriate theme function and reporting its results back to the user. The popularity of this convention informed jIRCii’s set keyword. jIRCii’s set keyword allows scripters to define how the client presents any of its output to the user. In fact, the client doesn’t have a default presentation of anything. The client’s defaults are defined in a built-in script.


Delegating default presentation to a script also streamlined jIRCii’s development process. I could focus on core features without thinking too much about the aesthetics of a feature. When it came time to work on the aesthetics, I could try things without restarting the client.

I applied the same concepts to jIRCii’s menubar and context-sensitive popup menus as well. jIRCii doesn’t define these things in its code, it delegates all of them to the default script. Again, jIRCii’s abstraction for user-defined menus, popups, and menu items were inspired by mIRC’s scripting engine.

jIRCii’s Scripting Language

Early into jIRCii’s life, I dabbled with different ways to give users control over the client. In the late-90s and early-2000s, there were not a lot of scripting engines built on top of Java. There was Beanshell, whose goal was to stay as close to Java as possible. There was Jacl, which was TCL on Java. While TCL was used as a scripting engine in some IRC clients, I wasn’t able to wrap my head around it at the time. Years later, I appreciate TCL much more. And, there was Jython which was a Python implementation for Java.

I wasn’t in love with any of the above options as an embedded scripting language. I really wanted a small language that I could extend with new constructs that exposed my application’s features. My goal with the scripting feature set was to court mIRC users. To do so, I needed a solution that didn’t feel aesthetically alien to mIRC scripters. I also wanted something a first-time programmer could reasonably pick up on his or her own.

The Sleep Factor

I didn’t expect that I would write jIRCii’s general purpose scripting language. I also didn’t expect that Armitage/Cobalt Strike would evolve into a stand-alone offensive platform either. Sometimes, when you decide to see a project through, you sign up for much more than you expected. Here’s the story behind the Sleep scripting language.

A common exercise for undergraduate Computer Science students is to build a simple programming language. Like other Computer Science students, I went through the exercise of building a simple LISP-like language interpreter in LISP. This learning exercise was my first exposure to BNF, recursive descent parsers, Abstract Syntax Trees, and interpreters. This exercise clicked with me and it very much whetted my appetite to explore these ideas further.

After I turned in the above assignment, I decided to sequester myself in a campus computer lab for the weekend. I set out to define a small language with aesthetical similarities to my then-favorite language, Perl. I built a lexical analyzer, a parser, and an interpreter for this simple language in a few days. This was the first version of Sleep. That was 2002.


I saw Sleep as a potential solution to my scripting conundrum for jIRCii. I would build Sleep as a small extensible language to embed into other applications. My initial target application was jIRCii. In a way, Sleep and jIRCii co-evolved with each other.

I started work on Sleep 2.0 around 2005. To me, a change in major version represents a change in fundamental assumptions and a strategic shift. Cobalt Strike’s early versions dabbled in a lot of ideas beneficial to red teaming. Cobalt Strike 2.0 was a concrete shift towards threat emulation with Malleable C2. Cobalt Strike 3.0 re-built Cobalt Strike as a platform for Red Team Operations and Adversary Simulations without the Metasploit Framework. Sleep 2.0 was a similar major shift. Sleep evolved from a very simple language to one that could call into Java’s APIs. This gave my scripters benefits similar to the ones PowerShell scripters enjoy calling the .NET API from PowerShell. I also added closures, coroutines, and continuations as language features. These additions gave Sleep a lot of power.

Sleep’s last update was in 2009. I stopped working on Sleep because at that point, the project met its original goals. It was also stable and feature complete to a reasonable point.

Since the mid-2000s, I’ve used Sleep quite a lot. When I was at the Air Force Research Lab, I used Sleep as a scripting engine in my various project prototypes. I kept Sleep integrated into jIRCii and as I mentioned previously, jIRCii had a healthy scripting community. I built a web application container for Sleep and for a time, I used Sleep for limited web application work. I also built the After the Deadline software service in Sleep. Finally, I tried to use Sleep as an application language. The lucky project to receive this treatment? Armitage.

I consider that a failed experiment. Much like other scripting languages, Sleep has a concept of a Global Interpreter Lock. Sleep’s GIL is a nuisance when building a multi-threaded GUI application that needs to interact with a server component. My choice to use Sleep is partially responsible for Armitage’s tendency to deadlock and some of the performance issues. Cobalt Strike 3.0 and later do not use Sleep as the application implementation language.


Cortana is the scripting engine in Armitage. This was a seven-month effort funded by DARPA’s Cyber Fast Track program. It was Mudge’s intent that CFT fund new efforts, not enhancements to existing ones. To pitch Cortana for CFT, I had to have an angle. Armitage was already a decent effort exploring red team collaboration among human actors. Why not take this to the next level and explore red team collaboration ideas between humans and bots? This was the idea behind Cortana.

Fortunately, Cortana was not my first scriptable application rodeo. I had experience with a base language (Sleep). I also knew what it would take to integrate this base language into an application (thanks jIRCii). This made it easy to explore the ideas that were the meat of the effort (positive control concepts, a headless client for bots, etc.)

Cortana brought some of jIRCii (and mIRC)’s scripting concepts into Armitage. It had events to respond to things that happened on the team server and within the client. I had one milestone focused on APIs for the Armitage GUI itself (e.g., popup menus and the like). I also had a milestone to strip Armitage to nothing and re-implement Armitage’s features in Cortana. I wanted to see if I could push the balance of “default script” stuff much further than jIRCii. I wasn’t happy with the results of this particular experiment and decided against adopting this for production Armitage. Consequently, production Armitage delegated nothing to its built-in scripting engine.

I released Cortana at DEF CON 20. Not only was it the largest audience I’ve spoken to in one room. It was also the one time I lost my voice before a talk as well.

The Cortana technology in Cobalt Strike (prior to 3.0) was mostly the same as the Cortana technology in Armitage. Over time though, I noticed that Cobalt Strike users had a much higher interest in Cortana than Armitage users. I had a non-trivial number of customers who had unzipped the cobaltstrike.jar file, figured out the Beacon RPC API, and were building scripts with this to automate and control Beacon. This was a lot of effort for these users to go to. I tried to meet these users half-way by publishing a partial Cortana API to script and control Beacon. All of this was a big obvious sign that I needed to expose a way to script Cobalt Strike’s features.

The Dark Corners of Cobalt Strike 3.0

Cobalt Strike 3.0 was a ground-up rewrite of Cobalt Strike’s GUI client and team server, notably without dependence on the Metasploit Framework.

I’m often asked why I changed X or Y in Cobalt Strike 3.0. I’m sometimes asked why did I bother with such large changes to a mature product? Depending on the context, I give different answers. The full answer is this: I learned a lot about my red team user base while selling and building Cobalt Strike 2.5 and its predecessors. These lessons included things my product didn’t do well and things my product would need to handle, eventually, to stay relevant to that user base.

One example of this is logging and reporting. Prior to 3.0, Cobalt Strike’s logging was borderline useless. Also, prior to 3.0, Cobalt Strike’s reporting was potentially beneficial to a penetration tester, but it didn’t show much imagination for the red teaming use case. There are a lot of reasons these things were the way they were, but ultimately, the 3.0 work was a chance to set some of these things right. Today, the reporting and logging are cited as one of the strengths in Cobalt Strike 3.0 and later.

No single insight, feature, or need drove the development of Cobalt Strike 3.0. It was the accumulation of all the user needs I wanted to tackle, but couldn’t with the old codebase and its dependencies. I didn’t take this decision lightly though…

I’m a big fan of Joel Spolsky’s Joel on Software blog. One post that sticks with me is Things You Should Never Do, Part I. In this post, Joel describes this mistake as the single worst strategic mistake any company can make. What is it? It’s to rewrite their code from scratch. I, as a single developer, knowingly decided to commit this very sin. The decision to rewrite Cobalt Strike could have ruined my company and destroyed my professional efforts going back to late-2011. Bluntly, people do their jobs with this toolset and they pay fairly for the privilege. My biggest fear is that my instincts were off and I was investing in the wrong ideas.

Six months later, all signs seem to indicate that Cobalt Strike 3.0 was the right move. The new product is very stable and performs much better than its predecessors. To many of my users, 3.0 was business as usual. I seem to have kept the things they used Cobalt Strike for, added some things they wanted, and discarded what they didn’t use. I’ve used 3.0 a few times now and I’ve been very happy with it. It’s a good product and it’s fun to use.

Aggressor Script

Scripting is one of those places where Cobalt Strike 3.0 gave me a second chance. Aggressor Script is the Cobalt Strike 3.0 successor to Cortana. Aggressor Script is not compatible with Cortana. It can’t be. Cortana builds on Armitage’s interface to the Metasploit Framework. Aggressor Script builds on Beacon and Cobalt Strike 3.0’s team server.

During Cobalt Strike 3.0’s development, I had a rule: no experiments. I knew the risk I was (already) taking with the rewrite. I wanted to stick with my lessons learned and my personal best practices as much as possible.

Cobalt Strike 3.0’s first milestones were a team server and a simple GUI client. The team server would act as a broker to receive messages from clients, broadcast messages to clients, and play previous messages back to new clients. On this initial foundation, I built Cobalt Strike 3.0’s first feature: the event log.

I remember demoing this early progress to a customer. I had a /names command, /msg, and /me. I also had the ability to redefine Cobalt Strike’s presentation of event log output through a default script. I joked that my goal was to replace Cobalt Strike 2.5 with a very nice, very expensive, IRC client.

All joking aside, I borrowed a lot of concepts from jIRCii in the design of Cobalt Strike 3.0. This is especially evident with Aggressor Script. Aggressor Script uses the set keyword to define how the client presents output to the user. Aggressor Script uses the on keyword to respond to events. Aggressor Script also uses the alias keyword to define new commands in Beacon. I also use scripting conventions from jIRCii to script menus and keyboard shortcuts. Much like jIRCii, Cobalt Strike 3.0 defines its representation of events and menu structure in a default built-in script. A lot of these conventions in jIRCii were heavily inspired by mIRC scripting.

So there you have it, that’s Aggressor Script’s Secret mIRC scripting past. There’s an irony here. In the 90s, some folks would use the scripting engine in their chat program to build hacking tools. Now, 20 years later, I use the scripting engine in my hacking tool to build chat tools. Pretty funny.