利用python发送icmp包详解(ping)

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的库

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
53
54
55
56
57
58
59
#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位的不会溢出,
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:

如无特殊说明,均为原创内容。转载请注明出处!