如何使用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抓包如下:

icmp ping python代码 python发送icmp包_python


以上是通过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抓包结果:

icmp ping python代码 python发送icmp包_回送_02

如何获取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