如何使用python3模拟icmp发包
- 模拟icmp协议快速探测内网机器
- ICMP 包原理
- 简单的创建icmp包信息
- 创建raw socket 发送数据包
- 接收数据包,以及解包
- 完整的一次发包以及解析
- 如何获取ttl值
- 模拟icmp批量探测内网存活机器
模拟icmp协议快速探测内网机器
如果给定一个b段,如何快速检测出网段中的存活机器,我们肯定会想到ping命令,但有些机器可能开启防火墙策略,但是一个网段内,网关一般是可以被ping通的,所以icmp协议针对探测存活网段,缩小目标很重要。本文章主要讲解如何使用python3 raw socket 完成icmp协议探测
ICMP 包原理
我们要模拟ICMP发包,首先要了解其结构,一个ICMP报文包括IP报头(至少20字节)、ICMP报头(至少8字节)和ICMP报文(属于ICMP报文的数据部分),数据内容没有限制,我们本次不需要模拟IP头部信息,只要模拟ICMP头部和报文就可以, 通过wireshark抓包如下:
以上是通过ping baidu.com 抓包获取的,左边为请求的request包,右边回应的reply包,我们本次主要是模拟发送request包,以下为icmp request 包结构分析:
1.Type(类型):可以看到request中的type值为8,而reply中的type为0,下面表格中将对应Type的内容。
类型 | 含义 |
0 | 回送应答 |
3 | 目标不可达 |
4 | 原点抑制 |
5 | 重定向或改变路由 |
8 | 回送请求 |
9 | 路由器公告 |
10 | 路由器请求 |
11 | 超时 |
17 | 地址子网请求 |
18 | 地址子网应答 |
2.Code(代码) :占一字节,标识对应ICMP报文的代码。它与类型字段一起共同标识了ICMP报文的详细类型;
3. Checksum(校验和): 对包括ICMP报文数据部分在内的整个ICMP数据报的校验和;
4. Identifier: 为进程号,回送响应消息与回送消息中identifier保持一致,随机即可;
5. Sequence Number:序列号,由主机设定,一般设为由0递增的序列,回送响应消息与回送消息中Sequence Number保持,随机即可;
简单的创建icmp包信息
header = struct.pack('bbHHh', 8, 0, 0, 12345, 0) # 创建头部
data = struct.pack('d', time.time()) # 创建data数据 时间戳随机
icmp 需要校验和
def inCksum(packet):
if len(packet) & 1:
packet = packet + '\0'
words = array.array('h', packet)
sum = 0
for word in words:
sum += (word & 0xffff)
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return (~sum) & 0xffff
创建一个完整的icmp包
header = struct.pack('bbHHh', 8, 0, 0, 12345, 0) # 创建头部
data = struct.pack('d', time.time()) # 创建data数据 时间戳随机
packet = header + data
chkSum = inCksum(packet)
header = struct.pack('bbHHh', 8, 0, chkSum, 12345, 0)
icmp_data = header + data
创建raw socket 发送数据包
Sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
Sock.sendto(icmp_data , (ip, 0))
接收数据包,以及解包
recv_packet, addr = Sock.recvfrom(1024)
type, code, checksum, packet_ID, sequence = struct.unpack("bbHHh", recv_packet[20:28]) # 前20个字节为ip头部信息,后8位为icmp头部信息
完整的一次发包以及解析
import array
import socket
import struct
import time
def inCksum(packet):
if len(packet) & 1:
packet = packet + '\0'
words = array.array('h', packet)
sum = 0
for word in words:
sum += (word & 0xffff)
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return (~sum) & 0xffff
header = struct.pack('bbHHh', 8, 0, 0, 12345, 0) # 创建头部
data = struct.pack('d', time.time()) # 创建data数据 时间戳随机
packet = header + data
chkSum = inCksum(packet)
header = struct.pack('bbHHh', 8, 0, chkSum, 12345, 0)
icmp_data = header + data
Sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
Sock.sendto(icmp_data , ("172.16.0.254", 0))
recv_packet, addr = Sock.recvfrom(1024)
type, code, checksum, packet_ID, sequence = struct.unpack("bbHHh", recv_packet[20:28]) # 前20个字节为ip头部信息,后8位为icmp头部信息
print("头部信息:", type, code, checksum, packet_ID, sequence)
以下为 wireshark抓包结果:
如何获取ttl值
ttl值在回报的ip头部信息中, 占1个字节位置
ttl = struct.unpack("!BBHHHBBHII", recv_packet[:20])[5]
模拟icmp批量探测内网存活机器
不多说了,直接上代码
import socket
import struct
import time
import array
import random
import threading
class IcmpScan:
def __init__(self, ip_list, timeout=3):
self.ips = ip_list
self.__data = struct.pack('d', time.time()) # 需要发送的数据
self.__id = random.randint(1000, 65535)
self.timeout = timeout
self.socket = self.rawSocket
self.finished = False
@property
def rawSocket(self):
try:
Sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
Sock.settimeout(self.timeout)
except:
Sock = self.rawSocket
return Sock
def inCksum(self, packet):
if len(packet) & 1:
packet = packet + '\0'
words = array.array('h', packet)
sum = 0
for word in words:
sum += (word & 0xffff)
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return (~sum) & 0xffff
def create_packet(self):
header = struct.pack('bbHHh', 8, 0, 0, self.__id, 0)
packet = header + self.__data
chkSum = self.inCksum(packet)
header = struct.pack('bbHHh', 8, 0, chkSum, self.__id, 0)
return header + self.__data
def recv_packet(self):
while 1:
try:
recv_packet, addr = self.socket.recvfrom(1024)
type, code, checksum, packet_ID, sequence = struct.unpack("bbHHh", recv_packet[20:28])
if packet_ID == self.__id:
ttl = struct.unpack("!BBHHHBBHII", recv_packet[:20])[5]
print(f"检测到存活设备: {addr[0]} ttl: {ttl}")
except:
if self.finished:
break
def start(self):
random.shuffle(self.ips) # 乱序一下地址
packet = self.create_packet()
time_space = 1 / 1000
t = threading.Thread(target=self.recv_packet,)
t.start()
for i, ip in enumerate(self.ips):
try:
self.socket.sendto(packet, (ip, 0))
time.sleep(time_space)
except socket.timeout:
break
except:
pass
self.finished = True
time.sleep(self.timeout + 1)
self.socket.close()
t.join()
if __name__ == '__main__':
ip_list = ['172.16.0.' + str(i) for i in range(1, 255)]
icmp_scan = IcmpScan(ip_list)
icmp_scan.start()
结果如下:
检测到存活设备: 172.16.0.28 ttl: 128
检测到存活设备: 172.16.0.44 ttl: 64
检测到存活设备: 172.16.0.47 ttl: 64
检测到存活设备: 172.16.0.64 ttl: 64
检测到存活设备: 172.16.0.5 ttl: 64
检测到存活设备: 172.16.0.55 ttl: 128
检测到存活设备: 172.16.0.38 ttl: 64
检测到存活设备: 172.16.0.7 ttl: 128
检测到存活设备: 172.16.0.25 ttl: 64
检测到存活设备: 172.16.0.12 ttl: 64
检测到存活设备: 172.16.0.56 ttl: 128
检测到存活设备: 172.16.0.10 ttl: 64
检测到存活设备: 172.16.0.13 ttl: 64
检测到存活设备: 172.16.0.54 ttl: 128
检测到存活设备: 172.16.0.254 ttl: 255
检测到存活设备: 172.16.0.42 ttl: 64
检测到存活设备: 172.16.0.24 ttl: 64
检测到存活设备: 172.16.0.8 ttl: 64
检测到存活设备: 172.16.0.15 ttl: 64