Skip to main content

Chapter 2.2 - IDS/IPS: Signatures, Anomaly Detection & Evasion

Module 2: Traffic Analysis & Intrusion Detection Prerequisites: Chapter 2.1 (Packet Analysis & Protocol Dissection)


Table of Contents​

  1. Detection Philosophy: IDS vs IPS
  2. Deployment Architectures
  3. Signature-Based Detection
  4. Anomaly & Behavioral Detection
  5. Snort: Rules, Preprocessors & Tuning
  6. Suricata: Multi-Threading, EVE JSON & Lua Scripting
  7. Evasion Techniques
  8. Detection Engineering Workflow
  9. MITRE ATT&CK Mapping

1. Detection Philosophy: IDS vs IPS​

An Intrusion Detection System (IDS) is a passive observer - it mirrors traffic, inspects it, and raises alerts without modifying the traffic flow. An Intrusion Prevention System (IPS) sits inline; it can drop, reset, or modify packets in real time. The choice is not purely technical - it's also organizational risk tolerance. A misconfigured IPS that generates false positives becomes a self-inflicted denial-of-service. Most enterprise environments start with IDS, validate rules with low false-positive rates, then promote to IPS mode on high-confidence signatures only.

Both paradigms rely on the same detection engines underneath. What changes is the enforcement hook: IDS reads from a tap or SPAN port, while IPS inserts itself into the forwarding path using techniques like NFQUEUE (Linux Netfilter), inline interface pairs, or bump-in-the-wire network appliances.

NIDS vs HIDS:

  • Network IDS (NIDS) operates on raw traffic - sees everything traversing a network segment, but has no visibility into host state (process memory, syscalls, file changes).
  • Host IDS (HIDS) runs as an agent on the endpoint - deep host visibility, but sees only that host's traffic and is subject to tampering if the host is compromised.

In mature environments these layers are complementary. A NIDS detects lateral movement across the wire; a HIDS catches the resulting process execution on the target host.


2. Deployment Architectures​

Tap vs SPAN​

MethodProsCons
Network TAP (hardware)Passive, zero packet loss, cannot be detected or disruptedHardware cost, limited to physical links
SPAN / Mirror PortCheap, flexible, software-configurable on managed switchesCan drop packets under load, limited to switch-local traffic
Inline (bump-in-wire)Active blocking possible, full packet accessSingle point of failure, latency added, fail-open/fail-closed decision required
Virtual TAP / vSwitchWorks in virtualized/cloud environmentsHypervisor overhead, vendor-specific config

Placement Strategy​

Placing a single NIDS at the perimeter misses east-west traffic entirely. The modern approach segments sensors across trust boundaries:

Internet -->[Perimeter Firewall] --> DMZ Sensor
--> [Core Switch] --> Internal Sensor
--> [DC Fabric] --> Data Center Sensor

Each sensor reports to a central SIEM. Correlation across sensors is where sophisticated multi-stage attacks become visible - a port scan from the perimeter sensor followed by authentication anomalies on the internal sensor is a kill-chain signal invisible to either sensor alone.


3. Signature-Based Detection​

Signatures match known-bad patterns: byte sequences, protocol field values, behavioral fingerprints. Detection is fast and precise for known threats. The weaknesses are equally well-known: zero-days produce no signature hits; minor payload mutations break exact matches.

How Signatures Work​

A signature encodes:

  1. Protocol context - TCP, UDP, ICMP, application layer (HTTP, DNS, TLS)
  2. Direction - client-to-server, server-to-client, or bidirectional
  3. Content match - byte patterns, regex, protocol field values
  4. Threshold / frequency - detect on N occurrences in T seconds

False Positive vs False Negative Tradeoff​

Alert firesAlert does not fire
Attack presentTrue PositiveFalse Negative
No attackFalse PositiveTrue Negative

Signature sensitivity is tuned by adjusting content specificity and threshold. A signature matching User-Agent: Mozilla fires on virtually all HTTP traffic (useless). A signature matching a specific 16-byte shellcode sequence is highly precise but misses polymorphic variants.


4. Anomaly & Behavioral Detection​

Anomaly detection establishes a statistical baseline of "normal" and alerts when observed behavior deviates beyond a threshold. It is the only mechanism with a theoretical chance of catching novel attacks.

