ICMP(Internet Control Message
Protocol,Internet 报文协议) 是 TCP/IP 的一种子协议,位于 OSI7 层网络模型中的网络层,其目的是用于在 IP 主机、路由器之前传递控制信息
OS17 层网络模型
图片来自 https://www.cnblogs.com/yunjisuanchengzhanglu/p/16502307.html
ICMP 工作流程
ICMP 中提供了多种报文,这些报文又可以分成两大类:"差错通知" 和 "信息查询"
差错通知
当 IP 数据包在对方计算机处理过程中出现未知的发送错误时,ICMP 会向发送者传送错误事实以及错误原因等,示意图如下
信息查询
信息查询由一个请求和一个应答构成的。只需要向目标发送一个请求数据包,如果收到了来自目标的回应,就可以判断目标是活跃主机,否则可以判断目标是非活跃主机,示意图如下
ICMP 主机探测过程
Ping 命令是 ICMP 中较为常见的一种应用,经常使用这个命令来测试本地与目标之前的连通性,发送一个 ICMP 请求消息给目标主机,若源主机收到目标主机的应答响应消息,则表示目标可达,主机存在。例如想要判断某一台主机是否为存活主机,那么 ping 一下该主机,查看有无回应即可判断是否存活
现在来编写一个利用 ICMP 实现探测活跃主机的代码程序。程序有很多种可实现的方法,这里借助 Scapy 库来完成。它是 python 中的一个第三方库,在 Scapy 库中已经实现了大量的网络协议。如 TCP、UDP、IP、ARP 等,使用该库可以灵活编写各种网络工具。
首先安装 Scapy
接下来就可以进行实现了
1. 导入程序代码所应用到的模块:scapy、random、optparse,其中 scapy 用于发送 ping 请求和接受目标主机的应答数据
1 2 3
| from scapy.all import * from random import randint from optparse import OptionParser
|
对用户输入的参数进行接受和批量处理,并将处理后的 IP 地址传入 Scan 函数
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
| def main(): parser = OptionParser("Usage:%prog -i <target host>") #输出帮助信息 parser.add_option('-i',type='string',dest='IP',help='specify target host') #获取IP参数 options,args = parser.parse_args() print("Scan report for"+options.IP+"\n") #判断是单台主机还是多台主机 #IP中存在-,说明是要扫描多台主机 if '-' in options.IP: #代码举例:198.168.1.1-120 #通过”-“进行分隔,把192.168.1.1和120分开 #把192.168.1.1通过”,“进行分隔,取最后一个属作为range函数的start,然后把120+1作为range函数的stop #这样循环遍历出需要扫描的IP地址 for i in range(int(options.IP.split('-')[0].split('-')[3]),int(options.IP.split('-')[1])+1): Scan( options.IP.split('.')[0]+'.' +options.IP.split('.')[1]+'.'+options.IP.split('.')[2]+'.'+str(i) ) time.sleep(0.2) else: Scan(options.IP) print("\nScan finished!...\n") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("interrupted by user,killing all threads...")
|
Scan 函数通过调用 ICMP,将构造好的请求包发送到目的地址,并根据目的地址的应答判断目标主机是否存活。存活的 IP 地址会打印出 "xx.xx.xx.xx->Host
is up",对于不存活的主机打印出"xx.xx.xx.xx->Host is down":
1 2 3 4 5 6 7 8 9 10 11 12
| def Scan(ip): ip_id = randint(1,65535) icmp_id = randint(1,65535) icmp_seq = randint(1,65535) packet=IP(dst=ip,ttl=64,id=ip_id)/ICMP(id=icmp_id,seq=icmp_seq)/b'rootkit' result = sr1(packet,timeout=1,verbose=False) if result: for rcv in result: scan_ip = rcv[IP].src print(scan_ip + '--->' 'Host is up') else: print(ip + '--->' 'Host is down')
|
Nmap
此处,也可以导入 Nmap 库函数,实现探测主机存活工具的编写。这里使用 Nmap 函数的 - sn 与 - PE 参数,-PE 表示使用 ICMP,-sn 表示只测试该主机的状态
首先导入代码所应用到的模块:nmap、optparse。nmap 模块用于产生 ICMP 的请求数据包,optparse 用于生成命令行参数
1 2
| import nmap import optparse
|
接着利用 optparse 模块生成命令行参数化形式,对用户输入的参数进行接收和批量处理,最后将处理后的 IP 地址传入 NmapScan 函数
1 2 3 4 5 6 7 8 9 10 11
| if __name__ == '__main__': parser = optparse.Optparse('usage:python %prog -i ip \n\n') parser.add_option('-i','--ip',dest='targetIP',default='192.168.1.1',type='string',help='target ip address') options,args = parser.parse_args() if '-' in options.targetIP: for i in range(int(options.IP.split('-')[0].split('-')[3]), int(options.IP.split('-')[1]) + 1): NmapScan( options.IP.split('.')[0] + '.' + options.IP.split('.')[1] + '.' + options.IP.split('.')[2] + '.' + str(i) ) else: NmapScan(options.IP)
|
接下来就是处理 NmapScan 这个扫描判断函数了,因为要传入 - sn
-PE 参数,所以需要通过调用 nm.scan () 函数来实现,发起 ping 扫描,并打印出扫描之后的结果
1 2 3 4 5 6 7 8 9 10 11 12 13
| def NmapScan(targetIP): #实例化PortScanner对象 nm = nmap.PortScanner() try: #hosts为目标IP地址,arguments为Nmap的扫描参数 #-sn:使用ping进行扫描 #-PE:使用ICMP的echo请求包(-pp:使用timestamp请求包 -PM:netmask请求包) result = nm.scan(host=targetIP,argument='-sn -PE') #对结果进行切片,提取主机状态信息 state = result['scan'][targetIP]['status']['state'] print("[{}] is [{}]".format(targetIP,state)) except Exception as e: pass
|
最后,基于 ICMP 协议的主机探测、判断主机存活是一种很常见的方法,无论是以太网还是互联网都能使用。因此,当网络设备,如路由器、防火墙之类的对 ICMP 采取了限制策略,那就会影响到扫描结果,导致结果不准确。
Nmap 模块实现的大体上的流程与使用 Scapy 差不多,相当于是把接受用户参数调用了 optparse 模块来实现,然后同样的遍历传入 NmapScan,再在 NmapScan 函数里面调用一个 nm.scan () 函数来传入 - sn
-PE 参数,就可以实现 ICMP 协议的实现和主机状态的探测功能