.. keeps the backdoor in play.
In this post I will give you a review of the Offensive Security Engineering course from Udemy by Casey Erdmann, and showcase my further development of the custom C2 implant used in this course.
Context
I recently completed the Offensive Security Engineering course. ‘Buying’ this course was a no-brainer for me, since it was in a sale which made the course completely free. I really enjoyed it though, and learned more than I thought I would. I got to discover a whole new side of penetration testing/red teaming, a part which isn’t much talked about in my limited experience.
To be honest, in writing this I feel embarrassed that I got this course for free. If I knew how great it was I’d have happily paid for it. Easy for me to say of course, but I am serious, and so I pledge to purchase any follow-up course Casey Erdmann releases. I’m sure it will not disappoint.
A Round of Applause
The start of the course can seem a bit slow, introducing you to the tools of the deployment trade like terraform, the NodeJS serverless framework, a bit of AWS and the salt stack.
It picks up steam quickly though, making you set up a VPN to tunnel your metasploit shells through.
In the second part of the course though, you get swept up off your feet by a whirlwind of redirectors, custom and OTS1 webshells & C2 servers (Empire & Merlin), a complete phishing operation with Gophish and multiplayer C2 using Teamserver (Cobalt Strike & Armitage).
The real gold though, in my opinion, are the bits of red teaming advice Casey Erdmann gives throughout his course. I’ve made elaborate notes while going through the course and have distilled his tips to the following few key points:
- Running an effective red team back-end requires the best blue team practices. (with a few exceptions)
- Get in using custom C2 tooling, then stay in by disabling AV and upgrading to better C2 tooling (Merlin, Empire, ..)
- Being receptive to new tooling can make it so that you can bypass AV solely by the sheer novelty of the tool
(instead of trying to obfuscate and/or remove the artefacts of more established frameworks) - Keep track of the artefacts you’re leaving behind! You might be able to use them to get back in or privesc back to where you were before. This is also crucial since all of your artefacts will need to be removed afterwards.
- Stick to your role! If you need to simulate a sophisticated APT, pull all the tricks. If it’s inside the role, you can use off the shelf software like Metasploit & Covenant.
→ Stick to the TTPs!!
This course reminded me that I still have sooo much to learn. The TTPs seem such a core concept of red teaming, and before this course I had no idea they were even a thing. So one last time..
Tactics
Techniques
Procedures
In all honesty, this course was excellent and deserves its 4.6/5 rating on Udemy.
It also deserves a round of applause!
Beyond the Course
I really enjoyed the course and per Casey’s suggestion I started expanding and improving the C2 implants he provided in his course. I forked his repo in Github, then imported it to Gitlab.
My first changes made the go implant more stable by preventing it from crashing when it received an empty command or parsing fails for some other reason.
1
2
3
4
// Prevent out of bounds crash when parsing fails.
if len(cmdParsed) == 0{
return 0
}
I also applied the obvious improvements as pointed out by Casey. I added some global variables:
1
2
3
4
const cmd_delay time.Duration = 10
const port string = "1337"
const hostname string = "http://127.0.0.1"
const serverUrl string = hostname + ":" + port
1
2
3
4
5
6
7
# Configuration
SSL = True
SSLDirectory = "./"
SSLDomain = "localhost"
IP = '127.0.0.1'
port = 1337
An Insecure (Web)Shell
I then began adding TLS support, so that the implant could connect using the HTTPS protocol. This was easy for both Python and Go, but since this was my first time working with Go I had to feel my way around first.
Accepting any TLS cert in go was a bit more challenging as I wasn’t at home at all with the &<object> syntax. It came around to the following small piece of code DuckDuckGo nicely retrieved for me:
1
2
3
4
5
6
7
8
// Create http client
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
I also added a python routine to generate a new self-signed certificate:
1
2
3
4
5
6
def generateSSLKeyAndCert():
generateKeyAndCertCmd = "openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -addext 'subjectAltName = IP:" + str(IP) + "' -days 365 -nodes -subj '/CN=" + SSLDomain +"'"
args = shlex.split(generateKeyAndCertCmd)
print("Generating fresh SSL key and certificate..", end="")
p = subprocess.run(args,capture_output=True)
print(" done!")
A Language Pivot
Since I’ve got a technical interview coming up which I expect to be Java-heavy, I decided to make a Java C2 implant that could talk to the python server. This in and of itself isn’t the worst idea with Java being a very prominent cross-platform language, but I think the compiled go payload lends itself to obfuscation more easily.
To get the java code to accept any kind of certificate is easily achieved with the following snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Create the all-trusting TrustManager
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
// Use the all-trusting TrustManager to initialize the SSL Context
sc.init(null, trustAllCerts,new java.security.SecureRandom());
// Create HTTPS connection Object (note: no actually connection to the URL is made at this point)
con = (HttpsURLConnection) url.openConnection();
// Apply SSL Context to the HTTPS connection
con.setSSLSocketFactory(sc.getSocketFactory());
When https support was enabled and the C2 implant returned its results, I switched over to refactoring. Also improving the flow of the program to make sure not a single GET request went to waste. I sprinkled some comments around where needed and went on to the next objective: actual security.
A Secure (Web)Shell
By this time my java implant was far beyond the Golang one, although I might be biased. :)
In any case, I’m quite pleased with how it turned out. Functionality-wise it almost has everything I could want in a basic C2 implant.
It has one glaring vulnerability though. Accepting any HTTPS certificate allows an attacker to easily pull a man-in-the-middle attack. It’s only logical the next objective was to actually making the webshell secure. It was high-time to lose the all-trusting TrustManagers and InsecureSkipVerify: true’s of this world.
A Secure Python Server
The first step was to add root certificate generation to the python server, this allows us to sign our HTTPS certificate with a root cert we can later include in the C2 implant and trust exclusively.
This was relatively straightforward to do, but required some focus to keep all of the variables straight:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
root_key = SSLDirectory + "root.key"
root_cert = SSLDirectory + "root.crt"
rootname = "C2-testing-CA"
# Generate private key & root certificate
root_gen_cmd = "openssl req -nodes -new -x509 -keyout " + root_key + " -sha256 -days 420 -out " + root_cert + " -subj '/CN=" + rootname +"'"
args = shlex.split(root_gen_cmd)
print("Generating fresh root key and certificate..", end="")
p = subprocess.run(args,capture_output=True)
print(" done!")
# Use the root private key & cert to generate a new -signed- cert for this domain.
server_key = SSLDirectory + "server_" + SSLDomain + ".key"
server_cert = SSLDirectory + "server_" + SSLDomain + ".crt"
server_CSR = SSLDirectory + "server_" + SSLDomain + ".csr"
server_SAN_config = SSLDirectory + "server_" + SSLDomain + ".ext"
# Create a Private key for the to-be-signed cert
server_gen_key_cmd = "openssl genrsa -out " + server_key + " 2048"
args = shlex.split(server_gen_key_cmd)
print("Generating fresh private key for the server..", end="")
p = subprocess.run(args,capture_output=True)
print(" done!")
# Create a Certificate Signing Request (CSR)
server_gen_csr_cmd = "openssl req -new -key " + server_key + " -out " + server_CSR + " -nodes -subj '/CN=" + SSLDomain +"'"
args = shlex.split(server_gen_csr_cmd)
print("Generating a Certificate Signing Request for the server certificate..", end="")
p = subprocess.run(args,capture_output=True)
print(" done!")
# Create Subject Alternative Name (SAN) configuration file
with open( server_SAN_config, "w") as SAN_config_file:
SAN_config_file.write("""
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = """ + SSLDomain)
# Create signed certificate
server_crt_signing_cmd = "openssl x509 -req -in " + server_CSR + " -CA " + root_cert + " -CAkey " + root_key + " -CAcreateserial -out " + server_cert + " -days 420 -sha256 -extfile " + server_SAN_config
args = shlex.split(server_crt_signing_cmd)
print("Signing the server certificate with root key..", end="")
p = subprocess.run(args,capture_output=True)
print(" done!")
# => Use <domainname>.key & <domainname>.crt for https server
httpd.socket = ssl.wrap_socket (httpd.socket,
keyfile=server_key,
certfile=server_cert, server_side=True)
A Secure Go Implant
To make the Go implant exclusively trust the root certificate was rather easy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Create new cert pool
rootCAs := x509.NewCertPool()
// Read in the cert file
cert, err := ioutil.ReadFile(rootCertFile)
if err != nil {
// As an implant we don't do crashes!
// log.Fatalf("Failed to append %q to RootCAs: %v", rootCertFile, err)
}
// Add cert to certpool
rootCAs.AppendCertsFromPEM(cert)
// Trust the certpool
config = &tls.Config{
InsecureSkipVerify: false,
RootCAs: rootCAs,
}
Since we’re developing an implant, having it leave additional artefacts isn’t desirable.
We want to include the root certificate as a string inside the source, which is easily done in Go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Replace
cert, err := ioutil.ReadFile(rootCertFile)
rootCAs.AppendCertsFromPEM(cert)
// With
var rootCert string = `-----BEGIN CERTIFICATE-----
MIIDETCCAfmgAwIBAgIUQ++p3RnqZIEck9B4Clk+W7qlLrswDQYJKoZIhvcNAQEL
BQAwGDEWMBQGA1UEAwwNQzItdGVzdGluZy1DQTAeFw0yMDEwMTYwOTA1MTdaFw0y
MTEyMTAwOTA1MTdaMBgxFjAUBgNVBAMMDUMyLXRlc3RpbmctQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDfPxM7G8knDe2+lWO2AUnrPkjVp00m8gq2
pM3eK83Vm0O9Iz7nEPolIOCB4hlD4hab3NdGkLk88UhpOGITyxrhszJO7Lrr2JRv
Gg+foquxGc87g7JIn68A/jkpoFbystlnma8U6O1gIFE/nMlWmJiI72y5fvt+K1QM
Cl0CQicbQ5zVTJXadOhc96sp8wRmXP/MCfgRqb6OhrLLxbxATCcPVfeUa4U72ELI
KBBy7XHHOVJKXnJveqYt8sJbgD4uCrRab6wIP54C9ribTsIHXNDMBnYnuITK2Khe
03Bgh4P3XaSYBYkvkUVNjZQKAqpaumxT4XUJlmxS9Tk/Ylosce5hAgMBAAGjUzBR
MB0GA1UdDgQWBBRwx6MAEWoUSOG0p0MyaiuIJ1HaRTAfBgNVHSMEGDAWgBRwx6MA
EWoUSOG0p0MyaiuIJ1HaRTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4IBAQCXH28a3PWqK8iulos2rpEJQ630Z888HLiF0a7rmWbW9d11OK9Ak68Mbp3v
5aGTZLS37j/2VapG9FKDNjsv3GgxMZvZLILSLXgictXzkEoDH+B62uclH23CwlUL
xmYIsKijkuEIsRSmtXTs3iWwZR+Urpp+lQJDveskvF/Cyh/8dMhLZthxLSHE3JTJ
tNOMlxEJcX3d9wrFtAhNxIqJfYy75g27tG2u8/OQ1xdGcMRNk8OwCewZFXFxV7lx
bnzu8EMt06zKWZxCM2gxq5wdtwj1vMwiQSwf18XsguBFqPRrtYoPHsJH9mN6kDYJ
hPnSHjmcNSJGvEU/5buBLy9vnLeE
-----END CERTIFICATE-----`
rootCAs.AppendCertsFromPEM([]byte(rootCert))
I’ve been thinking (and attempting) to use build parameters to inject the root certificate dynamically. It’s pretty easy to set up for simple strings, but I couldn’t find a way to make it read the root certificate. My current hunch is that it’s got to do with the length or some offending characters, since it works for ordinary strings. I stopped investigating this in favor of completing the Java implant. I might get back to this later on, as it is much cleaner than including the string blob statically in the source code.
A Secure Java Implant
To make the Java implant do the same was something else entirely.. I was lost in the in the documentation of keystores, factories and contexts. The snippet about to follow took me several hours to cobble together and is considerably more ugly than the equivalent in Go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// To start of, we need an SSLSocketFactory:
private static SSLSocketFactory createSSLSocketFactory(File crtFile) throws GeneralSecurityException, IOException {
SSLContext sslContext = SSLContext.getInstance("SSL");
// Create a new trust store, use getDefaultType for .jks files or "pkcs12" for .p12 files
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// You can supply a FileInputStream to a .jks or .p12 file and the keystore password as an alternative to loading the crt file
trustStore.load(null, null);
// Read the certificate from disk
X509Certificate result;
try (InputStream input = new FileInputStream(crtFile)) {
result = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input);
}
// Add it to the trust store
trustStore.setCertificateEntry(crtFile.getName(), result);
// Convert the trust store to trust managers
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
}
// Then create the HTTPS connection as follows:
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
// Load the rootCert file from disk
con.setSSLSocketFactory(createSSLSocketFactory(this.rootCert));
There’s probably a better way to do this, but I’m already quite happy it works the way it is. Now to make Java load the root certificate as a string is again a bit more ugly than it was in Go (courtesy of no multi-line string support):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
String rootCert = "-----BEGIN CERTIFICATE-----\n" +
"MIIDETCCAfmgAwIBAgIUc2G/nQpdc04TomkW4QiX+RYh44AwDQYJKoZIhvcNAQEL\n" +
"BQAwGDEWMBQGA1UEAwwNQzItdGVzdGluZy1DQTAeFw0yMDEwMTYxNzEzMTZaFw0y\n" +
"MTEyMTAxNzEzMTZaMBgxFjAUBgNVBAMMDUMyLXRlc3RpbmctQ0EwggEiMA0GCSqG\n" +
"SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAgkj9+cU5iZryBw6rRnPQVjpfEu1NF1SZ\n" +
"H8eeDWoYn+1RnVP0yOnNqYHT9ES3ZYp3qt12tgRnBC+fSbVxceDridkUTA6mpWrB\n" +
"1uu73qAjq7Qg6DV2pvdTbFWSt4o0uCgRyO7No4En+kpNcd6LvFbitV4P8+sdgrIm\n" +
"lcuQsQW1lxTnNJEc76e4d/iJvauG/6Atxi7l0z+Z/aSCtj4LxOsBnDdOmBlco0w4\n" +
"zaES/8Yb1wyuNRehqFEVzifUy6hq/7l63vlK/u1EqYb6FrRTAoWoJ7gK0nfO1R7l\n" +
"cJh9npEGitoWltEYP+1DFvVwJs9PcUOCAY2JOlmZbR575jml+RitAgMBAAGjUzBR\n" +
"MB0GA1UdDgQWBBT30EAnf1Lt60Kxpl8q5uU6aVO7HTAfBgNVHSMEGDAWgBT30EAn\n" +
"f1Lt60Kxpl8q5uU6aVO7HTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA\n" +
"A4IBAQCoztAWIoWIFA1qYx3LSuDZ77sh0iKjfWEgEI+2i7CTcKfWvZStPJzVNuYq\n" +
"v5dD4b/CoomXaIuEybu8N4wl+UU3diGTSC5NIRK9RLnTHJllllTb25ONNaMCHPYb\n" +
"wSHhXUoRbOZS98N2nRKmExnHVHp5XAzBWFwxmbufGNYGLE9N4HosPlyBaLedatlH\n" +
"e1GS8pOrQu3byhL3K1S+Ok2WI7zC5LS/hnM2/jytAixjJrdRYhEDI5qEoHfjUdDI\n" +
"7izcLN3uDIsNn9VLQ593QHDiAwiCPVjSRh0jXYRSMHHnxW2fCBO5yoxSYyXhFLGd\n" +
"PS6SOkBTPdq+sn+IT6FL2e+TEnA2\n" +
"-----END CERTIFICATE-----";
// To load the root certificate above, replace
try (InputStream input = new FileInputStream(crtFile)) {
result = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input);
}
trustStore.setCertificateEntry(crtFile.getName(), result);
// With
try (InputStream input = new ByteArrayInputStream(rootCert.getBytes())) {
result = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input);
}
trustStore.setCertificateEntry("rootCert", result);
This last bit probably could’ve been solved by making a fat jar (which included its runtime dependencies, possibly including the root certificate). I had initially not thought of this option and so have not explored it. I do like the simplicity of simply embedding the string, but it does have drawbacks. As far as I know including the file in the jar would be the best way to make the root certificate dynamic, but still baked-in. An equivalent for using build parameters in Go.
A Mobile Implant
Now that the Java implant was complete it would be easy to make the transition to Android.
The first thing to do was to add the INTERNET permission to the manifest file. I’ve not done a lot of Android development, but after figuring out how to trigger the connection when pressing a button I stumbled into my first exception:
1
android.os.NetworkOnMainThreadException
This exception serves to push people away from doing dumb stuff when developing a mobile application. Responsiveness is everything when dealing with (mobile) applications, so it is important to keep slow, blocking things away from the main thread. Otherwise the app will freeze until the slow (blocking) function returns with its result. In this case the app would become unresponsive until whatever networking I did completed, undesirable for a decent app but perfectly fine for a proof of concept like this. I did however choose not to simply ignore the exception, both because when doing implants one doesn’t want to make it obvious and it really isn’t hard to fix.
I decided to go for the easiest solution that I could find, using the Thread library to create and start a new thread:
1
2
3
new Thread(new Runnable(){
// Implant connection loop
}).start();
Android Shells
When I actually got the connectivity working again, I hit one more hitch by trying to use printf on a null value (when the local results were empty). Swapping printf out with System.out.println easily fixed that. I’ve honestly got no clue why I used printf there in the first place.. Either way, this got me to the next serious decision I needed to make: How to execute commands on Android?
First I was thinking about implementing everything myself, essentially creating my own crappy and very limited version of bash. But I was getting strange feedback from the C2 implant when trying to run commands. Some commands would return a “this command does not exist” type of error, while others would return nothing. This made me think some commands were actually successfully running in some kind of Android shell, but either I wasn’t properly capturing their results or something was causing it not to return any results.
The implant still uses Runtime.getRuntime().exec(command);
to execute commands, but how does the Runtime library actually work on Android? Where do the commands go? I didn’t have a clue Android had any resemblance of a shell, but it makes sense seeing where Android came from.
In the next few days I plan on finding out all I can about this mystical Android shell.
Footnotes
off-the-shelf ↩