Workshop

C2 Server

Beginner  ·  Objective: Understand how command and control servers work.

Objective

Understand how command and control servers work.

C2 Workshop, Part 1: Building a Command & Control Server from Scratch

Workshop series: This is Part 1 of a multi-part hands-on series where we build a real Command & Control framework — the same architecture used by attackers — to understand exactly how it works from the inside.


What Is C2, and Why Should You Care?

In 2010, a piece of malware called Stuxnet was discovered inside industrial control systems at Iranian nuclear facilities. It was unlike anything analysts had seen: a sophisticated worm that would lie dormant on most machines, but silently activate and sabotage uranium enrichment centrifuges on very specific hardware configurations. To do this, Stuxnet used a Command & Control architecture — agents running quietly on compromised machines, receiving instructions from servers the attackers controlled, executing them, and reporting back.

C2 stands for Command and Control. The basic idea is elegant and unsettling in equal measure:

  1. An attacker plants a small program — the agent — on a victim's machine.
  2. The agent periodically calls home to a server the attacker controls — the C2 server.
  3. The server sends back commands. The agent executes them. Results come back.
  4. Repeat, indefinitely, until someone notices. (Many don't, for a very long time.)

The agent doesn't open any ports. It doesn't listen for incoming connections. It just looks like ordinary outbound web traffic — the kind your browser generates hundreds of times a day. That's what makes it so effective, and so hard to catch without the right tools.

In this workshop, we're going to build one. Not to cause harm — we're the good guys here — but because understanding how an attack works is the most direct path to understanding how to defend against it. The commands we run will be harmless. The architecture is real.


What We're Building

A minimal C2 system has two pieces:

In Part 1, we're using a mock server: a stripped-down version that serves a scripted sequence of commands from a list we define in advance. Think of it as training wheels — enough to see the mechanics clearly, simple enough that nothing surprising happens.

The only command we'll run in this part: printing a string to the terminal. That's it. But the communication pattern is identical to what real malware uses. The difference between a C2 framework and a Remote Access Trojan is intent and consent — the code is exactly the same.


Prerequisites

Python 3 and the requests and flask libraries.

Check if Python is installed:

python3 --version

If you see something like Python 3.11.4, you're set.

If Python isn't installed:

On macOS (via Homebrew):

# Install Homebrew first if you don't have it
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

brew install python

On Ubuntu/Debian Linux:

sudo apt update && sudo apt install python3 python3-pip

Install the required libraries:

pip3 install flask requests

Platform note: This workshop is written for macOS and Linux. The concepts and most of the code translate to Windows — you'd swap python3 for python and pip3 for pip in commands.


Step 1: Run the Mock Server

In a terminal, navigate to the directory containing the workshop files and run:

python3 v1_mock_server.py

You should see:

Mock server running on http://localhost:5001
Script has 3 entries

What this program does:

v1_mock_server.py is a small Flask web server that simulates a C2 server. It exposes a single endpoint — POST /checkin — that agents call to announce themselves and collect their next command.

The server works through a script: a list of responses defined at the top of the file, consumed one at a time, top to bottom. Each agent check-in gets the next entry in the list. Once the script runs out, every subsequent check-in gets a null response — no command.

The script right now looks like this:

SCRIPT = [
    {"command": None},
    {"command_id": "001", "type": "print", "params": {"text": "Hello from the C2 server!"}},
    {"command": None},
]

First check-in gets nothing. Second check-in gets a print command. Third check-in and beyond get nothing again.

The server also checks a security token on every request. Any agent that sends the wrong token — or no token at all — gets a 401 Unauthorized and nothing else. The token is a simple shared secret: test-token-insecure. (The name is self-aware. We'll harden this in a later part.)

Leave this terminal running. Open a new one.


Step 2: Run the Agent

In your second terminal window:

python3 v1_agent.py

You should see:

Agent started. Polling http://localhost:5001 every 5s
Received: id=001 type=print
[COMMAND] Hello from the C2 server!

That's the full C2 loop. The agent checked in, received a print command, executed it, then went back to sleep for 5 seconds. On its next check-in, the server had nothing queued and returned null — so the agent waited and checked in again.

Now change the message.

Open v1_mock_server.py in a text editor and find the SCRIPT list. Change the text parameter:

SCRIPT = [
    {"command": None},
    {"command_id": "001", "type": "print", "params": {"text": "You've been compromised. (Just kidding. But this is how it starts.)"}},
    {"command": None},
]

Stop the server with Ctrl+C and restart it:

python3 v1_mock_server.py

The agent is still running in its other terminal. Within 5 seconds, it'll check in again, pick up the new command, and print your new string.

This is a glimpse into why C2 is so powerful. The attacker can change what the agent does without ever touching the victim's machine again. Once the agent is installed, all they have to do is update the server. The agent does the rest.


What Just Happened (Under the Hood)

Let's trace the exact communication so nothing is a black box.

When the agent checks in, it sends an HTTP POST to /checkin:

POST http://localhost:5001/checkin
X-Agent-Token: test-token-insecure
Content-Type: application/json

{"host_id": "student-machine"}

The server checks the token (X-Agent-Token). If it matches, it returns the next entry from the script:

{
  "command_id": "001",
  "type": "print",
  "params": { "text": "Hello from the C2 server!" }
}

The agent reads the type field, looks it up in its dispatch table, and calls the matching function with the params:

COMMANDS = {
    "print": do_print,
}

do_print prints [COMMAND] Hello from the C2 server! to the terminal. Simple. What makes this architecture interesting is that COMMANDS could contain anything — execute a shell command, read a file, encrypt a directory, take a screenshot. The server doesn't care what the agent can do; it just sends a type and params. The agent decides what that means locally.

The agent's main loop, at its core:

while True:
    POST /checkin with auth token
    if a command came back:
        look up command type in dispatch table
        execute it
    sleep 5 seconds
    repeat

No complex state, no persistence, no elaborate handshake — just a token check and a dispatch table. This simplicity is intentional for Part 1. We want the shape of C2 to be unmistakable before we add complexity.


Red Team / Blue Team Sidebar

If you were the attacker:

You'd swap localhost:5001 for a domain you control — something innocuous-looking like telemetry-cdn-assets.com. You'd use HTTPS so the traffic looks encrypted and legitimate. You might set POLL_INTERVAL to 900 seconds (15 minutes) so the agent barely registers in network logs — one outbound connection every quarter hour looks like a software update check. You'd add persistence so the agent restarts on reboot. None of these are difficult changes.

If you were the defender:

The agent in this demo is trivially detectable — plain HTTP to localhost, announcing its activity in a terminal. A real agent would be much quieter. What defenders look for: unusual outbound connections to new or low-reputation domains, beaconing (the same destination, same interval, like clockwork), and processes that survive reboots without an obvious reason.

This tension — attacker makes C2 quieter, defender gets better at spotting the pattern — is one of the fundamental dynamics of the industry. Every detection technique is a response to an evasion technique. Every evasion technique is a response to detection. Round and round.


Where Do Real C2 Servers Live?

Not on AWS. Not on any mainstream provider that responds to abuse complaints.

Criminal C2 infrastructure runs on what the industry calls bulletproof hosting (BPH) — ISPs that knowingly rent servers to criminals, ignore law enforcement takedown requests, and accept only cryptocurrency. Their underground forum ads are blunt about it: "We don't care about your activity. We won't get taken down."

They stay alive through a few tricks: rotating IP addresses at short intervals so blocklists can't keep up (called fast-flux), hiding behind chains of shell companies spread across multiple jurisdictions, and piggybacking on legitimate lower-tier hosting providers that are simply too lax to notice. When a block of IPs eventually gets blacklisted, they migrate to fresh infrastructure and update their DNS. The operation continues.

Some notorious examples: the Russian Business Network, one of the earliest large-scale BPH providers, ran phishing, botnets, and identity theft at industrial scale in the mid-2000s. McColo, a US ISP knowingly hosting botnet C2 for a significant chunk of global spam, was cut off by its upstream providers in 2008 — and global spam volume briefly collapsed overnight. More recently, LolekHosted was taken down in 2023 after hosting infrastructure for the Netwalker ransomware gang, requiring years of multi-country coordination to pull off.

Takedowns happen. But new providers fill the gap quickly. It's one of the reasons cybercrime is so persistent — the attackers don't just have technical skill, they have a shadow economy built specifically to keep them running.


This workshop is for educational purposes. Everything we build here runs on your own machine, talks only to a local server, and executes only harmless demonstration commands. Understanding these techniques is the first step to defending against them.

← All Workshops