Architecture
High-Level Architecture
JustC2 follows a classic C2 architecture with three tiers:
┌─────────────┐ HTTPS/WSS ┌──────────────┐ HTTP/TCP/SMB/DNS ┌─────────────┐
│ JustClient │ ◄──────────────────► │ JustServer │ ◄────────────────────► │ Agents │
│ (Operator) │ WebSocket + REST │ (Teamserver) │ Listener Protocols │ (Targets) │
└─────────────┘ └──────────────┘ └─────────────┘
│
┌─────┴─────┐
│ Extenders │
│ (.so) │
└───────────┘Component Responsibilities
| Component | Language | Role |
|---|---|---|
| JustServer | Go | Central teamserver — manages agents, listeners, tasks, and operator connections |
| JustClient | C++ (Qt6) | Desktop GUI for operators to interact with the teamserver |
| Extenders | Go (.so) | Plugin modules that implement listener and agent logic |
| axc2 | Go | Shared type definitions used by the server and extenders |
JustServer
The teamserver is the central hub. It exposes a REST API and WebSocket endpoint over TLS for operator connections, and manages listener processes that communicate with deployed agents.
Core Subsystems
- Connector — HTTP router (Gin framework) that handles REST API requests, JWT authentication, and WebSocket upgrades for real-time operator communication
- Server — Core business logic: agent lifecycle, task queuing and dispatch, tunnel management, download tracking
- Database — SQLite-based persistence for agents, tasks, credentials, targets, screenshots, and downloads
- Extender Manager — Loads Go plugin
.sofiles at startup, manages listener and agent type registration - Event Bus — Publishes real-time events (new agents, task completions, downloads) to connected operators via WebSocket
- Profile — Parses
profile.yamlto configure the teamserver, HTTP server behavior, and TLS settings
Authentication Flow
Operator JustServer
│ │
├── POST /login ───────────►│ (username + password)
│ │
│◄── access_token + ────────┤ (JWT tokens)
│ refresh_token │
│ │
├── GET /connect ──────────►│ (OTP-based WebSocket upgrade)
│ │
│◄══ WebSocket ════════════►│ (real-time sync)
│ │
├── GET /agent/list ───────►│ (Bearer access_token)
│◄── agent data ────────────┤
│ │
├── POST /refresh ─────────►│ (refresh_token)
│◄── new access_token ──────┤- The operator logs in with credentials via
POST /loginand receives JWT access and refresh tokens - An OTP (one-time password) is generated to establish a WebSocket connection for real-time updates
- All subsequent API calls use the access token as a Bearer token
- When the access token expires, the refresh token is used to obtain a new one
Agent Communication Flow
Agent Listener (Extender) JustServer
│ │ │
├── beacon/check-in ───────►│ │
│ ├── InternalHandler() ─────────►│
│ │ ├── TsAgentCreate()
│ │◄── agent ID ──────────────────┤
│◄── response ──────────────┤ │
│ │ │
│ ... time passes ... │ │
│ │ │
├── beacon/poll ───────────►│ │
│ ├── GetHostedAll() ────────────►│
│ │◄── packed tasks ──────────────┤
│◄── tasks ─────────────────┤ │
│ │ │
├── beacon/result ─────────►│ │
│ ├── ProcessData() ─────────────►│
│ │ ├── TsTaskUpdate()- An agent checks in with its listener, which calls
InternalHandler()on the teamserver - On first check-in, the server creates the agent record and returns an agent ID
- The agent periodically polls for tasks; the server packs queued tasks via the extender’s
PackTasks()function - When the agent returns results, the extender’s
ProcessData()function decodes and dispatches them
JustClient
The Qt6 desktop client provides a multi-tabbed interface for operators:
- Sessions Table — Lists all active agent sessions with OS, user, hostname, and status
- Sessions Graph — Visual network graph of agent relationships and pivots
- Console — Per-agent interactive console for executing commands
- Listeners — Create, start, stop, and configure listeners
- Downloads — Track file downloads from agents
- Tunnels — Manage SOCKS proxies and port forwarding tunnels
- Screenshots — View captured screenshots
- Credentials — Manage harvested credentials
- Targets — Track discovered hosts on the network
- Chat — Communicate with other operators
- Terminals — Interactive terminal sessions on agents
The client connects to the server over TLS, authenticates via JWT, and maintains a WebSocket connection for real-time event delivery. It uses the Qlementine theme engine for a modern dark UI with JSON-based theme customization.
Extender System
Extenders are Go plugins compiled as shared libraries (.so files). Each extender implements either a listener or an agent type.
Listener Extenders
A listener extender implements the PluginListener interface:
type PluginListener interface {
Create(name, config string, customData []byte) (ExtenderListener, ListenerData, []byte, error)
}The returned ExtenderListener handles the listener lifecycle:
type ExtenderListener interface {
Start() error
Edit(config string) (ListenerData, []byte, error)
Stop() error
GetProfile() ([]byte, error)
InternalHandler(data []byte) (string, error)
}Agent Extenders
An agent extender implements the PluginAgent interface:
type PluginAgent interface {
GenerateProfiles(profile BuildProfile) ([][]byte, error)
BuildPayload(profile BuildProfile, agentProfiles [][]byte) ([]byte, string, error)
CreateAgent(beat []byte) (AgentData, ExtenderAgent, error)
GetExtender() ExtenderAgent
}Configuration
Each extender has a config.yaml that declares its type, binary file, and relationships:
# Listener extender example
extender_type: "listener"
extender_file: "listener_beacon_http.so"
ax_file: "ax_config.axs"
listener_name: "BeaconHTTP"
listener_type: "external"
protocol: "http"# Agent extender example
extender_type: "agent"
extender_file: "agent_beacon.so"
ax_file: "ax_config.axs"
agent_name: "beacon"
agent_watermark: "be4c0149"
listeners:
- "BeaconHTTP"
- "BeaconTCP"
- "BeaconSMB"
- "BeaconDNS"
multi_listeners: falseListener Types
- External — The listener registers HTTP/public endpoints on the teamserver’s own HTTP server. The teamserver handles TLS termination. Examples: BeaconHTTP, BeaconDNS, GopherTCP
- Internal (Bind) — The listener opens its own port or pipe independently of the teamserver. Examples: BeaconTCP, BeaconSMB