Statistical Methods​

  • Volume-based: bytes/sec, packets/sec, flows/sec per host or segment. A host suddenly sending 10x its baseline egress is anomalous.
  • Protocol conformance: RFC-compliant parsers flag malformed headers. A TCP segment with both SYN and FIN set simultaneously is RFC-illegal - either a scanner or an evasion attempt.
  • Entropy analysis: DNS query labels with high entropy (e.g., a3f9c2b1d4e7.evil.com) suggest DNS tunneling or DGA (Domain Generation Algorithm) traffic. Shannon entropy:
H = -Sum p(x) * log2(p(x))

Normal hostnames score 2.5-3.5 bits/char. DGA domains typically score 3.8-4.2.

  • Flow behavior profiling: src_port, dst_port, byte_count, packet_count, duration tuples per source IP. NetFlow / IPFIX data feeds ML models (isolation forests, autoencoders) that flag outlier flows without inspecting payload.

Behavioral Detection​

Rather than detecting a specific exploit, behavioral rules fire on patterns of activity:

  • Horizontal scan: one IP contacts >N unique IPs on the same port in T seconds
  • Vertical scan: one IP contacts >N unique ports on the same destination in T seconds
  • Beaconing: periodic outbound connections at fixed intervals (C2 check-in)
  • Exfiltration spike: upload-to-download ratio reversal for a host that normally consumes more than it produces

Behavioral detection produces fewer actionable alerts than signature detection but catches evasive, slow-burn campaigns that deliberately stay below per-signature thresholds.


5. Snort: Rules, Preprocessors & Tuning​

Snort is the reference NIDS implementation. Its rule language is widely cloned (Suricata accepts Snort-compatible rules). Understanding Snort rule anatomy is foundational.

Rule Structure​

action proto src_ip src_port direction dst_ip dst_port (options)

Full anatomy of a real rule:

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
msg:"ET WEB_SERVER SQL Injection attempt -- select from";
flow:established,to_server;
content:"select"; nocase; http_uri;
content:"from"; nocase; http_uri; distance:0;
pcre:"/select.{0,30}from/Ui";
classtype:web-application-attack;
sid:2006445;
rev:9;
metadata:affected_product Web_Server_Applications,
attack_target Web_Server,
mitre_tactic TA0001;
)
FieldMeaning
alertGenerate alert (other actions: drop, reject, pass, log)
tcpMatch TCP protocol
$EXTERNAL_NETVariable defined in snort.conf; typically !$HOME_NET
flow:established,to_serverMatch only on packets within an established TCP session, in the client->server direction
content:"select"; nocase; http_uriCase-insensitive byte match in the HTTP URI buffer (post-normalization)
distance:0Second content must appear immediately after first
pcre:"/select.{0,30}from/Ui"Perl-compatible regex; U = HTTP URI buffer, i = case-insensitive
classtypeAssigns priority class for alerting
sidUnique rule ID

Preprocessors / Inspectors​

Raw TCP reassembly and application-layer normalization happen in preprocessors before rules execute. This is critical: a content match against a fragmented or out-of-order stream would miss attacks split across segments.

Key preprocessors:

  • stream5 - TCP/UDP stream reassembly and session tracking
  • frag3 - IP defragmentation (prevents fragment overlap attacks)
  • http_inspect - HTTP normalization: decodes %20, //, ..%2F, chunk encoding, etc.
  • dcerpc2 - SMB/DCERPC parsing (detects EternalBlue-style exploits)
  • sfportscan - Detects horizontal/vertical scan patterns
# Run Snort in IDS mode on interface eth0, log to /var/log/snort
snort \
-i eth0 \ # capture interface
-c /etc/snort/snort.conf \ # main config (includes rules, preprocessors, vars)
-A fast \ # alert format: fast (one-line), full, unified2
-l /var/log/snort \ # log directory
-D \ # daemonize
-u snort -g snort # drop privileges after startup

# Test a rule file for syntax errors without capturing live traffic
snort -T -c /etc/snort/snort.conf

# Replay a pcap against Snort rules (offline analysis)
snort \
-r capture.pcap \ # read from pcap instead of live interface
-c /etc/snort/snort.conf \
-A console \ # print alerts to stdout
-l /tmp/snort_replay

Tuning: Suppression and Thresholding​

Untuned Snort on a busy network produces thousands of alerts/hour, most of which are false positives or low-value noise. Suppression and thresholding are the primary tools:

