DISCLAIMER: this blog post covers functionality of Cobalt Strike that is not officially supported, nor fully tested or confirmed to ever appear with the current specs as official functionality. If you want to experiment with this yourself, @armitagehacker has provided a link that will be used for future documentation.
It is however a lot of fun. It provides flexibility and allowed us to leverage the power of CS in a far from default network setup at a high secure environment. We truly hope this functionality becomes available in future releases of Cobalt Strike.
We encountered the need for alternatives to the standard DNS, HTTP or HTTPS channels for C2 traffic We feel Mallable format allows for great C2, especially when combined with Haproxy which sends all non C2 traffic to a real site that relates to the Mallable profile used. Still we would like to be able to build our own channels and have Cobalt Strike talk over that. We even considered building a proxy that could decode a special Mallable profile, push the data trough our own channel, reparse it in the Mallable profile on our redirectors and forward it to the Cobalt Strike teamserver.
After asking @armitagehacker for alternative ways of communication and/or insight in the Mallable profile engine, he mentioned there was some experimental code that we could play with. This interface called “External_C2” allows some process to talk to an alternative port on the Team Server giving it a binary beacon package. Below is a high level overview of the data flows.
Our Third-party Controller is called C2_server, and the Client is called C2_client. C2_client would ask for a first stage, load that shellcode and connect to the named pipe of the beacon. C2_client then reads from this named pipe and simply sends this package to the C2_server which on his turn pops it into Team Server and received a response to be sent back to C2_client and thereafter to the beacon. The way C2_client and C2_server communicate is entirely up to the developer: consider the beacon data a ‘black box’ and make sure you handle it as binary data (not a 0x00 terminated string).
In Cobalt Strike this ‘unannounced code’ can be used by running this script:
Now an external_C2 listener is listening on port 2222 of the teamserver.
PoC for beacon via file share
@armitagehacker provided us with some sample C code which had client and server in 1 process. After slicing this code into a client and server part we managed to get a working beacon talk through our own connections. And actually, for the sake of debugging I decided to have the C2_client place its data in a file and have the C2_server code check that file for its size every X seconds. If the size was over 2 bytes a packet was ready to be picked up and handed to Team Server. The response would be put in another file which in return was picked up by the C2_client. This means that for this to work the client and server have to be able to write and read on the same directory. I’ve been testing on Samba shares and on a Owncloud synchronized folder. Both worked fine, although the latter being very slow.
In order to speed up development I rewrote as much as possible in Python. This also allows easy access to numerous communication channels that we can develop more easily in Python than in C. Up to now I’ve not been able to get the C2_client fully in python as getting access to the named pipe is not working. But just putting those functions in a DLL which is called from python solved this problem.
You can find the code on the outflank github.
A full python version is more than welcome; I simply had no more time on my hands debugging the named pipes issued I had using Win32 python. Also, this is the first time I’ve written python code using a C DLL, so don’t judge me as a developer as I’m not a developer.
Note that I’d be more than happy to see someone develop a pure python implementation of this!
PoC in action
Some months after we created the PoC we had a gig at client where we had to think creatively. Now, you have to understand that the architectural setup of this client’s network is extremely non-default, which comes from their high-secure data handling and low risk appetite. Without going into too much details, there are multiple ‘Super secure’ zones that our attack targets. These are strictly separated from the regular organizational IT, but also from the IT management zone. The figure below gives you an idea of the setup.
For this specific scenario, the client agreed upon an ‘assume breach’ approach where we were given network access to the IT management zone. A few network sniffs and SMB-relay attacks later we had control over some accounts in the IT management zone, and we could login to the Bastion host.
The next step would be to start a beacon from the Bastion host connecting back to our Team Server in the IT management zone. But the interesting thing is that the system is really is as Bastion as you can get. So, no inbound and outbound SMB, WinRM, WMI or whatever other management protocol you can think of. Also, ARP spoofing, ICMP redirects, LLMNR and other multicast/broadcast traffic was blocked at the network layer. The only outbound traffic that was allowed from the Bastion host was to the Super Secure Zones, inbound web-RDP traffic and SMB traffic to the central file share.
It goes beyond this article to zoom in on the specifics of the share. But as it serves a significant business requirement you can imagine it’s not your regular share and there are many safeguards in place. Nevertheless, it did what it is supposed to do: make files accessible. It seemed we could see our little PoC perform in a real-life setup.
We put the required files on the file share and ran the following commands:
- Attacker’s machine: started Team Server and loaded the external.cna file
- Attacker’s machine: C2_server.py (either run this from the location where the files will live or alter the source code)
- Bastion host: c2file.exe \\sharesystem\share\subdir\TEMPFILENAME
And boom: new beacon!
From there on it behaved as any other beacon with the exception of:
- No mega throughput.
- Incorrectly reported the ‘last time seen’ as the small packages are discarded. Small as in the 4 bytes hello calls. This is actually 4 bytes of length, 0 bytes of content.
- No ‘external’ IP.
But regarding the basic commands and functionality, it’s all normal.
In the screenshot below you can see the Cobalt Strike interface, with a beacon connected from the Bastion host. The tab that is open shows the screenshots we just made, which proves that we can use the built-in Cobalt Strike commands. The screenshot itself shows the desktop of the host with a cmd.exe running c2file.exe, and an Explorer window with the contents of the share. Notice the bea.be* files on the share, its where the data exchange is happening.
This works on the “inside” of the file share to a Team Server in the IT management zone, but also ‘through’ the file share with a Team Server on the lesser secured ‘Regular org IT zone’. Enough proof for the attack. But from there you could do your port redirection or other network trickery to make it connect to a Team Server on the internet.
How about jumping air gaps?
While the above situation was very specific, we can think of some other ways for using this same PoC in other environments. For example, in air-gapped setups where you can use a USB thumb as a data transport. Have your Team Server look at the path of the thumb drive. Dismount and mount in your target. There, start the c2file pointing to the path of the thumb drive, and voila. We verified this in our lab, and it works. But as you expect the throughput is limited by your own speed of moving the thumb drive back and forth. But technically it’s a C2 jumping air gaps. 🙂
This external C2 functionality of Cobalt Strike however provides way more options with just little coding effort. Some idea’s:
- C2 over chat protocols
- C2 over network covert channels
- C2 trough database fields
- C2 trough synced locations (owncloud / RSYNC etc.)
- C2 on a laptop moving in and out a secured location daily (might be laggy … )