Pipeline Stages#
graph LR
Raw[Raw Bytes] --> Magic[Magic Routing]
Magic --> Crypto[Crypto Layer]
Crypto --> Codec[Protocol Codec]
Codec --> Handler[Message Handler]
Handler --> Response[Response]
Response --> Codec2[Codec Encode]
Codec2 --> Crypto2[Crypto Encrypt]
Crypto2 --> Wire[Wire Bytes]
1. Magic Routing#
The first 4 bytes of every message identify the agent package. The pipeline uses these magic bytes to select the correct CryptoProvider and ProtocolCodec.
This allows multiple agent types to coexist on a single listener — each agent package registers unique magic bytes, and the pipeline routes accordingly.
2. Crypto Layer#
The CryptoProvider handles:
- Registration: Accept the agent’s public key, generate a server keypair, derive a shared session key via ECDH
- Session messages: Decrypt inbound data and encrypt outbound responses using AES-256-GCM with the session key
Session tokens (16 bytes) identify the agent’s session. A null token (all zeros) indicates a registration message.
3. Protocol Codec#
The ProtocolCodec translates between wire bytes and internal message dicts:
- Decode: Wire bytes → Python dict
- Encode: Python dict → Wire bytes
The dev agent codec uses JSON + zlib compression.
4. Message Handler#
The handler processes the decoded message based on its type field:
| Type | Action |
|---|---|
register | Create agent record, assign ID and session token |
checkin | Update last-seen timestamp, return queued tasks (and relay responses if applicable) |
task_result | Store result, trigger credential extraction and file transfer processing |
task_assignment | Deliver task payload to agent (server → agent) |
beacon_config | Update beacon interval and jitter |
kill | Terminate the agent |
survey_result | Update agent metadata (hostname, OS, IPs, etc.) |
key_rotation_request | Server requests session key rotation |
key_rotation_response | Agent acknowledges key rotation |
relay_forward | Forward traffic from interior agents via P2P relay; auto-discovers relay topology |
plugin_message | Route plugin-specific messages to registered handlers |
Streaming Results#
Tasks can stream results without completing — the task enters a running state and produces multiple results over several check-in cycles. This is used by long-running managed modules (keyloggers, port scanners) that send incremental output.
Relay Response Queueing#
When agents relay traffic for interior agents, responses are buffered in the pipeline and returned to the relay agent on its next check-in. The relay agent then forwards them to the appropriate interior agent.
Wire Format#
| |
Total header: 20 bytes fixed, followed by the encrypted and encoded payload.
Registration Sequence#
sequenceDiagram
participant Agent
participant Listener
participant Pipeline
participant DB
Agent->>Listener: magic + null_token + public_key + register_msg
Listener->>Pipeline: on_message(raw_bytes)
Pipeline->>Pipeline: Route by magic bytes
Pipeline->>Pipeline: Generate keypair, derive session key
Pipeline->>DB: Create agent record, store session key
Pipeline->>Listener: magic + session_token + server_public_key + response
Listener->>Agent: Response bytes
Check-in Sequence#
sequenceDiagram
participant Agent
participant Pipeline
participant DB
Agent->>Pipeline: magic + session_token + encrypt(checkin)
Pipeline->>Pipeline: Lookup session by token
Pipeline->>Pipeline: Decrypt with session key
Pipeline->>Pipeline: Decode with codec
Pipeline->>DB: Update last_seen, fetch pending tasks
Pipeline->>Agent: encrypt(codec(tasks))
Anti-Replay Protection#
The dev agent includes a monotonic counter in every encrypted message. The server tracks the last seen counter per session and rejects messages with a counter value less than or equal to the previously seen value.