The fragility of conventional C2 channels
Most command-and-control (C2) frameworks assume a clean, reliable, and persistent network connection. They wrap an interactive shell in a standard HTTP/S beacon or a raw TCP socket and send heartbeats every few seconds.
In hardened enterprise networks, this model breaks down immediately:
- TLS Inspection and Interception: Corporate proxies terminate outbound TLS connections, inspect request bodies, and validate client JA3/JA4 fingerprints. Unfamiliar Go or custom TLS client handshakes are flagged or dropped at the perimeter.
- Behavioral Beaconing Detection: Network detection systems (NDR) calculate periodicity heuristics across outbound HTTP streams. Consistent jitterless polling schedules trigger automated quarantine.
- Egress Filtering: Strictly segmented subnets (such as SCADA networks, core banking enclaves, or air-gapped engineering environments) block outbound TCP ports 80 and 443 entirely. Only internal DNS resolvers or specific loopback relays are permitted egress.
When direct TCP/TLS egress is denied, operators turn to out-of-band and covert communication channels—transporting data over DNS, ICMP, DoH, WebSocket, or header steganography.
However, covert channels introduce a new operational failure mode: fragility. Subdomains in DNS queries have strict 63-character label limits and 255-character overall payload limits. UDP packets get dropped or arrive out of order. Network gateways rewrite header fields, and aggressive proxy timeouts truncate connections.
To study how to build resilient, fault-tolerant transport pipelines across degraded networks, we engineered ghostlink.
The ghostlink pipeline architecture
The core architectural principle of ghostlink is the strict decoupling of data preparation and session cryptography from the physical or network transport.
The encoding pipeline treats the network as an untrusted, unreliable bit-pipe. Before a single packet is dispatched, the payload passes through five deterministic stages:
1. Compression (zlib)
Covert channels like DNS or ICMP have severe bandwidth penalties. ghostlink compresses the raw byte stream using zlib, reducing character count and minimizing the total number of frames required to transmit a given command or exfiltrated file.
2. Forward Error Correction (XOR parity layout)
On lossy transports like UDP-based DNS or raw ICMP, packets can be dropped silently. Rather than relying on heavy TCP-style retransmission handshakes (which multiply network signatures), ghostlink generates XOR parity shards:
$$\text{Parity} = \text{Chunk}_1 \oplus \text{Chunk}_2 \oplus \dots \oplus \text{Chunk}_k$$
If any single data chunk in a group is lost in transit, the receiver mathematically reconstructs the missing byte sequence from the surviving chunks and the parity shard.
3. Framing & envelope protocol v2
Each chunk is packaged into a structured frame containing stream identifiers, sequence numbers, and total chunk counts:
type FrameHeader struct {
Magic [2]byte // 0x47, 0x4C ('GL')
Version uint8 // Protocol v2
StreamID uint32 // Unique transaction identifier
SeqNum uint16 // Chunk index (0-based)
TotalChunks uint16 // Total chunks in stream
PayloadLen uint16 // Length of chunk payload
Flags uint8 // Parity, control, or fin flags
}
4. Ephemeral session encryption (P-256 ECDHE + AES-256-GCM)
Even if an intermediate network tap captures the raw covert frames, the payload contents are protected. ghostlink performs an ephemeral P-256 elliptic-curve Diffie-Hellman (ECDHE) exchange (or uses a configured pre-shared key) to derive a 256-bit symmetric session key. Every framed chunk is encrypted using AES-256-GCM with a unique 12-byte initialization vector (IV), ensuring both confidentiality and cryptographic integrity.
The 9 covert transport implementations
ghostlink features 9 registered channel drivers designed for controlled laboratory research:
| Transport | Channel Mechanism | Carrier Medium | Environment Requirements |
|---|---|---|---|
| DNS | Subdomain query framing | UDP / Port 53 | Dedicated authoritative nameserver or lab loopback |
| DoH | DNS queries over HTTPS (RFC 8484) | HTTPS / Port 443 | Reaches public DoH resolvers (Cloudflare, Google) |
| HTTP | Chunked request/response headers | HTTP / Port 80, 8080 | Standard web proxies, loopback HTTP |
| WebSocket | Persistent framed data streaming | WS / WSS | Full-duplex bidirectional stream |
| ICMP | Echo request & reply data fields | ICMP Type 8 / Type 0 | Requires raw socket (CAP_NET_RAW or root) |
| TCP ISN | Initial Sequence Number modulation | Raw TCP 32-bit SYN ISN | Experimental; OS TCP stack dependent |
| IPv4 ID | IP identification field modulation | 16-bit IPv4 header ID | Experimental; requires raw packet injection |
| Timing | Inter-arrival timing modulation | Packet arrival intervals | Highly sensitive to network jitter |
| Packet Size | Length modulation | Modulated frame padding | Sensitive to MTU and router fragmentation |
Deep-dive: DNS & DoH query framing
In the DNS transport driver, binary ciphertext chunks are encoded into Base32 or Hex strings that strictly conform to RFC 1035 label specifications. A long exfiltration stream is fragmented into queries formatted as:
[seq].[stream-id].[chunk-data].[signature].c2.lab.internal
The authoritative DNS server receives the iterative queries from upstream resolvers, strips the domain suffix, extracts the sequence headers, and feeds the stream to the receiver engine.
Offline pipeline verification & deterministic test harness
Testing C2 transports on live networks without prior validation is reckless. ghostlink provides a dedicated offline validation harness that exercises the full cryptographic and framing pipeline without touching a network interface:
# Verify build, platform, and protocol readiness
ghostlink doctor
# List registered transport drivers and capabilities
ghostlink transports
# Run deterministic end-to-end pipeline validation
ghostlink demo
The demo command runs an end-to-end transfer entirely in-memory:
GHOSTLINK REFERENCE DEMO
Platform: darwin/arm64 · Protocol: v2 · Mode: offline loopback
[1] P-256 ECDHE Ephemeral Key Exchange:
Client Public Key: 04a1b2c3d4e5f6... (P-256)
Server Public Key: 04f6e5d4c3b2a1... (P-256)
Derived Shared Key: 256-bit AES-GCM session key confirmed
[2] Payload Processing:
Raw Input: "CONFIDENTIAL_LAB_RECORD_ARTIFACT_DATA_2026" (44 bytes)
zlib Compression: 44 bytes -> 38 bytes
XOR Parity Layout: 3 data shards + 1 parity shard generated
[3] Protocol v2 Framing & AES-256-GCM Encryption:
Frame 0 (Seq 0/3): 14 bytes ciphertext + 12-byte IV + 16-byte GCM tag
Frame 1 (Seq 1/3): 14 bytes ciphertext + 12-byte IV + 16-byte GCM tag
Frame 2 (Seq 2/3): 10 bytes ciphertext + 12-byte IV + 16-byte GCM tag
Frame 3 (Parity): 14 bytes ciphertext + 12-byte IV + 16-byte GCM tag
[4] Receiver Decryption & Reassembly:
All frames verified against GCM authentication tags.
Recovered Bytes: 44 bytes
SHA-256 Checksum: d4f8a1... [EXACT MATCH]
Defensive detection engineering: sniffing the covert channels
Understanding how out-of-band channels operate is essential for engineering effective network detections:
1. DNS entropy and volume heuristics
Covert DNS channels produce distinctive statistical patterns. Defenders should monitor for:
- High Shannon Entropy: Base32/Hex encoded labels exhibit entropy above 3.8, while legitimate hostnames rarely exceed 3.0.
- Query Volume Anomaly: Thousands of unique subdomains queried under a single second-level domain (SLD) within minutes.
- Resource Record Types: Repeated
TXTorNULLqueries used to return large command responses to endpoints.
2. Wireshark protocol dissection (tools/ghostlink.lua)
To assist blue teams during packet analysis, ghostlink includes a custom Lua dissector for Wireshark. When inspecting network captures, Wireshark automatically identifies GL protocol v2 magic bytes, decodes frame headers, and displays stream sequencing:
-- tools/ghostlink.lua: Wireshark dissector snippet
local p_ghostlink = Proto("ghostlink", "Ghostlink Protocol v2")
local f_magic = ProtoField.bytes("ghostlink.magic", "Magic")
local f_version = ProtoField.uint8("ghostlink.version", "Version")
local f_stream = ProtoField.uint32("ghostlink.stream", "Stream ID", base.HEX)
local f_seq = ProtoField.uint16("ghostlink.seq", "Sequence Number")
local f_total = ProtoField.uint16("ghostlink.total", "Total Chunks")
function p_ghostlink.dissector(buffer, pinfo, tree)
if buffer:len() < 14 then return end
if buffer(0,2):string() ~= "GL" then return end
pinfo.cols.protocol = "GHOSTLINK"
local subtree = tree:add(p_ghostlink, buffer(), "Ghostlink Transport Frame")
subtree:add(f_magic, buffer(0,2))
subtree:add(f_version, buffer(2,1))
subtree:add(f_stream, buffer(3,4))
subtree:add(f_seq, buffer(7,2))
subtree:add(f_total, buffer(9,2))
end
Evidence and limits
All testing, throughput evaluations, and protocol dissections were performed in isolated laboratory virtual machines over loopback (127.0.0.1) and private virtual networks. Experimental raw-packet channels (TCP ISN, IPv4 ID, timing modulation) require root/administrator privileges and are sensitive to host scheduler jitter and intermediate router re-packetization. No unauthorized networks or live production systems were targeted.
References
- jankesec, ghostlink (Out-of-Band Transport Research Framework): github.com/jankesec/ghostlink
- IETF, DNS Queries over HTTPS (RFC 8484): datatracker.ietf.org/doc/html/rfc8484
- NIST, Recommendation for Block Cipher Modes of Operation: GCM (SP 800-38D): csrc.nist.gov/publications/detail/sp/800-38d/final
- MITRE ATT&CK, Protocol Tunneling (T1572): attack.mitre.org/techniques/T1572/
- MITRE ATT&CK, Application Layer Protocol (T1071): attack.mitre.org/techniques/T1071/
How current is this note?
The latest source-review, content-update, or publication date is shown.
The author completed a technical review. This does not, by itself, claim lab reproduction.

