Overview
The C2 framework uses modern cryptographic primitives to protect all agent-server communications. Every message is encrypted with AES-256-GCM and authenticated with a 16-byte authentication tag, ensuring confidentiality, integrity, and authenticity.Encryption Algorithm
The framework uses AES-256-GCM (Galois/Counter Mode) for authenticated encryption:- AES-256: Advanced Encryption Standard with 256-bit keys
- GCM: Provides both encryption and authentication in a single operation
- Authentication tag: 16 bytes appended to ciphertext for integrity verification
AES-GCM is the industry standard for authenticated encryption, used in TLS 1.3, IPsec, and many other security protocols. It provides strong security guarantees when used correctly.
Why AES-GCM?
- Authenticated Encryption: Combines confidentiality and integrity in one primitive
- Performance: Hardware acceleration available on modern CPUs (AES-NI)
- No Padding Oracle: GCM mode doesn’t require padding, eliminating padding oracle attacks
- Parallel Processing: CTR mode allows parallel encryption/decryption
Encryption Implementation
Theencrypt() function generates a random 12-byte nonce and returns both ciphertext and nonce:
- Encryption
- Decryption
common/crypto.py
- Each encryption generates a fresh 12-byte nonce using
os.urandom() - The nonce is returned alongside the ciphertext and transmitted in the message
- The ciphertext includes a 16-byte authentication tag automatically appended by AES-GCM
- Decryption verifies the tag and raises
InvalidTagexception if tampered
Nonce Reuse is Catastrophic:
Reusing a nonce with the same key in AES-GCM breaks confidentiality and authenticity. The framework generates a fresh random nonce for every message to prevent this.
Reusing a nonce with the same key in AES-GCM breaks confidentiality and authenticity. The framework generates a fresh random nonce for every message to prevent this.
Key Derivation
The framework uses HKDF-SHA256 (HMAC-based Key Derivation Function) to derive the session key from a pre-shared key (PSK):common/crypto.py
- Algorithm: SHA-256 hash function
- Length: 32 bytes (256 bits) for AES-256
- Salt: Fixed value
b'c2-lab-fixed-salt-v1'(lab environment) - Info: Context string
b'c2-framework-v1'for domain separation
Session Key Derivation
Theget_session_key() function derives the operational key from the configured PSK:
common/crypto.py
Lab Environment Notice:
The current implementation uses a fixed salt for simplicity in lab environments. Production deployments should generate unique salts per deployment or use per-agent keys.
The current implementation uses a fixed salt for simplicity in lab environments. Production deployments should generate unique salts per deployment or use per-agent keys.
Pre-Shared Key (PSK)
Both agent and server must be configured with the same 32-byte pre-shared key. The PSK is stored incommon/config.py and must be kept secret:
PSK Distribution
In the lab environment, the PSK is compiled into both the agent and server binaries. For production deployments, consider:- Per-Agent Keys: Unique PSK for each agent, stored in operator database
- Key Rotation: Periodic key changes to limit exposure window
- Secure Distribution: Out-of-band key delivery (USB, encrypted channels)
- Hardware Security Modules: Store keys in HSM for high-security environments
The framework currently uses a single PSK for all agents to simplify lab exercises. Production C2 frameworks typically use per-agent keys or asymmetric key exchange protocols.
Encryption Flow
Agent to Server
- Agent builds a message dict (e.g.,
TASK_PULL) - Message is serialized to JSON and converted to UTF-8 bytes
- Random padding is added to obscure plaintext length
encrypt()generates a random 12-byte nonce- Plaintext is encrypted with AES-256-GCM using the session key
- Ciphertext (including 16-byte tag) and nonce are framed into binary envelope
- Envelope is sent via HTTPS POST to
/beacon
Server to Agent
- Server receives binary envelope from agent
- Header is validated (magic, version, length)
- Nonce is extracted from first 12 bytes of body
- Remaining bytes are passed to
decrypt()as ciphertext+tag decrypt()verifies authentication tag (raises exception if invalid)- Plaintext is decrypted and padding is stripped
- JSON payload is parsed into a dict
- Server processes the message and builds a response
- Response follows the same encryption flow back to the agent
- Full Encryption Stack
- Full Decryption Stack
Authentication Guarantees
AES-GCM provides strong authentication guarantees:Message Integrity
The 16-byte authentication tag ensures that any modification to the ciphertext will be detected:Message Authenticity
Only parties possessing the session key can create valid ciphertexts. This proves that messages originated from a trusted source (agent or server).Authenticated Encryption vs. Encrypt-then-MAC:
AES-GCM is an AEAD (Authenticated Encryption with Associated Data) mode that provides both confidentiality and authenticity in a single operation. This is preferable to separate encrypt-then-MAC schemes because it’s harder to use incorrectly.
AES-GCM is an AEAD (Authenticated Encryption with Associated Data) mode that provides both confidentiality and authenticity in a single operation. This is preferable to separate encrypt-then-MAC schemes because it’s harder to use incorrectly.
Replay Protection
While AES-GCM prevents tampering, it doesn’t prevent replay attacks. The framework adds an additional layer:common/message_format.py
server/server_main.py
Why not use the AES-GCM nonce for replay protection?
The GCM nonce is only 12 bytes and is used for encryption/decryption. A separate application-level nonce (UUID) is stored in the database to track seen messages, providing defense-in-depth.
The GCM nonce is only 12 bytes and is used for encryption/decryption. A separate application-level nonce (UUID) is stored in the database to track seen messages, providing defense-in-depth.
Security Considerations
Strengths
- Strong Encryption: AES-256-GCM is industry standard and well-analyzed
- Authenticated Encryption: Combines confidentiality and integrity
- Random Nonces: Prevents nonce reuse vulnerabilities
- Replay Protection: Database-backed nonce tracking prevents replay attacks
- Traffic Padding: Random padding obscures message sizes
Limitations
- Static PSK: All agents share the same key in lab environment
- No Forward Secrecy: Compromised PSK decrypts all past communications
- No Key Rotation: Session key never changes during deployment
- Fixed Salt: Salt is hardcoded rather than randomly generated
Production Recommendations
For Production Deployments:
- Implement per-agent keys or asymmetric key exchange (ECDHE)
- Add forward secrecy with ephemeral session keys
- Implement key rotation policies
- Use unique salts per deployment
- Consider certificate-based agent authentication
- Store keys in hardware security modules (HSMs)
Cryptographic Constants
common/crypto.py
- 12-byte nonce: Standard size for AES-GCM (96 bits)
- 32-byte key: AES-256 requires 256-bit (32-byte) keys
- 16-byte tag: Standard GCM tag size for 128-bit security
- HKDF info: Context string for domain separation in key derivation
Changing these constants would break protocol compatibility. Any modifications require synchronized updates to both agent and server code.