CyberSCI Nationals 2021 - Track 1 of 4: Command and Control
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 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:
- Command and Control
- Ransomware
- Blog
- 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:
We now have the password so we can access the network and begin. đ
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:
- Getting user access to the C2 server machine
- Finding the C2 Server on that machine
Getting Access to the Server:
We begin by scanning the machine to find running services:
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:
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.
Set the LHOST
, RHOST
, and RPORT
options:
Run the exploit:
I dropped into a shell here, because it was faster and easier to use use then meterpreter.
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 (/
).
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.
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:
- Since the c2-server file is currently compiled python we should âuncompileâ it to get the original code
- We should figure out how to connect to the client (victim) machine
- 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:
We can see the shut down command is there, but when we run it we get:
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:
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.
- The server signs the command shutdown with their private key, and sends that signature with the command itself.
- 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:
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.
The termination code 16e6d22e82b591d6
is the flag, so we are finished and we have completed track 1 or 4! đ đ„ł
Thanks for reading!
Next I cover track 2: Ransomware here: CyberSCI Nationals: Track 2 of 4: Ransomware