# threshold.conf -- suppress a specific sid from a specific source entirely
suppress gen_id 1, sig_id 2006445, track by_src, ip 10.10.10.5

# Rate-limit: only alert once per 60 seconds per source IP
threshold gen_id 1, sig_id 2006445, type limit, track by_src, count 1, seconds 60

# Alert only when threshold is exceeded: 10 hits in 5 seconds
threshold gen_id 1, sig_id 1000001, type threshold, track by_src, count 10, seconds 5

6. Suricata: Multi-Threading, EVE JSON & Lua Scripting​

Suricata is the production-grade evolution of the Snort model. Key differentiators:

FeatureSnort 2.xSnort 3.xSuricata
Multi-threadingNo (single-threaded)YesYes (worker threads per CPU)
Protocol detectionPort-basedPort-basedPort-agnostic (deep protocol ID)
TLS inspectionLimitedLimitedJA3/JA4 fingerprinting, cert extraction
Outputunified2 binaryunified2 / JSONEVE JSON (rich structured logs)
Lua scriptingNoLimitedYes - full Lua rule engine
File extractionNoNoYes (HTTP, SMB, FTP, NFS)

EVE JSON Output​

Suricata's EVE (Extensible Event Format) JSON log is the primary integration point with SIEMs and Elastic Stack. Every event type (alert, flow, dns, http, tls, files) emits a structured JSON record:

{
"timestamp": "2024-11-15T14:23:01.882342+0000",
"event_type": "alert",
"src_ip": "192.168.1.50",
"src_port": 54821,
"dest_ip": "203.0.113.44",
"dest_port": 4444,
"proto": "TCP",
"alert": {
"action": "allowed",
"gid": 1,
"signature_id": 2100498,
"rev": 7,
"signature": "GPL ATTACK_RESPONSE id check returned root",
"category": "Potentially Bad Traffic",
"severity": 2
},
"flow": {
"pkts_toserver": 6,
"pkts_toclient": 9,
"bytes_toserver": 472,
"bytes_toclient": 1841
},
"payload": "aWQKdWlkPTAocm9vdCkgZ2lkPTAocm9vdCkK",
"payload_printable": "id\nuid=0(root) gid=0(root)\n"
}

Suricata CLI​

# Start Suricata in IDS mode; AF_PACKET for high-performance packet capture
suricata \
-c /etc/suricata/suricata.yaml \ # main config
--af-packet=eth0 \ # AF_PACKET mode (kernel bypass lite)
-D \ # daemonize
--pidfile /var/run/suricata.pid

# Replay pcap offline (useful for testing new rules)
suricata \
-c /etc/suricata/suricata.yaml \
-r suspicious_traffic.pcap \ # offline pcap
-l /tmp/suricata_output # EVE JSON written here

# Live-tail alerts from EVE JSON
tail -f /var/log/suricata/eve.json \
| jq 'select(.event_type == "alert") | {ts: .timestamp, sig: .alert.signature, src: .src_ip}'

# Update Emerging Threats ruleset
suricata-update # pulls latest ET Open rules, rebuilds rule index
suricata-update --reload-command "kill -USR2 $(cat /var/run/suricata.pid)"

Lua Scripting Example​

Lua rules execute arbitrary detection logic beyond what the rule keyword language supports. Useful for stateful detection across multiple packets:

-- detect_base64_dns_tunnel.lua
-- Fires if a DNS query name is >40 chars and entropy > 3.8

function init(args)
local needs = {}
needs["payload"] = tostring(true) -- request access to packet payload
return needs
end

function match(args)
local payload = args["payload"]
if not payload then return 0 end

-- extract DNS QNAME (simplified: skip 12-byte header)
local qname = payload:sub(13)
if #qname < 40 then return 0 end

-- Shannon entropy calculation
local counts = {}
for i = 1, #qname do
local c = qname:sub(i, i)
counts[c] = (counts[c] or 0) + 1
end
local entropy = 0
for _, count in pairs(counts) do
local p = count / #qname
entropy = entropy - p * math.log(p) / math.log(2)
end

if entropy > 3.8 then return 1 end -- 1 = match, 0 = no match
return 0
end
-- Rule referencing the Lua script
alert dns any any -> any 53 (
msg:"Possible DNS Tunnel - High Entropy Query";
lua:detect_base64_dns_tunnel.lua;
sid:9900001;
rev:1;
)

