Roll Your Own Basic C2

Gr@ve_Rose
8 min readAug 5, 2020

A command and control system (C2) is a method where you have a client and server where the client executes commands as seen from the server. Most of the time when people heard about C2 infrastructure, they think of RedTeam activities however you can use the same principles for remote system administration. Here’s how to build your own basic C2 infrastructure.

The need for this requirement stemmed from having on-site (CPE) devices with customers however not every customer wants to configure a VPN with us (for whatever reason). This means that we need a way to manage the CPE devices securely but without updating infrastructure in a major way. Although we are fully dual-stacked with IPv4 and IPv6, not all of our customers are which means that direct access and firewalling with live IP addresses is out of the question for using IPv6 as a solution. Since most, if not all, customers also use RFC-1918 addresses on the inside and perform Hide/Dynamic NAT, this is out for IPv4 as well. Since we can’t go to them, they need to come to us.

Having the CPE device SSH to us (locked down with firewalling) is the way we want to go. “But wait… I thought you wanted to SSH into the CPE device, not have the CPE device SSH to your infrastructure.”, you may say. And you’d be right. We’re going to leverage SSH remote/reverse tunnels to accomplish our goal.

With SSH, we’re able to create tunnels inside the SSH connection itself. You can use these tunnels as SOCKS proxies to funnel traffic through, forward individual ports out the other side (Local SSH Port Forwarding) or have a new TCP socket opened up on the server to interact with locally (Remote/Reverse SSH Port Forwarding). The last one is what we’re going to work with.

The basic concept is that the CPE device is going to create an SSH connection from itself to our SSH server and within that SSH tunnel, will instruct the server to open TCP/2000 in a LISTEN state. Any traffic sent into that socket will then travel through the SSH connection to the CPE device to be processed. Our destination configuration is the CPE device itself on TCP/22 — SSH.

Here’s how that is going to look with a diagram. The actual commands and flow information will be after.

Basic Topology

Here you can see a very basic network setup. We have a C2 server running a web server and an SSH server behind our firewall. We’ll add a Static NAT (for IPv4) so that these will be reachable from the outside but only for the source IP of the Client firewall. We definitely don’t want people to try and brute-force their way into our C2 server. We will also publish a DNS name for our C2 instance; We’ll use “c2.example.com” for our purposes of this document. On the right is the client’s firewall and our CPE device behind it. They need to ensure that HTTPS, SSH and DNS is allowed outbound. If they use an internal DNS server, we can eliminate the need for outbound DNS.

Calls From CPE to C2

Now the CPE device will make calls over HTTPS to our C2 server to see if there are any commands for it to process. Our only command options we’re going to build in are “start” and “stop” to manage our SSH connection. “But how do we get the CPE device to make calls and process actions?”, you ask. Good question. Let’s start digging into the details.

Our CPE script is a basic BASH script which is called with a cron job every x minutes. For our example, we’ll set it to run every ten minutes. Here’s the script (Apologies for the formatting — It seems Medium doesn’t like it too much):

#!/bin/bash
URL=”https://c2.example.com/client/check.html"
CURL=$(which curl)
OF=”/tmp/check”

SSH=$(which ssh)
KEY=”/root/.ssh/new_rsa”
LPORT=22
RPORT=2000
USER=”user”
RHOST=”c2.example.com”

$CURL -k -s $URL -o $OF > /dev/null

RESULT=$(cat $OF)

if [ “$RESULT” == “start” ]; then
if [ “$(ps ax | grep ssh | grep 127.0.0.1\:22 | grep -v grep | wc -l)” == “0” ]; then
$SSH -oStrictHostKeyChecking=no -i $KEY -T -N -R $RPORT:127.0.0.1:$LPORT $USER@$RHOST
fi
elif [ “$RESULT” == “stop” ]; then
if [ “$(ps ax | grep ssh | grep 127.0.0.1\:22 | grep -v grep | wc -l)” == “1” ]; then
kill $(ps ax | grep ssh | grep 127.0.0.1\:22 | awk ‘{print $1}’)
else
echo “Got here”
fi
fi

#EOF

We will go through all of this and piece it together but right now, we’ll focus on the C2 checking. The first thing we do is set a few variables that we will use:

$URL is the full URL of where our “start/stop” page will be.
$CURL is set to the local path of our curl program.
$OF is our output file

We then run our command to get the contents of the $URL and store it in the $OF with “$CURL -k -s $URL -o $OF > /dev/null”. Lastly, we create a new variable called $RESULT which will be either “start” or “stop”.

That’s it. Every ten minutes, we get the contents from the webpage from our C2 server and stick it in a file.

But how do we connect?

