Overview
The C2 framework uses a classic client-server architecture where agents (clients) connect to a central server to receive commands and report results. This design provides centralized control while maintaining operational security through encrypted communications.Components
Server
The server is built with FastAPI and handles all inbound agent communications through a single/beacon endpoint.
- Server Startup
- Beacon Endpoint
server/server_main.py
- Accept and validate encrypted beacons from agents
- Manage agent sessions and track last-seen timestamps
- Queue and dispatch commands to agents
- Store task results and session data in SQLite database
- Perform nonce replay detection to prevent replay attacks
The server maintains three core components in global state:
Databasefor persistent storageSessionManagerfor tracking active agent sessionsCommandQueuefor managing pending and executing tasks
Agent
The agent is a lightweight client that runs on target systems and maintains persistent communication with the server.agent/agent_main.py
- Check in with the server and receive a unique session ID
- Send periodic beacons (TASK_PULL) to request new commands
- Execute received commands and capture stdout/stderr/exit code
- Report task results back to the server
- Implement exponential back-off on connection failures
- Terminate gracefully on TERMINATE signal
The agent uses a
BeaconLoop class that encapsulates all beacon logic, including:- Initial CHECKIN to register with the server
- Periodic TASK_PULL messages with configurable jitter
- Automatic retry logic with exponential back-off
- Task execution and result reporting
Request Flow
The typical interaction flow between agent and server:-
Initial Checkin
- Agent sends
CHECKINmessage with system information (hostname, username, OS) - Server creates a new session, assigns a UUID, and stores it in the database
- Server responds with the assigned
session_id
- Agent sends
-
Beacon Loop
- Agent sleeps for configured interval (with jitter)
- Agent sends
TASK_PULLmessage to check for pending commands - Server checks the command queue for the session
- Server responds with either:
TASK_DISPATCHcontaining a command to executeTASK_PULLresponse withstatus: no_taskTERMINATEsignal to shut down the agent
-
Task Execution
- Agent executes the command using the executor module
- Agent captures stdout, stderr, exit code, and duration
- Agent sends
TASK_RESULTmessage with execution data - Server stores the result and marks the task complete
-
Session Management
- Server updates
last_seentimestamp on every beacon - Operator can deactivate sessions, causing TERMINATE on next beacon
- Sessions persist in the database across server restarts
- Server updates
Message Dispatch
The server routes all beacon messages through a central dispatcher:server/server_main.py
Security Considerations
Defense in Depth:
- All messages are encrypted with AES-256-GCM
- Nonce-based replay protection prevents message reuse
- Session IDs are UUIDs to prevent enumeration
- Payload size limits (256KB) prevent resource exhaustion
- Only the
/beaconendpoint is exposed; all other paths return 404
Scalability
The current architecture is designed for lab environments with moderate scale:- In-memory session state for fast lookups (restored from DB on startup)
- Async I/O with FastAPI and uvicorn for handling concurrent beacons
- SQLite database for persistence (can be replaced with PostgreSQL for production)
- Stateless beacon handling allows horizontal scaling behind a load balancer
For production deployments with thousands of agents, consider:
- Redis for distributed session state
- PostgreSQL for scalable persistent storage
- Message queue (RabbitMQ, Kafka) for command dispatch
- Multiple server instances behind a load balancer