7. Evasion Techniques​

Evasion is the adversary's answer to signature detection. Understanding evasion is mandatory for writing effective rules and for understanding the hard limits of signature-based systems.

Architecture of Evasion​

TCP Fragmentation & Reassembly Desync​

The classic Ptacek-Newsham evasion: send the same byte range in two overlapping fragments with different content. The IDS sees fragment A (benign), the endpoint reassembles using fragment B (malicious) based on the OS's overlap policy.

# fragroute -- intercept and fragment outbound packets
# /etc/fragroute.conf:
# ip_frag 8 # fragment into 8-byte chunks
# tcp_seg 1 # segment TCP into 1-byte chunks

fragroute -f /etc/fragroute.conf 192.168.1.100

# scapy -- craft overlapping fragments manually
python3 << 'EOF'
from scapy.all import *

target = "192.168.1.100"
payload_a = b"GET /safe" # fragment 1: benign
payload_b = b"GET /exploit" # fragment 2: same offset, malicious content
# Note: endpoint OS reassembly policy determines which wins

p1 = IP(dst=target, flags="MF", frag=0) / TCP(dport=80) / payload_a[:8]
p2 = IP(dst=target, frag=0) / TCP(dport=80) / payload_b[:8] # overlaps frag 0
send([p1, p2], verbose=False)
EOF

Defense: Modern preprocessors (frag3 in Snort, defrag engine in Suricata) implement per-OS reassembly policies and resolve overlaps consistently with the target OS, eliminating the desync window - provided the target OS is correctly identified in the preprocessor config.

HTTP Evasion​

Web application attacks are particularly susceptible to normalization evasion because HTTP allows many equivalent representations:

/admin/../admin/         -> /admin/           (path traversal collapse)
/%61dmin/ -> /admin/ (%61 = 'a' in hex)
/ADMIN/ -> /admin/ (case normalization)
/%252F -> /%2F -> / (double-encoding)
/admin%09/ -> /admin/ (tab character)

A signature matching content:"/etc/passwd" misses /etc%2Fpasswd, /%65%74%63/passwd, /etc/./passwd, etc. IDS HTTP preprocessors must normalize before matching.

# Test normalization coverage with nikto
nikto \
-h http://192.168.1.100 \
-evasion 1 \ # random URI encoding
-evasion 2 \ # directory self-reference /./
-evasion 4 # premature URL ending

Insertion Attacks​

Send packets the IDS processes but the endpoint rejects - inserting "noise" into the stream to break content matches:

  • TCP segments with invalid checksums (endpoint discards them; some IDS engines process them)
  • Packets with expired TTLs (reach IDS but not endpoint)
  • TCP RSTs mid-stream (endpoint closes connection; IDS may continue tracking)
# scapy: send a packet with TTL=1 (dies at first hop, never reaches target)
# If IDS is behind that hop, it processes the packet; target never sees it
from scapy.all import *
p = IP(dst="192.168.1.100", ttl=1) / TCP(dport=80, flags="A") / b"BADCONTENT"
send(p)

Defense: IDS sensors must be placed as close to the target as possible to minimize TTL manipulation windows. Suricata's stream-reassembly engine validates checksums; configure checksum-validation: yes in suricata.yaml.

Slow / Low-and-Slow Scans​

# nmap slow scan: 1 probe per ~15 minutes, randomized order
nmap \
-sS \ # SYN scan
-T0 \ # paranoid timing: 5 min between probes
--randomize-hosts \ # randomize target order
--data-length 15 \ # append random data to packets (breaks length signatures)
--ttl 64 \
192.168.1.0/24

# hping3: craft custom timing
hping3 \
-S \ # SYN flag
-p 22 \ # destination port
-i u1000000 \ # interval: 1 second between packets
192.168.1.100

Threshold-based detection (fire after N probes in T seconds) has a direct bypass: reduce probe rate below the threshold. Behavioral detection with longer time windows and per-source tracking is required.

Encryption as Evasion (T1573)​

