jianfen's blog

Information is beautiful

ps:个人理解如有错误请指导

ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

通俗的讲就是Ping包,icmp网络层协议所以不存在端口的概念,协议号为1让上层知道.
tcp,udp,ip都是采用校验和的算法,只是校验的数据所有变化.icmp会将头部和数据部分一起进行校验

维基百科关于icmp的详细解释:
https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol]

这里关于校验和做了重要说明:
Checksum
Error checking data, calculated from the ICMP header and data, with value 0 substituted for this field. The Internet Checksum is used, specified in RFC 1071

RFC1071讲述详细的校验过程:
https://tools.ietf.org/html/rfc1071
1的补码 = 反码求和

计算icmp的校验和必须包含header和data

要发送一次icmp就必须包含图中的必选信息:
请输入图片描述

//定义ICMP头部
unsigned char i_type; //8位类型
unsigned char i_code; //8位代码
unsigned short i_cksum; //16位校验和, 从TYPE开始,直到最后一位用户数据,如果为字节数为奇数则补充一位
unsigned short i_id ; //识别号(一般用进程号作为识别号), 用于匹配ECHO和ECHO REPLY包
short i_seq ; //报文序列号, 用于标记ECHO报文顺序 可置0
unsigned int timestamp; //时间戳 选项数据部分 可以无

python格式化对照表
请输入图片描述

现在来讲一讲实现的主要思路:

想要发送ping包首先要构造出icmp的完整包,关键就是校验和的计算方法

1.首先icmp的校验和需要头部和数据部分相加再进行校验和的运算

2.头部和数据部分相加必须为偶数,奇数补0就不用关心他了,因为我们只想实现发包,算法不过多研究

3.利用struct格式化为网络字节(类型[b],代码[b],校验和[H],识别号[H],序列号[h])第一次校验时,校验和为0,识别号一直都是进程号(os.getpid()),这就是头部的8个字节,头部和数据相加需要为偶数字节,那数据最小可以是[h]
struct.pack('bbHHh',8,0,0,self.__id,0)

4.def __doCksum(self,packet)
//校验和求法:
//把数据报看成16比特整数序列(按网络字节顺序)
//对每个整数分别计算其二进制反码,然后相加
//再对结果计算一次二进制反码而求得
一般程序为了计算方便会先相加,再加上进位,最后进行取反

5.再把cksum值带入原有包struct.pack('bbHHh',8,0,cksum,self.__id,0)

接下来需要用python实现发送一次icmp包的过程:
1.需要用到socket库
2.格式化数据的库struct,array
3.生成进程id的库

#coding=utf-8

import os
import socket
import struct
import array

class Pinger(object):
    def __init__(self,timeout=3):
        self.timeout = timeout
        self.__id = os.getpid()
        self.__data = struct.pack('h',1)#h代表2个字节与头部8个字节组成偶数可进行最短校验

    @property
    def __icmpSocket(self):#返回一个可以利用的icmp原对象,当做属性使用
        icmp = socket.getprotobyname("icmp")#指定服务
        sock = socket.socket(socket.AF_INET,socket.SOCK_RAW,icmp)#socket.SOCK_RAW原生包
        return sock


    def __doCksum(self,packet):#校验和运算
        words = array.array('h',packet)#将包分割成2个字节为一组的网络序列
        sum = 0
        for word in words:
            sum += (word & 0xffff)#每2个字节相加
        sum = (sum >> 16) + (sum & 0xffff)#因为sum有可能溢出16位所以将最高位和低位sum相加重复二遍
        sum += (sum >> 16) # 为什么这里的sum不需要再 & 0xffff 因为这里的sum已经是16位的不会溢出,可以手动测试超过65535的十进制数字就溢出了
        return (~sum) & 0xffff #最后取反返回完成校验

    @property
    def __icmpPacket(self):#icmp包的构造
        header = struct.pack('bbHHh',8,0,0,self.__id,0)
        packet = header + self.__data
        cksum = self.__doCksum(packet)
        header = struct.pack('bbHHh',8,0,cksum,self.__id,0)#将校验带入原有包,这里才组成头部,数据部分只是用来做校验所以返回的时候需要返回头部和数据相加
        return header + self.__data 


    def sendPing(self,target_host):
        
        try:
            socket.gethostbyname(target_host)

            sock = self.__icmpSocket
            sock.settimeout(self.timeout)

            packet = self.__icmpPacket

            sock.sendto(packet,(target_host,1))#发送icmp包

            ac_ip = sock.recvfrom(1024)[1][0]
            print '[+] %s active'%(ac_ip)
        except Exception,e:
            sock.close()

s = Pinger()


s.sendPing('192.168.1.103')

wireshark抓包查看

requests:
请输入图片描述

reply:
请输入图片描述


添加新评论 »

在这里输入你的评论...

勿忘初心,方得始终.