Please Don’t Pass to ‘Exec’ – Making Web Requests in Sleep

tl;dr

To avoid dependency issues take advantage of built in Java functionality provided by the Sleep scripting language used to write Cobalt Strike Aggressor Scripts. This post examines making web requests without passing execution to an external binary and also discusses using threading to make long requests in the background.

Why?

Recently I was working to build a Cobalt Strike Aggressor Script to integrate my latest work on Cryptbreaker (adding an API) with arguably one of the nicest C2 frameworks out there (the others are good and nothing’s perfect but I find myself using CS a decent amount).

In order to accomplish this I started my work like all good projects start – by identifying an existing Aggressor Script and copy-pasting large sections of functionality. For this particular script I chose to start with bluscreenofjeff’s beaconpire aggressor script.

I chose this script for a few reasons:

  1. It has functionality and menu’s to configure server settings for an API
  2. It is a script intended to integrate with an API
  3. bluscreenofjeff is generally awesome

Thanks to the existing code I quickly had a way to configure my server. Everything seemed to be going smoothly until I went to make a web request to my configured Cryptbreaker instance. My Cobalt Strike instance:

CS Error Message.jpg

Looking at the code for the function that led to that error showed something similar to the following…

$curl_command = exec("curl --insecure -i https://" . %cryptbreakerserverSettings["ip"] . ":" . %cryptbreakerserverSettings["port"] . "/api/jobs" --header "Apikey: " . %cryptbreakerserverSettings["token"]);
    @listeners = readAll($curl_command);
    closef($curl_command);

I mean, that looks sane enough. We use curl to make our web request and… oooooohhhh wait. This script is using exec to pass a web request out to curl. This works… but not on my Windows machine.

This got me thinking. There has to be someway to make a web request without passing execution out to another executable. I want to avoid this to ensure that my Aggressor Script works for all potential users regardless of what OS they’re using and to avoid adding dependencies to my code.

I searched the official Sleep documentation and Cobalt Strike Aggressor Script documentation for a while looking for some way to do this to no avail! How could a scripting language call itself a scripting language when you can’t even make a simple web request? (I thought to myself….)

Then I realized how dumb I was being after reading the second paragraph of the introduction of the Sleep scripting language:

Sleep is a Java-based scripting language heavily inspired by Perl. Sleep started out as a weekend long hack fest in April 2002. When nothing like Perl was available to build a scriptable Internet Relay Chat client, I set out to build the scripting language I wanted

So, Sleep is Java scripting language. As such, it allows you to directly interface with and use Java objects and classes. This means that we can make web requests natively in Sleep, we just have to leverage the underlying power of Java.

The official docs go into pretty good detail on how to access and use Java objects here so the remainder of this blog will focus on taking the previous curl command and converting it to a Sleep friendly, Java-native implementation.

Converting

The first step in this conversion process is to figure out what the Java implementation of our desired web request looks like. For this example we’ll assume that our request is the one shown in the earlier curl command that had the following attributes:

Using the standard java.net and java.io libraries our web request in pure Java might look something like this:

import java.net.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // Set our Site URL to contact
        URL siteURL = new URL("https://cryptbreaker.url.here");
        // Create our connection object to reference
        HttpURLConnection siteConn = (HttpURLConnection) siteURL.openConnection();
        // Set our headers
         siteConn.setRequestProperty("Apikey","api key value here");
        // Create a BufferedReader to read the response data from our connection
        // (making the connection in the process)
        BufferedReader in = new BufferedReader(new InputStreamReader(
                    siteConn.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        // Read all the output
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        // Close the buffered reader
        in.close();
        // print result
        System.out.println(response.toString());
    }
}

Following the principles laid out in the Sleep documentation converting this to Sleep is pretty straight-forward. Lets break it down one line at a time.

Starting at the top of our Java we import a couple libraries java.net.* and java.io.*. So at the top of our aggressor script we’ll have to add those imports.

With imports out of the way we can get to the main functionality of making a web request.

The Java code

URL siteURL = new URL("https://cryptbreaker.url.here");

Becomes

$siteURL = [new URL: "https://cryptbreaker.url.here" ];

Breaking this line down. Our variable declaration is stored into a Sleep scalar. We don’t need to declare a type in Sleep so URL siteURL becomes $siteURL

When declaring a new object expression in Sleep it takes the format of [target message: argument1, argument2, ..., argumentN] so in the case of our siteURL declaration we want to create a new object of type URL with a provided constructor argument so new URL("https://cruptbreaker.url.here") becomes [new URL: "https://cryptbreaker.url.here"].

With our siteURL variable declared we can move onto the second main line of logic where we declare our site connection. In Sleep instead of using dot notation we use the same [target message: argument1, argument2, ..., argumentN]previously discussed so our line of

HttpURLConnection siteConn = (HttpURLConnection) siteURL.openConnection();

Becomes

$siteConn = [$siteURL openConnection];

Seeing this pattern we can convert our next line pretty easily too.

siteConn.setRequestProperty("Apikey","api key value here");

Becomes

[$siteConn setRequestProperty: "Apikey", "api key value here"];

Instead of converting each and every line of the remainder of the script here lets just focus on two potentially tricky areas: declaring the BufferedReader and iterating over the resulting data.

BufferedReader

I wanted to call out the BufferedReader as it is the first time we’ve had to nest the creation of objects in a single Sleep statement. There’s not too much that is tricky aside from that fact that nesting works as you might expect.

Our original line was:

BufferedReader in = new BufferedReader(new InputStreamReader(siteConn.getInputStream()))

And using the patterns described so far it translates to:

$in = [new BufferedReader: [new InputStreamReader: [$siteConn getInputStream]]]

Iterating over results

The last main section to convert is our reading of the request response from our BufferedReader. Our Java code was the following:

String inputLine;
StringBuffer response = new StringBuffer();
// Read all the output
while ((inputLine = in.readLine()) != null) {
    response.append(inputLine);
}
in.close();
System.out.println(response.toString());

The conversion here is mostly 1-1 but not entirely. Our logic control occurs in Sleep and not in Java so we’re more heavily mixing Sleep and Java syntax.

The converted Sleep-friendly equivalent is:

$inputLine = [new String];
$done = 0;
$results = "";
while($done != 1){
    $inputLine = [$brIn readLine];
    $results = $results .$inputLine;
    if(strlen($inputLine) <= 0) {
        $done = 1;
    }
}
[$brIn close];
println($results);

Key differences in our conversion are:

  • We continue to use the Java String type to handle each line of input but use the Sleep string concatenation to build our result data vs the original solution’s use of a StringBuffer. (String concatenation in Sleep is "string1" . "string2"
  • Our loop uses a conditional checking to see if the variable $done has been set to 1 instead of the Java implementation of the read input line being null
    • This check is functionally implemented in the strlen($inputLine <=0) data-preserve-html-node=”true” check in the while loop

Making Requests in a new Thread

Sometimes web based execution can take a few seconds to complete. When this is the case we’ll want to avoid locking the main UI thread because we’re waiting for a network operation to complete. We can accomplish this by executing our network calls in a separate thread. While multi-threading is normally one of the more painful parts of scripting Sleep thankfully has a relatively easy way for us to accomplish this: the fork fuction

The Sleep documentation describes fork in the following way:

$ fork(&closure, [$key => $value, ...])

Where &closure refers to the function to run and an optional list of $key => $value pairs which can be used to pass values to the function.

In the instance of our long running web call we can use fork to make the request and process results without locking up the web UI. Lets say our function is called longRunningRequest and we want to pass it a value of $urlToRequest where we want to pass http://www.google.com as the url to request. In this instance our fork request could look like:

fork(&longRunningRequest, $urlToRequest => "www.google.com");

Now if we reference $urlToRequest in our longRunningRequest function we’ll get the value of http://www.google.com

Hopefully this explanation can help make your next Aggressor Script better. An additional resource that could help too is the aggressor script language support for Visual Studio code (if that’s your IDE of choice). Details here and you can install via the VS Addons menu.

Happy Hacking

-sy14r

Share:

Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on facebook
Facebook
Scroll to Top