TLS 1.3 with forward secrecy renders payload inspection impossible without a TLS inspection proxy. Attackers route C2 traffic over HTTPS/443 to legitimate-looking domains:

  • Domain Fronting: route traffic through a CDN (Cloudflare, AWS CloudFront); the SNI/Host header points to a legitimate domain while the actual content is proxied to the C2
  • HTTPS C2: Cobalt Strike, Covenant, Sliver all support full TLS C2 channels
  • Encrypted DNS: DNS-over-HTTPS (DoH) routes DNS queries over HTTPS, bypassing DNS-based detection

Without payload access, detection falls back on metadata:

  • JA3/JA4 fingerprinting: hash of TLS ClientHello parameters (cipher suites, extensions, elliptic curves) - fingerprints TLS client library independently of payload
  • Certificate analysis: self-signed certs, short validity windows, newly registered domains
  • Flow timing analysis: beacon periodicity, jitter patterns
# Extract JA3 fingerprints from pcap using zeek
zeek -r capture.pcap /opt/zeek/share/zeek/policy/protocols/ssl/ja3.zeek
cat ssl.log | zeek-cut ja3 ja3s server_name

# ja3 via python-ja3
pip install pyja3
python3 -m ja3 --json capture.pcap | jq '.[] | select(.ja3_digest == "51c64c77e60f3980eea90869b68c58a8")'
# Known Cobalt Strike default JA3: 51c64c77e60f3980eea90869b68c58a8 (changes with malleable profiles)

8. Detection Engineering Workflow​

Writing and deploying rules is not a one-time task - it is a continuous engineering loop. Alert quality degrades as the environment changes; rules written for last year's attack patterns may be tuned into silence while new techniques go undetected.

Building a Test Lab for Rule Validation​

# Replay attack PCAPs against Suricata offline -- no production risk
# 1. Download known-malicious PCAP
wget https://malware-traffic-analysis.net/2024/01/15/2024-01-15-Formbook-infection-traffic.pcap.zip
unzip -P infected 2024-01-15*.zip

# 2. Run Suricata against it
suricata \
-c /etc/suricata/suricata.yaml \
-r 2024-01-15-Formbook-infection-traffic.pcap \
-l /tmp/test_output \
-S /etc/suricata/rules/emerging-malware.rules # load specific ruleset only

# 3. Check what fired
cat /tmp/test_output/eve.json \
| jq 'select(.event_type=="alert") | .alert.signature' \
| sort | uniq -c | sort -rn

# 4. Check for missed events (what a competing ruleset catches that yours doesn't)
# Compare against ET Pro or Snort VRT results

Rule Performance Profiling​

# Suricata rule profiling -- identifies slowest rules
# In suricata.yaml, enable:
# profiling:
# rules:
# enabled: yes
# filename: rule_perf.log
# append: yes
# sort: avgticks

# After a run, examine top CPU consumers
cat /var/log/suricata/rule_perf.log | head -30
# Slow rules are usually PCRE-heavy; optimize by adding fast_pattern content match first
# fast_pattern tells Suricata to use the specified content for the multi-pattern matcher
# before evaluating the full rule

alert http any any -> any any (
msg:"Slow PCRE - optimized with fast_pattern";
content:"cmd.exe"; fast_pattern; http_uri; # MPSE matches this first
pcre:"/cmd\.exe(\s|%20|\+)\/[cC]/U"; # PCRE only runs if fast_pattern hit
sid:9900002;
)

9. MITRE ATT&CK Mapping​

TechniqueATT&CK IDDetection MethodKey Rule/Logic
Network Service ScanningT1046Behavioral (port scan thresholds)sfportscan preprocessor; flow count per src
Exploit Public-Facing AppT1190Signature (HTTP/SQL injection patterns)ET rules 2006445 family
C2 over HTTPST1071.001TLS metadata (JA3, cert analysis)JA3 blacklist; beacon periodicity
DNS Tunneling (C2)T1071.004Entropy analysis; query lengthLua entropy script; dns_query length threshold
Protocol TunnelingT1572Protocol anomalyNon-HTTP on port 80/443
Traffic Signaling (covert C2)T1205Behavioral (timing analysis)NetFlow jitter analysis
Obfuscated Files / InfoT1027Content inspection (base64, compression headers in unexpected contexts)file_data buffer inspection
Indicator Removal: Clear LogsT1070HIDS (file integrity monitoring)Out of scope for NIDS
Defragmentation EvasionT1599Preprocessor normalizationfrag3 / defrag engine
Encrypted ChannelT1573TLS fingerprinting; cert inspectionJA3; Suricata TLS app-layer