Entropy in Packet Analysis

Using information theory to detect network attacks through statistical signatures.

Network traffic has predictable randomness patterns. Normal traffic follows human behavior and protocol structures. Malicious traffic betrays itself through anomalous entropy characteristics.

The Detection Mechanism

Attackers face a dilemma: use predictable patterns and get fingerprinted, or use random patterns and get flagged for high entropy. This asymmetry makes entropy an effective detection tool without requiring specific attack signatures.

Entropy Signatures

Different traffic types have characteristic entropy levels:

Traffic Feature Normal Attack
Source IPs 4-8 bits (clustered) 32 bits (spoofed) or <3 bits (single source)
Destination Ports Low (common services) High (port scan)
Payload Bytes 4-6 bits (plaintext) 7.5-8 bits (encrypted C2)

Shannon entropy formula:

\[H(X) = -\sum_{i=1}^{n} p(x_i) \log_2 p(x_i)\]

Implementation for network data:

import math
from collections import Counter

def calculate_entropy(data):
    if not data:
        return 0.0

    counts = Counter(data)
    total = len(data)

    entropy = 0.0
    for count in counts.values():
        if count > 0:
            p = count / total
            entropy -= p * math.log2(p)

    return entropy

def byte_entropy(payload):
    # Max entropy: 8 bits (256 possible bytes)
    # Encrypted: 7.5-8.0 bits
    # Plaintext: 4-6 bits
    return calculate_entropy(list(payload))

DDoS Detection

Spoofed IP Floods

Random spoofing creates maximum entropy (normalized ~1.0). Normal traffic rarely exceeds 0.7.

def detect_spoofed_flood(packets, threshold=0.85):
    src_ips = [p['src_ip'] for p in packets]
    unique_ratio = len(set(src_ips)) / len(src_ips)
    norm_entropy = calculate_entropy(src_ips) / math.log2(len(set(src_ips)))

    return norm_entropy > threshold and unique_ratio > 0.9

Amplification Attacks

Distinctive asymmetric signature:

def detect_amplification(packets):
    src_ips = [p['src_ip'] for p in packets]
    src_ports = [p['src_port'] for p in packets]

    return (calculate_entropy(src_ips) < 3.0 and
            calculate_entropy(src_ports) < 1.0)

Port Scanning

Sequential scans produce predictable sequences with near-zero entropy. Random scans show high port entropy with high failure rates.

def detect_port_scan(packets):
    dst_ports = [p['dst_port'] for p in packets]
    failures = sum(1 for p in packets if p.get('tcp_flags') == 'RST')

    port_entropy = calculate_entropy(dst_ports)
    unique_ratio = len(set(dst_ports)) / len(dst_ports)
    fail_ratio = failures / len(packets)

    return (port_entropy > 8.0 and
            unique_ratio > 0.8 and
            fail_ratio > 0.7)

Payload Analysis

Content type reveals itself through byte entropy:

Content Entropy
Encrypted (TLS, SSH) 7.5-8.0 bits
Compressed 7.0-7.9 bits
Executables 5.0-7.0 bits
Plaintext 4.0-5.0 bits
Base64 5.9-6.0 bits

DNS tunneling detection:

def detect_dns_tunnel(queries, threshold=4.5):
    suspicious = []
    for query in queries:
        subdomain = query.split('.')[0]
        char_entropy = calculate_entropy(list(subdomain))

        if char_entropy > threshold and len(subdomain) > 50:
            suspicious.append(query)

    return suspicious

Packed malware shows uniformly high entropy (7.0+ bits) across all sections. Normal executables have varied section entropy.

Baseline Detection

Effective detection requires learning normal patterns:

class EntropyDetector:
    def __init__(self, baseline_mean, baseline_std, z_threshold=3.0):
        self.baseline_mean = baseline_mean
        self.baseline_std = baseline_std
        self.z_threshold = z_threshold

    def analyze(self, packets):
        src_ips = [p['src_ip'] for p in packets]
        entropy = calculate_entropy(src_ips)

        z_score = (entropy - self.baseline_mean) / self.baseline_std

        if z_score > self.z_threshold:
            return 'anomaly_detected', entropy, z_score
        return 'normal', entropy, z_score

Sliding window for real-time detection:

from collections import deque, Counter

class SlidingEntropyWindow:
    def __init__(self, window_size):
        self.window = deque(maxlen=window_size)
        self.counts = Counter()

    def add(self, value):
        if len(self.window) == self.window.maxlen:
            old = self.window[0]
            self.counts[old] -= 1
            if self.counts[old] == 0:
                del self.counts[old]

        self.window.append(value)
        self.counts[value] += 1

        return self.entropy()

    def entropy(self):
        total = len(self.window)
        if total == 0:
            return 0.0

        h = 0.0
        for count in self.counts.values():
            p = count / total
            h -= p * math.log2(p)
        return h

Advanced Techniques

KL divergence measures distribution shift from baseline:

\[D_{KL}(P \| Q) = \sum_{x} P(x) \log_2 \frac{P(x)}{Q(x)}\]

Conditional entropy reveals correlation breakdown during attacks. Normal traffic: knowing source IP predicts destination port. Attack traffic: correlation breaks.

Limitations

Entropy is not perfect:

Use entropy as one signal among many. Combine with behavioral models, reputation data, and ML for robust detection.

Conclusion

Entropy detects attacks through statistical fingerprints. Different attack types have distinct randomness signatures. The method works because attackers cannot hide without creating either predictable patterns or detectable randomness.

Key insight: measuring fundamental statistical properties catches attacks that evade signature-based detection. No need to know the specific attack. Measure deviation from expected behavior.

Practical application: baseline normal entropy, detect deviations via z-scores, classify attack type by signature pattern. Works for DDoS, port scans, tunneling, and malware.