CyberSCI Nationals 2021 - Track 1 of 4: Command and Control

/assets/images/cybersci/cybersci_banner.png

Hello! A couple weeks ago I had the opportunity to compete in CyberSCI nationals, a cybersecurity competiton for university students across Canada.

It is an amazing event run by some great people, so if you are a university student in Canada be sure to check it out here 🙂: CSC21 - Cyber Security Challenge 2021

The scenario presented to us as competitors was there was there is a group called dogedays /assets/images/cybersci/doge_group.png that has dealings in cryptomining and ransomware, and it is our job to break into their operations.

The challenges were in the “hackquest” format. They were organized into 4 tracks, and in each track we “attacked” a different branch of their operations. These were:

  1. Command and Control
  2. Ransomware
  3. Blog
  4. CryptoDB

I am starting on the Command and Control track. This track involves breaking into dogedays command and control server and taking control of it. It involves two challenges that I will go over below. I will all briefly discuss the challenge starting point, although it doesn’t really count as a seperate track.

Challenge 0: Starting Point

Before we could get access to the dogedays network we needed to find valid credentials (for a vpn). The inital acceess we were given was to this personal bot.

Personal Bot for all my stuff:
https://d2q4qbwa42slid.cloudfront.net/index.html
cyberslob  /  *{unintelligible}*

By asking it the right questions we could find all the answers to the users personal questions, and get their password. For example, for Q1: What are your mother’s favorite flowers?, we would use the command order flowers, which would reveal that information in the form of order history.

I don’t have any notes for this one, but it looked like this: /assets/images/cybersci/track0.png

We now have the password so we can access the network and begin. 😝

/assets/images/cybersci/c2c1.png

Challenge 1: Obtaining Access to Command and Control

We are given the hostname: c2-server.dogedays.ca, and the following challenge:

Our recon squad found a C2 server machine that controls a vast array of victim computers all over the world. We urgently need to take control of it. There is a C2 server program on the machine - find it. Submit its full name with path as the proof that you were successful.

From the challenge description we can guess that there will be two steps to this challenge:

  1. Getting user access to the C2 server machine
  2. Finding the C2 Server on that machine

Getting Access to the Server:

We begin by scanning the machine to find running services: /assets/images/cybersci/track1/3.png

We can see that on port 8081 there is a blackice-icecap? service running, that sound familiar so I checked it out in my browser and got: /assets/images/cybersci/track1/1.png

Exploiting the Server:

We can see this an Apache Druid server, and we can also find the version number: 0.20. If we google this version we find:

Apache Druid 0.20.0 Remote Command Execution

Just our luck, there is even an exploit available on metasploit. /assets/images/cybersci/track1/2.png

Set the LHOST, RHOST, and RPORT options: /assets/images/cybersci/track1/5.png

Run the exploit:

/assets/images/cybersci/track1/61.png

I dropped into a shell here, because it was faster and easier to use use then meterpreter.

/assets/images/cybersci/track1/62.png

Finding the C2 Server:

Now that we have a shell we must find the c2 server. The proper way to do this would be to run find / -name "c2*" 2>/dev/null or something similiar, but since the c2 wasn’t exactly hidden it wasn’t necessary.

We can find the c2 server in the root directory (/). /assets/images/cybersci/track1/7.png

In this folder we have the actual c2 application:

root@ip-10-0-1-35:/c2-server-89fc41ac# ls
c2-server.pyc <--
private.pem

So the flag is /c2-server-89fc41ac/c2-server.pyc, and we can move on to the next challenge. /assets/images/cybersci/doge4.gif

/assets/images/cybersci/c2c2.png

Challenge 2: Shutting down the C2

We are given the following challenge description:

Great job getting into the server! Now we need to shut down the malware on the victim machines, before any further damage is done. Figure out how the server works, and stop the malware remotely. The flag is the ‘termination code’ that you receive upon successful malware shutdown.

Persistence

To make our lives easier, we use the running ssh service to get a friendlier shell. We put our public key in the authorized_keys file in /root/.ssh/authorized_keys.

root@ip-10-0-1-35:~/.ssh# echo "ssh-rsa AAA..." >> authorized_keys

We can now logon to the server anytime, via ssh w/o needing to have the password.

Back to the challenge:

Looking at the challenge description we can guess that we should do the following:

  1. Since the c2-server file is currently compiled python we should “uncompile” it to get the original code
  2. We should figure out how to connect to the client (victim) machine
  3. We should figure out how the communication between the client and server is done.

Getting the C2 Server Source:

The c2-server.pyc is a file containing the compiled bytecode of the original python source file. To translate the python bytecode back into the equivalent python source uncompyle6 was used.

Because it was easiest, I installed uncompyle6 right on the machine, and used it to uncompile the c2-server there:

root@ip-10-0-1-35:/c2-server-89fc41ac# pip3 install uncompyle6        	
pip3 install uncompyle6

root@ip-10-0-1-35:/c2-server-89fc41ac# uncompyle6 c2-server.pyc
uncompyle6 c2-server.pyc > c2_server_orig.py

Connecting the Victim:

Now we have the source code of the c2-server. Lets run it!

We get this menu: /assets/images/cybersci/track1/12.png

We can see the shut down command is there, but when we run it we get: /assets/images/cybersci/track1/16.png

Trying other commands gets us nowhere, so we exit the c2-server and look at the source code.

Examining the Source Code:

The server starts by making a connecting to client:

def main():
snipped...
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as (sock):
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('0.0.0.0', C2_SERVER_PORT))
            sock.listen()
            conn, addr = sock.accept()
            with conn:
                print(server_text('* Victim connected.'))
                response = receiveResponse(conn)
                print(client_text(response))
                continueRunning = True

It then gets commands from the user until the user selects exit.

while continueRunning:
        com = getMenuCommand()
        if com == EXECUTE:
            snipped...
        elif com == RANSOMWARE:
            sendBytes(conn, buildPackage(COMMANDS[com], ''))
            response = receiveResponse(conn)
            print(client_text(response))
snipped...

When the shutdown command is given it only prints the following message and doesn’t actually send anything to the client:

elif com == SHUTDOWN:
    print(server_text('* Great job, dummy, you forgot to implement this function... :( Then again, why would someone want to shut down malware on the victim machine?'))
snipped..

If we copy the code used for implementing RANSOMWARE (shown above) we get:

elif com == SHUTDOWN:
        sendBytes(conn, buildPackage(COMMANDS[com], ''))  # <- build data package and send to client
        response = receiveResponse(conn) # < - recieve response from client and print it.
        print(client_text(response))

So now we try to call shutdown:

> 4
invalid signature recieved

We must look further into how it works.

The first thing it does when you select a command is call buildPackage(). This function packages the data to be sent to client. We can also note it calls signData, which returns a signature. This signature is added to the package that is sent to the client:

def buildPackage(command, payload):
    data = intToBytes(len(command)) + command.encode('utf-8') + intToBytes(len(payload)) + payload.encode('utf-8')
    signature = signData(data)
    pack = intToBytes(len(signature) + len(data)) + signature + data
    return pack

The signData() function is the source of the problem, as it does not actually return a signature, but rather an empty string.

def signData(data):
    signature = b''
    signatureBlock = intToBytes(len(signature)) + signature
    return signatureBlock

We can also find an RSA private key in the c2-server folder, which is further proof that we need to implement the signData() function: /assets/images/cybersci/track1/10.png

This is likely the how the server server works (keeping in mind that this is a CTF so not everything will make perfect logical sense):

For all the other commands they are considered fully implemented so do not need a signature. But the shutdown command must be signed.

  1. The server signs the command shutdown with their private key, and sends that signature with the command itself.
  2. The client compares recieves the message, and signature and uses the servers public and the message to check the validity of the signature.

Here is a visual:

/assets/images/cybersci/track1/c2_signing.png

Implementing the signData Function:

Doing some googling we find this page, which has an example that works: https://riptutorial.com/python/example/19025/generating-rsa-signatures-using-pycrypto

The problem for our team was finding the correct padding, we mistakenly didn’t use any padding, and then tried to do math liked we had learned in school
. 😭

The correct padding is PKCS 1.5 which is somewhat standard (to rationalize its use here). I used the example code to implement the signData function so that it would correctly sign the shutdown message.

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
def signData(data):
    signature = b''
    
    with open('private.pem', 'r') as f:
        key = RSA.importKey(f.read())
    
    hasher = SHA256.new(data)  # <- compute the hash of the message
    signer = PKCS1_v1_5.new(key)  # padding
    signature = signer.sign(hasher) # <- sign the hash
    signatureBlock = intToBytes(len(signature)) + signature
    return signatureBlock

Then I ran the c2-server script again. /assets/images/cybersci/track1/18.png

The termination code 16e6d22e82b591d6 is the flag, so we are finished and we have completed track 1 or 4! 🏁 đŸ„ł

Thanks for reading! /assets/images/cybersci/doge3.png

Next I cover track 2: Ransomware here: CyberSCI Nationals: Track 2 of 4: Ransomware