Skip to content

Instantly share code, notes, and snippets.

@EvanMcBroom
Last active December 14, 2023 20:39
Show Gist options
  • Save EvanMcBroom/3020e8d2d2dfc3de292e3b68b8b01014 to your computer and use it in GitHub Desktop.
Save EvanMcBroom/3020e8d2d2dfc3de292e3b68b8b01014 to your computer and use it in GitHub Desktop.
Sleepy - Python Tooling for Sleep

Sleepy - Python Tooling for Sleep

Thank you to SpecterOps for supporting this research and to Sarah, Cody, and Daniel for proofreading and editing! Crossposted on the SpecterOps Blog.

TL;DR: You can use sleepy to automate common tasks when working with Sleep code.

Raphael Mudge created the embeddable scripting language, Sleep, in April 2002. Sleep was designed to extend Java applications and has been used in few projects; most notably Cobalt Strike.

Cobalt Strike's reliance on a relatively unpopular language with a small developer community has resulted in a need for language tooling and a lack of community support to build it. In this article, I will present a brief primer on Cobalt Strike's usage of Sleep, an issue that SpecterOps and I have experienced due to Sleep’s lack of tooling, and the solution we developed to overcome this issue.

Aggressor Scripts

Using Sleep scripts is the officially supported method for users to modify, extend, and automate Cobalt Strike. The distribution of Sleep that comes with Cobalt Strike is heavily modified to support these use cases. First, the distribution is extended with snippets of Java code (i.e., "bridges") that allow Sleep code to interact with features of Cobalt Strike. Second, the distribution ships with an additional library of premade functions to simplify working with the product. These modifications are significant enough that code written for Cobalt Strike's distribution of Sleep is referred to using a new term (i.e., “aggressor scripts”), but the underlying language is the same.

Many users have written aggressor scripts for Cobalt Strike, of which the free and open-source software (FOSS) ones are tracked by the product's parent company (i.e., Fortra) on their Community Kit page. A subset of these scripts are designed to provide front end user information for another type of product extension known as a Beacon object file (BOF). We use many community provided BOFs at SpecterOps and internally manage their updates using Git, a CI/CD pipeline, and an artifact store.

Proprietary Systems Integration

These systems have allowed us to automate the majority of the process of managing BOF updates but they introduce an overhead problem. Our systems will push compiled BOFs to an artifact store and their associated aggressor scripts will need to be updated to pull from that store instead of the file system. Due to the lack of Sleep tooling, we are required to manually inspect these aggressor scripts after each update to check if they have meaningfully changed from our modified copies (e.g., by adding user documentation for a new BOF argument). If they have, we then need to manually update our internal copies appropriately.

Our systems are automated enough that they do not need a dedicated employee to tend to them each day, but the manual component requires an employee to shepherd each new update we do. We would ideally automate the process of modifying public aggressor scripts to pull from our artifact store; however, such a task would require Sleep tooling – specifically, a lexer, parser, and code generator for the language.

Sleep's [Lack of] Tooling

Technically, the official language distribution comes with tools, but they do not support our use case. The distribution provides a standalone read-eval-print loop (REPL) command-line interface (CLI) tool for executing Sleep code and an embeddable parser written in Java. The REPL tool does not help with our specific problem and the parser does not produce a modifiable abstract syntax tree (AST) that may we use to generate new Sleep code.

Updating the existing parser to support our needed features would be too problematic. The parser conflates lexing and parsing tasks in its class design to where updating one class would break other classes. In design terms, they have high coupling and low cohesion. Even if the parser supported our needed features, it would need updates regardless because it does not conform to its own language documentation. As an example, the parser allows for missing semicolons after statements and missing commas between list items; both issues should be caught as syntax errors.

Our Solution

Presented with these issues, we decided to write our own lexer and parser for Sleep. Each may be used as standalone tools and the parser will generate a modifiable AST that may be used to generate new Sleep code. Both tools were written as a Python library named sleepy because it is a common language for all of our coworkers to develop in. The tools support all but one language feature, so-called Java object expressions (a.k.a, "Haphazard Object Extensions for Sleep"), which is enough support for our use cases and hopefully any use case you may have as well. You may access the project here and the project examples here.

With these tools, we are now able to fully automate our internal systems for managing BOF updates. When a community BOF is updated in our internal Git repositories, our CI/CD pipeline may use our parser to convert an associated aggressor script to an AST, modify any code in the AST that reads a BOF from the file system to instead read it from our artifact store, then generate a new aggressor script from the modified AST that our operators may use.

Let me show you an example of how we may do this. We will use TrustedSec’s “Situational Awareness” project to demonstrate because we have it integrated with our internal update management workflow. The project includes C code for several BOFs but we will focus the project’s aggressor script. The main goal of the script is to register a new beacon command for every BOF the project defines. The script defines a utility function named readbof for reading the project’s BOFs from the filesystem. For our use case, we would prefer the readbof function to read a BOF from our artifact store. Sleepy makes it simple to automate that change.

First, we will create an aggressor script that defines our new readbof function.

sub readbof {
    local('$bofArch $bofName $bofBytes');	
    $bofArch  = barch($1);

    # Convert the BOF name to its file name in the artifact store
    $bofName = $2 . "." . $bofArch . ".o";

    # Use our own function to access the BOF
    $bofBytes = download_from_artifact_store($bofName);
    if(strlen($bofBytes) == 0) {
        berror($1, "*ERROR* Failed to access BOF in the artifact store: $bofName");
    }
    btask($1, "Running BOF: $bofName");
    return $bofBytes;
}

Then, we will use sleepy’s parser to convert the script into an AST.

from sleepy.parser import *

patch = None
with open('new_readbuf.cna', 'r') as file:
    parser = SleepParser(quiet=True)
    script = parser.parse(file.read()) 
    patch = script.body[0]

Finally, we will use sleepy’s parser to enumerate each statement in the original script, replace its readbof function with our patch, and output a new script that works with our artifact store.

with open('SA.cna', 'r') as file:
    # Convert the original script to an AST
    parser = SleepParser(quiet=True)
    oldScript = parser.parse(file.read())

    # Create an empty AST to build the new script
    newScript = Script([])

    # Build a new script from the original and
    # replace the readbof function with our patch
    for statement in oldScript.body:
        if isinstance(statement, EnvBridge) \
            and statement.keyword == 'sub' \
            and statement.identifier == 'readbof':
            newScript.body += [patch]
        else:
            newScript.body += [statement]

    # Output the new script
    print(newScript)

This was a simple example because only one patch was needed, but sleepy is flexible enough to automate complicated patches and other use cases as well.

To show some of these use cases, the project includes a longer example that converts an aggressor script to a JSON file that summarizes all of the BOFs it defines a Beacon command for, if any. Such a script is useful for integrating BOF updates with other systems such as an internal documentation server or another C2 platform. A similar script could also be used to notify operators when the documentation for a Beacon command has changed so that they may review it. These are only a few examples and I am sure readers will have more ideas for how the project can help with their own internal workflows.

Conclusion

Managing updates for third-party Sleep code can cause management overhead that is difficult to automate due to a lack of tools for the language. We specifically encountered this problem at SpecterOps when updating aggressor scripts for open-source BOFs to integrate with our internal systems.

The solution we developed was to build a lexer, parser, and code generator for Sleep which we are releasing for others to use as well. The project includes example ways to use the tools including a syntax checker, code formatter, and code minimizer. We hope you enjoyed this work and find the tools helpful for your own internal workflows!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment