7 minutes
Please Don’t Pass to ‘Exec’ - Making Web Requests in Sleep
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:
- It has functionality and menu’s to configure server settings for an API
- It is a script intended to integrate with an API
- 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:
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:
- A GET request
- To https://url/api/jobs
- With an Apikey header containing our API token
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)
check in the while loop
- This check is functionally implemented in the
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 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 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