2026-03-08

Hiding in Plain Sight: ICMP Tunneling, Firewall Evasion, and the Forensic Hunt

Introduction

Every network has defensive rules. Firewalls and access control lists regulate traffic flow, yet attackers often exploit trusted protocols to bypass these controls. A locked door is only as strong as the walls around it. ICMP (Internet Control Message Protocol) represents one such vulnerability, a protocol so trusted that firewalls frequently permit it without inspection.

What is ICMP?

ICMP serves diagnostic functions in networking, carrying error messages and operational information about IP operations. It powers the ping command that network administrators rely on for testing connectivity. Unlike TCP or UDP, ICMP lacks ports, sessions, or connection concepts, making it historically lenient in firewall configurations.

How ICMP Tunneling Works

The technique encapsulates arbitrary data, such as SSH sessions, inside ICMP echo request and reply packet payloads. The process involves:

  1. Client-side tools chunk TCP data into payloads
  2. Each chunk wraps inside an ICMP echo request sent to a relay server
  3. The relay unwraps the payload, forwards TCP data to the actual destination
  4. Responses return wrapped in ICMP echo replies
  5. The client reconstructs the TCP stream

Tools like ptunnel-ng, icmptunnel, and hans automate this setup. The technique succeeds because packets appear legitimate at first glance, valid checksums, expected request/reply patterns, and many firewalls without deep packet inspection allow them through.

Detection Methods

Digital forensics can identify ICMP tunneling through several anomaly indicators:

Volume and Frequency: Normal ICMP traffic is sporadic; tunneling creates continuous high-frequency streams. Alert on hosts sending more than 50-100 ICMP packets per minute consistently.

Payload Size: Legitimate ICMP echo requests carry 32 bytes (Windows) or 56 bytes (Linux). Tunneling tools use MTU-sized payloads exceeding 1400 bytes. Filter with:

icmp and data.len > 64

Entropy Analysis: High-entropy payloads suggest encrypted tunneled data; tools like binwalk and scalpel can quantify this.

Timing Patterns: Tunneling generates programmatic timing patterns that statistical analysis can reveal through inter-packet arrival time examination.

Endpoint Forensics: Search for running processes (ptunnel, hans, icmptunnel), unusual loopback listeners, shell history, and system logs.

Defensive Countermeasures

Organizations can implement several protections:

  • Rate-limit ICMP at perimeter boundaries (approximately 10 packets per second per host)
  • Enable deep packet inspection on next-generation firewalls
  • Deploy IDS signatures, Snort and Suricata both provide ICMP tunneling detection rules
  • Block oversized ICMP payloads (anything over 128 bytes is suspicious)
  • Continuously monitor and baseline ICMP traffic patterns

Hands-On Lab Architecture

The lab uses an LXC-based setup with three containers:

  • lxc-client (10.10.10.10): Restricted internal client
  • lxc-relay (10.10.10.20): Relay/proxy server outside firewall
  • lxc-target (10.10.10.30): SSH server destination

A firewall rule permits only ICMP between client and relay while blocking TCP/UDP traffic.

Prerequisites

Install required tools on Ubuntu 22.04/Debian 12:

sudo apt update && sudo apt install -y lxc lxc-utils lxc-templates \
  iptables net-tools tcpdump wireshark-common tshark

Container Creation

# Create three Ubuntu 22.04 containers
sudo lxc-create -n lxc-client -t ubuntu -- -r jammy
sudo lxc-create -n lxc-relay  -t ubuntu -- -r jammy
sudo lxc-create -n lxc-target -t ubuntu -- -r jammy

Network Configuration

Configure static IPs by editing container configs at /var/lib/lxc/<name>/config:

lxc-client config:

lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.ipv4.address = 10.10.10.10/24
lxc.net.0.ipv4.gateway = 10.10.10.1

lxc-relay config:

lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.ipv4.address = 10.10.10.20/24
lxc.net.0.ipv4.gateway = 10.10.10.1

lxc-target config:

lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.ipv4.address = 10.10.10.30/24
lxc.net.0.ipv4.gateway = 10.10.10.1

