Introducing Slither, my custom C2 framework

It’s been a while since I have posted a blog! Life has been quite busy, I spent the summer working at the Cyber Dynamics Laboratory as a Research Assistant, focusing on satellite network attacks and simulating them appropriately. Now, I am currently on the job hunt!
I have spent most of my days working on projects and learning as much as I can to level up my skills. The only issue is, I haven’t been posting about it.
That stops now!
Here is an introductory blog post to a project that I have been working on. This project is in no way complete, but when is a project ever really done? Regardless, I want to lay out this introductory post for two reasons:
- To show my thought process through the projects development
- So that future blog posts I do are not a million words in length
Lets get into it.
Inspiration
In February 2013, security firm Mandiant published a report on APT1, connecting the group to a unit of the Chinese Communist Party’s People’s Liberation Army Unit 61398. This became by far the most popular APT report to date, and for good reason. In the article, Mandiant provide a detailed overview of APT1’s TTPs, specific malware and C2 infrastructure, trace APT1s operating headquarters, and they even put faces to their most prolific operatives. If you haven’t read it already, I highly suggest you do so here. As far as threat intelligence reports go, it is very accessible.
What interested me most in the report, was the descriptions of APT1’s C2 infrastructure, particularly their “beachhead” backdoor family: WEBC2. WEBC2 is a basic backdoor that retrieves a webpage from a C2 server. It then finds commands either within HTML comments, or other HTML tags. These backdoors were labelled “beachhead” backdoors, as they were the first attacker entry point after a spear phishing victim had downloaded the file. WebC2 utilized rudimentary commands to perform reconnaissance on a victim machine and staged for another, more capable backdoor to be introduced.
Now, this report is 12 years old, and a tactic such as this would likely be ineffective today. Regardless, I still wanted to test and see how something similar would look on my network, and I thought that this would be a great introduction into C2 infrastructure as well.
Throughout this article I will be using some C2 terminology, so it best I establish it here. I am using using definitions outlined in this wonderful article
- C2 Framework – A specialized implementation of the client-server model used for managing compromised systems remotely. It provides a comprehensive platform for post-exploitation activities, maintaining persistence, and coordinating attacks.
- C2 Server – The central command center or logical node of the entire C2 operation. It functions as the hub where attackers orchestrate operations, issue commands, manage connections to compromised systems, and store logs.
- C2 Agent – The preferred industry term for the autonomous program that runs on the compromised/victim system and operates on behalf of the attacker. It sends requests to the C2 server and executes received commands. Also known by framework-specific names like Beacons, Demons, Grunts, or Implants.
- C2 Client – The user interface or frontend application that operators use to interact with the C2 server. It sends requests to the server (such as commands to execute) and receives responses, functioning as a client in the client-server relationship between the operator and the C2 server.
So in the case of WebC2, the C2 agent was the backdoor on the users system, the C2 Server was the server that was serving the HTML pages for the agent to download commands.
Adversary emulation tools
I haven’t published these blog updates, but a few months ago, I configured my Proxmox homelab. I will probably write a blog post about it soon, there is just a few more things I would like to add and integrate in. Here is a quick image of its current state.
You’ll notice that in terms of Adversary emulation, I have MITRE’s Caldera, which has multiple different C2 agents that can be deployed. Ideally this would be outside of the Proxmox server to really do some adversary emulation. but I just quickly wanted to see what it could do. Their “best” agent, called Sandact is written in Go. However, I didn’t want to read through Go syntax, so I decided to focus on their older, and more depreciated python “ragdoll” agent. Out of all the agents in Caldera, ragdoll mimics APT1’s WebC2 the best, look at this code for the agent getting commands:
def _next_instructions(self, beacon):
soup = BeautifulSoup(beacon.content, 'html.parser')
instructions = soup.find(id='instructions')
return json.loads(self._decode_bytes(instructions.contents[0]))
Â
Â
Ragdoll utilizes an HTML parser, in this case the BeautifulSoup library, obtaining instructions from HTML object assigned the id of ‘instructions’. Making this the world’s least obfuscated c2 communication! Jokes aside, this is likely not too far from what the original WebC2 did. However, I wanted to extend this further. So not wanting to deal with the headache of an already existing repository, I decided to quickly whip up my own tool.
I then proceeded to fall down the rabbit hole.
My Own C2 Framework
Looking back at my development notes, hilariously my biggest gripe with Caldera’s ragdoll was that it requested the HTML page directly to the IP address of the C2 server, rather than a domain.
The solution to this is incredibly simple, you would buy a domain and then point its DNS A Record to the C2 server that hosts the webpage.
I didn’t want to do this for two reasons:
- I didn’t want to handle exposing something in my home network publicly.
- I didn’t want to spend the money on a domainÂ
Good thing there is an even simpler workaround though. For all of the machines on my homelab, the default DNS resolver is my pfSense firewall. Specifically PfSense’s resolver is run using unbound, an open source dns resolver. For a great resource on how unbound functions at a basic level, I suggest watching this video So I inserted the following
What I really wanted foremost though was to make my C2 architecture resemble more what a server hosting a website would actually look like! So in the event that we wanted to snoop around, we wouldn’t find much.
Getting to the point of Caldera’s ragdoll agent didn’t take me long at all. With a barebones Apache web server, I was able to embed commands in its index.html page and have an agent download them and send results back. I had even implemented some slight obfuscation techniques for the agent.
But this didn’t really satisfy me. It felt a little too easy, so I decided I wanted to make my web server that hosted this web page resemble realistic web server infrastructure. You’ll notice that my focus on making things “realistic” results in me continually finding new things to fix or expand.
Web Servers to the Rescue
So, what do I mean when I say “realistic web server infrastructure”? Essentially I mean that a reverse proxy sits in front of our backend, that serves the proper pages based on incoming requests. My development to this point has been in python, which is a great language for web applications. Traditionally the infrastructure for a web application built in python is as follows:
Reverse Proxy (nginx, caddy, etc.) -> WSGI Server (uWSGI, gUnicorn, etc.) -> Web application (Django, Flask, etc.)
If you are not familiar with python web development, you probably are not aware of WSGI. Think of WSGI like the translator between the Python web application and the web server that’s actually serving it to the world. For more in depth information on this setup and WHY it historically has developed, I suggest you watch this wonderful talk by Ryan Wilson-Perkin.
My infrastructure is as follows
Nginx -> Gunicorn -> Python Flask Applications <-> Redis Database
At this point, I also decided I wanted to be able to host multiple domains and webpages for agents to get their commands. So I had to think of how to make this modular and extensible. Essentially I would want the ability for flask applications to be spun up for a specific domain, and then automatically configure with WSGI and nginx.
Now there are solutions that exist to solve this issue, such as uWSGI Emperor and Supervisor. Though I decided to build my own custom scripts to handle this. However, managing processes smoothly is a massive pain, and despite hours of trouble shooting, I still have the occasional process that does not kill properly. As a result, at some point I will likely modify my infrastructure, this holds for now though.
So I had reached the point where I had a more realistic web infrastructure. What next?
TUI and Storage
So at this point, I had a C2 Agent which could execute basic commands served through HTML, I had a C2 server. But what about a C2 client, or an interface for the operator to control the agents?
I won’t bore you with the details here, but I wanted an interface that allowed the ability to function as a shell, but also made the integration of commands really simple. Typerwith the extra module typer-shell, is great for this. It uses type hints and doc-strings to construct the documentation for your CLI. Letting you focus on developing rather than dealing with figuring out where you should add help for the user.
Another fundamental part of designing this infrastructure was how results of commands would be handled and stored. For this we needed to have some sort of data storage system, I went ahead and chose Redis..
What initially drew me to Redis was its channels functionality, which operates on a publish/subscribe approach. My Flask Applications could publish to a redis channel, and the TUI could then subscribe to a channel for a given domain that was being hosted. Thereby being able to see the results of commands that the agent sent back. Eventually however, I shifted away from channels. As channels in redis immediately remove a message once subscribers have seen it.
Thankfully Redis has a lot of different communication and data structures, I turned to streams. Streams can be accessed by simple key, and act as an entire log. So if I wanted to add functionality in the future where an entire domains log is downloaded or stored elsewhere, that was possible!
Something important to note about the current state of my C2 infrastructure is that the C2 server and the C2 client are running on the same machine. This is not good, as if the C2 server gets compromised, we would not want our C2 client to be compromised as well. For now however, I am also waiting to separate these functions, and do additional hardening. As for now I had bigger concerns on my hands, as at this point that I began to explore other C2 frameworks, and I realized that I wanted to shift away from sending commands through HTML.
Other C2 Frameworks
At this point, I had scaled the application to where I had all three core components of my C2 infrastructure established. I started looking for inspiration on what else I wanted to implement in my custom C2, and what I came across was Sliver.
Developed by the team at Bishop Fox, Sliver is a C2 framework that has cross-platform implant support, multiple communication protocols including Mutual-TLS and HTTP(S), built-in evasion techniques, and much much more. I came away with a ton of different ideas that I thought would be really fun and valuable to be able to learn and implement. I looked around at other C2 frameworks and I compiled a list of what I wanted to implement:
- Modifying communication to use direct HTTP messages rather than HTML embedded commands.
- Enable long polling to allow for more real-time connections between the agent and the server
- Be able to modify the agent’s behavior (timers, domain it is pointed towards, etc) using commands
- Allow an agent to send commands to multiple domains
- Likewise the ability to manage agents individually as they point to a domain
- Use a nonce to allow the server to identify if an incoming request is from an agent, otherwise a webpage should be stood up that looks realistic
- Encrypted communications between the agent and the server.
- Implementing HTTPS in communications between the agent and the server.
- Randomization of URL parameters for each message to the server. For example c2endpoint[.]com/images/cutedog.jpg and c2endpoint[.]com/library/images/catpic.jpg both would reach the same endpoint on the server
- Utilizing a smaller data structure format (such as a protbuf) rather than a JSON
- Make it such that the agent is able to handle proxies if configured in the network
- Separate the C2 client and C2 server infrastructure
- Switch the generation of flask applications and process management to a different library rather than custom scripts.
These features are in some rough order, and there are likely some that I am missing as well. Regardless I am going to do my best to implement all of them! A good amount of these are inspired from Sliver, so as a homage, my own C2 framework is called Slither (cause snakes Slither, and this is written in python)
Test, test, test, test!
Before taking the leap forward though, I had to take a step back. As I was really uneasy about handling the growing complexity of this codebase. I had to start to implement some rigorous testing.
To be frank, throughout my undergraduate degree we very rarely wrote tests. Even when it came to my senior capstone (which was a large codebase). I simply manually tested everything I could through the GUI or quick scripts, and I knew the codebase inside and out to fix any bugs that came up.
Looking back on this now, that was a crime. Testing is a godsend which forces developers to write better code that can easily to confirm our application works. I spent a few days refactoring my code to be more testable (shoutout to dependency injection) I will likely write a more in-depth article about testing in the future.
After spending a few days writing a bunch of tests and refactoring my code to be good testable code, I started working away at that feature list!
Next Steps
So where am I at right now in my massive checklist of ideal features?
Well at the time of writing this blog I had cleared, the first, second, and eighth feature on that list.
My next big feature I was working on was implementing encryption as well as a nonce to indication different encodings on the messages. I figured that an easy way to implement encryption could be through some pre-shared keys that are generated on the creation of the agent as an executable. Up until this point, my “agent” was just a python script, but in reality this needed to become an executable to really have pre-shared keys make sense.
However its at this moment where I learned a hard lesson. The reason comes down to Python’s fundamental design as a language. Python is an interpreted language, meaning that it is read line by line and executed right away. Other compiled programming languages have to be translated into machine code, and then are executed. An interpreted language comes with its required baggage, mainly that you NEED the interpreter in order to be able to run the language. Not only that, but you’d also need the Python standard library, any other modules that my agent uses, and the specific versions of these as well on the other machine. So, you can see how it would be a pain to be able to make executable agents for different operating systems we want to test.
Now does this mean it is not possible to cross compile python? Certainly not! For instance tools such as Nuitka allow for python to be compiled. However this compilation would have to occur on the environment that you are targeting. For instance, if I wanted my agent to be embedded in a windows system. I would have to use Nuitka on a windows machine for the python executable to then work on windows.
In production environments, this is usually handled in the CI/CD pipeline. Where different OS runners will handle the compilation. If you are interested, look at this GitHub action for Nuitka. For me however, implementing such a pipeline for generating agents is just not worth the hassle and headache.
Instead it would be easier to write the agent in a compiled language. This either means something such as C, C++, or Go. I am leaning towards go, as it seems to be the “easiest” language for the job, and the mascot for Go is an incredibly cute gopher. In these languages, it is so much easier to cross compile, for instance in go you simply call go build
. And you have your executable!
So, my next big step is doing some rewriting of my agent. On the bright side at least all my other infrastructure is good (for now)! I will post more regular blog updates about how the project is progressing! Until then you are welcome to check my project on my Github. Until then!