By Simon Buchheit
CatTails at its core is a raw socket library that allows a user to create raw socket applications in Golang without dependencies like libpcap. However, the current project (https://github.com/oneNutW0nder/CatTails) implements a command and control server (C2) along with an implant that can be used during Red vs. Blue competitions. When I started this project, I set out with the goal of learning Golang. This quickly evolved into searching for a way to achieve raw sockets without using libpcap which everyone seems to use. Unfortunately, using libpcap in a competition environment introduces an unwanted dependency. If a blue team removes libpcap my tool would stop working. This would make CatTails too fragile and not resilient enough. In addition to resiliency, libpcap would need to be installed on every machine in order for deployment to go smoothly. As you can see, this creates a lot of overhead that could be avoided if we don’t use libpcap. How might one create raw sockets without this awesome library in a language that everyone recommends using libpcap you might ask? Prepare to find out.
First, we need to define what raw sockets are and how they work. A raw socket is a network socket that allows the application to send and receive packets without and formatting or processing of the headers. Basically, instead of the kernel processing your packet, you as the programming have to handle the processing.
Fig 1. Packet processing flow
In this figure we can see the path a packet takes as it is processed by the network stack. On the left, you can see the path a packet takes when you use a normal socket. On the right, we have our raw socket. Notice how the raw socket skips layer three and four processing. This is why we can use raw sockets to bypass firewalls on the host. These fancy sockets allow us to bypass the processing by iptables or other firewalls. Pretty cool right! Now that you know what a raw socket is and the basics of how they work, lets look at how we can implement them in Golang.
Raw Sockets in CatTails:
First, we will need to create our raw socket. The code may look fairly straightforward, but finding the right combination of parameters took some time.
Fig 2. The “NewSocket()” function from “cattails.go”
Here we specify the socket family, socket type, and the protocol for the socket. The socket family is AF_PACKET which means this is going to be a packet socket. The socket type is SOCK_RAW which means we are going to have a raw socket. Finally, the protocol is ETH_P_ALL which means we want to be able to send and receive all protocols. That’s it! We now have a raw socket in Golang. But wait, we can’t just start sending and receiving data with this socket. Remember how packets are not processed and instead sent to the application? That’s right, our application has to handle all of the processing for both sending and receiving before we can actually use our new socket. This is one of the things that makes raw sockets complicated because the developer has to handle things that the kernel normally does without us knowing.
Anyway, let’s take a look at how we can build a packet for our socket.
Fig 3. Creating a packet with “CreatePacket()” in “cattails.go”
It looks like there is a lot going on in this code, but let’s break it down. We can use Google’s gopacket package (https://github.com/google/gopacket) to help us build packets correctly. Looking at the code, you can see that we need to pass a lot of information to the function. This information contains things like the source and destination IP and everything else we need to build a proper packet. Stepping through the code, we first create a buffer that will contain our packet once we are finished. Next, we define all of the structures for each layer of the packet that we will be using. For CatTails, we are going to use the ethernet (layer 2), IP (layer 3), and UDP (layer 4) structures. After filling out all of the structures we need to calculate the checksum so that routers and other networking devices do not drop our packets during transit. Then we serialize all of our layers together with the help of gopacket and we are done! Now that we have something to send we need a way to send it!
Fig 4. Sending a packet with “SendPacket()” in “cattails.go”
Thankfully, sending a packet is relatively straight forward. We bind our socket to an interface then we write our packet data to that interface. That’s it! Awesome, we are now able to make a socket, make a packet, and send the packet, but how are we going to receive it?
When I began trying to receive a packet with a raw socket I realized that no one has done with without libpcap. This is where everyone on the internet will tell you to use libpcap because it makes reading and filtering so much easier. These people are correct, however, you now have a dependency that you need to install and make sure a blue team member does not remove the library. This was not going to fly for me. Instead, I used a Berkeley Packet Filter (BPF). A BPF creates a “virtual machine” (however, not the virtual machines we are used to) and runs a BPF program which operates on a packet. The way BPF works is beyond the scope of this blog post, but it is very interesting if you have some time to check it out. Essentially, we can define a filter program, load it, and run received packets through the filter to see if they are ours. Let’s see how that works.
Fig 4. Receiving a packet with “ServerReadPacket()” in “cattails.go”
First, we make a buffer to hold an incoming packet, then we read a packet into the buffer from our socket. The next line of code (line 82 above) runs the BPF program on the packet we just received. Then we do some checking to see if the packet should be ignored or not. Finally, if the packet makes it through the filter stage, we can process it. You may be asking, “How do we write these filter programs”. Well, the filter programs are written using assembly like instructions which makes it pretty difficult to just pick up. Luckily, we can use tcpdump to generate a filter for us!
Fig 5. BPF Filter generated from tcpdump
Figure 5 is the filter program that will be loaded into our BPF virtual machine. Changing your filter is as easy as generating a new filter with tcpdump, then replacing the old filter with a new one. That’s what I call easy mode.
Now we can make raw sockets, make packets, send packets, and receive them! Congrats! You have now made a raw socket program capable of bypassing host firewall rules. What’s this good for you might ask? Let’s talk about how you can use CatTails.
Red vs. Blue Competitions:
I created CatTails to help others create a raw socket application without libpcap, and so I can create a tool for red-teaming competitions that leverages raw sockets. In a competition, one of the most important things a blue team can do is write firewall rules at both the host and network level. CatTails gives the user the ability to bypass any of the host based firewall rules that a team might put in place. Bypassing firewalls gives the blue teams a false sense of security and allows CatTails to persist even if a box has been firewalled off completely from the network.
Currently, I have a C2 and an implant that I use for Red vs. Blue team competitions. The implants are installed on the target machines, and they send callbacks to the C2 on a predetermined interval (roughly a minute). When the C2 gets a callback from a bot, it will reply with a command to run, if a command has been staged.
Fig 6. The CLI of the CatTails C2 staging and sending a command to the implants
Figure six shows the CLI for the C2. You can stage a command by typing in what you want to be executed on the infected machines, then any implant that calls back to the C2 will receive that command and execute it with root privileges. If you are interested in learning more about how to configure, deploy, and use CatTails, I suggest visiting the project on my Github https://github.com/oneNutW0nder/CatTails.
After completing this project, I deployed it in two Red vs. Blue team competitions. The first was a fairly large competitions that consisted of 10 teams each with 7 Linux based operating systems in their infrastructure. I deployed the CatTails implant on all 7 machines for all 10 teams, and I also deployed on all 7 boxes for 3 testing teams. This amounts to CatTails being deployed on roughly 91 machines (I say roughly because some implants may have died or not installed correctly). During deployment, everything was going smoothly. All implants were calling back to a single CatTails C2 with very little issue. However, when the competition began, I noticed that all of my callbacks would stop for about an hour, then they would all come back. This behavior was very strange and can be seen in data that I collected during the competition.
Fig 7. Number of callbacks starting at deployment and going through the whole competition
Looking at the graph, the bars starting around 21:00 is when I finished deploying to all of the teams the night before the competition. I did not have the C2 running over night which explains the massive gap starting just after 03:00. When the bars start up again is when the competition began. You can see that everything started off strong, but the callbacks began to stop and start. Let’s zoom in and get a clearer picture of what was happening.
Fig 8. Dropping callbacks
We can see in this graph that the number of callbacks was very close to the number of total hosts I deployed on (91 total). The missing 10 probably comes from a test team being reset so the CatTails implants were no longer on those machines. Regardless, this graph shows that for a while, all my implants are functioning correctly, then they all seem to die for about 20 minutes. Then they all seem to come back to life all at once. Weird right!?
Fig 9. Graph of callbacks for the morning
Here is a graph showing the callbacks for CatTails all morning. The first 20 minute gap that we just looked at, then all of a sudden, everything seems to die for an hour before coming back all at once. Here is an even better representation of these seemingly dead areas.
Fig 10. Access over time graph for CatTails
This graph shows the number of callbacks that CatTails received over the length of the competition. This graph leads me to believe that I my callbacks were getting lost in the void of networking somewhere. I believe that something was happening with the infrastructure and networking that the competition was hosted on, which was causing me to lose so many callbacks. Regardless of my “packet loss”, CatTails performed very well in its first test run. When I was receiving callbacks from my implants, I was able to execute root commands on the infected machines that made the competition challenging for the blue teams. I was also able to tweak some things with the C2 that made performance with Goroutines slightly better.
In the second competition, things went much better than the first. This time, there were 4 teams, each with 4 Linux machines. This leaves me with 16 CatTails implants, much less than the first competition. Unlike the first time, all of my implants stayed alive during the duration of the competition.
Fig 11. Access over time for CatTails implants in competition 2
As you can see, the number of callbacks is much more consistent during the competition until the end where we begin to hit services and machines very hard. The difference between this competition and the first is the infrastructure and the number of implants. The infrastructure could have been setup better than the first one, but this is unlikely as they were both run in ESXi. The number of implants, however, drastically changed. I believe this has something to do with the C2 not being able to handle that many implants calling back to it which would lead to the large gaps in callbacks shown in figure 10. This performance issue will require more testing and research to determine the cause of the inconsistency.
Overall, CatTails proved to be an effective tool during this competition making up about 23% of the total callbacks from all tools that red team had deployed (shown below).
Fig 12. Percent contribution to total callbacks
In both competitions, CatTails has proved to be an effective tool in maintaining access and running root commands across a competition environment regardless of lost callbacks. With more tweaking and features it will become a very powerful and easy to use tool that will benefit any red team wishing to own a Linux box.
When I started this project, I did not expect it to take as long as it did. Just figuring out how raw sockets worked in Golang was a project in itself. Once I figured that out, I had to figure out how to get filtering to work without using libpcap which I could not find any resources online for. Seriously, I could not find anyone that had done filtering without libpcap in Golang. This spurred me on to complete this project and write about it so others do not have to struggle as I did. I learned a lot from this project and its challenges and I hope that this blog helps you, the reader, complete your next big project.