Starting Containers

sudo lxc-start -n lxc-client
sudo lxc-start -n lxc-relay
sudo lxc-start -n lxc-target

# Verify operation
sudo lxc-ls --fancy

Software Installation

On relay and client:

sudo lxc-attach -n lxc-relay  -- bash -c "apt update && apt install -y ptunnel-ng"
sudo lxc-attach -n lxc-client -- bash -c "apt update && apt install -y ptunnel-ng openssh-client"

On target:

sudo lxc-attach -n lxc-target -- bash -c "apt update && apt install -y openssh-server && \
  useradd -m labuser && echo 'labuser:labpass' | chpasswd && \
  systemctl enable ssh && systemctl start ssh"

Firewall Simulation

Implement iptables rules to block TCP/UDP while permitting ICMP:

# Allow ICMP between client and relay
sudo iptables -A FORWARD -p icmp -s 10.10.10.10 -d 10.10.10.20 -j ACCEPT
sudo iptables -A FORWARD -p icmp -s 10.10.10.20 -d 10.10.10.10 -j ACCEPT

# Block other traffic from client to relay
sudo iptables -A FORWARD -s 10.10.10.10 -d 10.10.10.20 -j DROP
sudo iptables -A FORWARD -s 10.10.10.20 -d 10.10.10.10 -j DROP

# Allow relay <-> target unrestricted
sudo iptables -A FORWARD -s 10.10.10.20 -d 10.10.10.30 -j ACCEPT
sudo iptables -A FORWARD -s 10.10.10.30 -d 10.10.10.20 -j ACCEPT

# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1

Verify firewall operation, direct SSH from client to target should fail:

sudo lxc-attach -n lxc-client -- ssh labuser@10.10.10.30
# Expected: Connection timed out

Establishing the ICMP Tunnel

Start the proxy on the relay server:

sudo lxc-attach -n lxc-relay -- ptunnel-ng -v 3

On the client, establish the tunnel forwarding local port 2222 through ICMP:

sudo lxc-attach -n lxc-client -- ptunnel-ng \
  -p 10.10.10.20 \
  -lp 2222 \
  -da 10.10.10.30 \
  -dp 22 \
  -v 3 &

Connect via SSH through the tunnel:

sudo lxc-attach -n lxc-client -- ssh labuser@localhost -p 2222
# Password: labpass

This establishes an SSH connection over ICMP packets through the simulated firewall.

Traffic Capture and Forensic Analysis

Capture ICMP traffic on the LXC bridge:

sudo tcpdump -i lxcbr0 -w /tmp/icmp_tunnel_capture.pcap icmp

Analyze packet sizes using tshark:

tshark -r /tmp/icmp_tunnel_capture.pcap \
  -T fields \
  -e frame.number \
  -e ip.src \
  -e ip.dst \
  -e icmp.type \
  -e data.len \
  | sort -k5 -n | tail -20

Examine packet frequency:

tshark -r /tmp/icmp_tunnel_capture.pcap \
  -q -z io,stat,1,"icmp"

Open in Wireshark for visual inspection:

wireshark /tmp/icmp_tunnel_capture.pcap &
# Filter: icmp and data.len > 100

Observable evidence includes:

  • Payloads exceeding 1000 bytes versus 64-byte baseline
  • Packet rates far exceeding typical ping behavior
  • Clear bidirectional request/reply patterns

Cleanup

# Remove firewall rules
sudo iptables -F FORWARD

# Stop containers
sudo lxc-stop -n lxc-client
sudo lxc-stop -n lxc-relay
sudo lxc-stop -n lxc-target

# Optional: destroy containers
sudo lxc-destroy -n lxc-client
sudo lxc-destroy -n lxc-relay
sudo lxc-destroy -n lxc-target

Conclusion

ICMP tunneling demonstrates protocol abuse where a diagnostic utility becomes a covert channel. Building this lab reveals how SSH sessions traverse ICMP packets, captured evidence becomes forensically identifiable, and the architectural assumptions underlying firewall trust prove exploitable. Understanding protocol design at this depth illuminates both offensive possibilities and defensive necessities.