We are going to have to use passphrase-less PKI for our SSH connection since this needs to be automated; There won’t be anyone to type in a passphrase or password. Before we get to deploying the CPE, there are some things we need to prepare.

  1. On the client, run `ssh-keygen -t rsa -b 4096 -C “root@cpe”` replacing “root@cpe” with whatever is required for the user and the system name. DO NOT enter a passphrase when prompted.
  2. Now that you have a passphrase-less key, from the Client, run `ssh-copy-id user@c2.example.com`. This will copy the key from the Client to the C2 server for the “user” account. You can verify that it works by running “ssh user@c2.example.com” and getting logged in automatically.
  3. On the Server, edit `/home/user/.ssh/authorized_keys` and prepend the syntax `command=”echo”` to the line with the newly added key. It must appear before anything else. It will look like this when done: command=”echo” ssh-rsa AAAAB3NzaC1y…..
    The reason we are doing this is to ensure that the TTY which spawns with SSH will only run the “echo” command. Since we’re installing a device at a location we don’t control, there’s nothing stopping a threat actor inside the customer network from logging into the local account (Single-User Mode password reset) and running our script. This would then give them direct access to our C2 server. By setting the “command” we ensure that they can only type one thing in which will just be echoed back to them and the SSH tunnel will close.
  4. Put the previously mentioned script on the CPE device and store it somewhere notable like “/opt/ssh_tunnel.sh”. Make it executable with “chmod ugo+x /opt/ssh_tunnel.sh
  5. Create a cron job to run this program every ten minutes or however often you want it to run. */10 * * * * /opt/ssh_tunnel.sh
  6. On the server, create a subdirectory in the HTML root for the name of the client you are deploying this CPE device to. mkdir /var/www/html/c2.example.com/new_client/
  7. Create your “check.html” file (or whatever name of file you are using) and put the word “stop” in there. echo stop > /var/www/html/c2.example.com/new_client/check.html
  8. [Optional] Edit “/etc/services” on the Server to reflect the “$RPORT” set in the script to have the service name “New_Client”. Now, if you run “netstat -ap | grep LIST | grep New_Client”, you will know that your SSH tunnel has been created. You can use this type of logic to create e-mail notifications or update “pam.d” to notify on TTY spawns.
  9. So long as the Client can reach the Server over HTTPS and SSH, you’re all set to go.
  10. When you want the SSH tunnel to come up, edit your “check.html” file to have the word “start” instead and it will come up. When you’re done, change it back to “stop”.

So what exactly is happening? Let’s break this down further:

Flow for SSH Reverse Tunnel Connection

This is the SSH Reverse Tunnel flow and is started with the script line: $SSH -oStrictHostKeyChecking=no -i $KEY -T -N -R $RPORT:127.0.0.1:$LPORT $USER@$RHOST

If we expand this into a more human-readable output, we get: ssh -oStrictHostKeyChecking=no -i /root/.ssh/new_rsa -T -N -R 2000:127.0.0.1:22 user@c2.example.com

The main part to understand is the “-R 2000:127.0.0.1:22” which is the actual Reverse Tunnel. It tells SSH that we are going to create a LISTEN socket on TCP/2000 on the Server. From there, anything that goes into that port/socket will be sent through the SSH tunnel and sent to the destination of 127.0.0.1 on TCP/22. With that understood, reference the diagram above.

  1. Our local machine runs “ssh cpe_user@c2.example.com -p 2000
  2. The C2 Server then forwards the traffic through the SSH tunnel to be processed by the CPE device
  3. The CPE device receives a SYN packet for 127.0.0.1 on TCP/22 and forwards it to that destination
  4. The destination is itself (127.0.0.1) and it has TCP/22 open so it sends a SYN/ACK back along the reverse path, in this case, the SSH tunnel
  5. After the three-way handshake, cipher exchange and authentication is done (much like any other SSH session), our local machine is now SSH’d into the CPE device.

The last parts are about the if/else statements in the script. Those are there to ensure that the CPE doesn’t try to open more than one SSH connection at a time if the C2 command is “start” and also, if the C2 command is “stop” to only run the “kill” command if there is actually an SSH connection running already. Lastly, the “Got here” clause is if the C2 command is something other than “start” or “stop” so hopefully we never reach this point.

There you have it. A basic C2 infrastructure you can set up in a small amount of time. There are a lot of other security items you can wrap around this such as “Client Authentication per Web Directory” where the CPE must have a matching certificate to even read the C2 web directory but that and other security restrictions are outside the scope of this document. Regardless, make sure your firewall rules have specific sources to ensure the only people who can access your C2 are those where you’re deploying your CPE device.

--

--

Gr@ve_Rose

CSO, Security Engineer, RedTeamer, PenTester, Creator of https://tcpdump101.com, Packet Monkey