【问题标题】:How is an ICMP packet constructed in pythonpython中如何构造一个ICMP包
【发布时间】:2016-01-05 15:08:12
【问题描述】:

为了学习,我目前正在尝试创建一个简单的 python porgram 来将 ICMP ping 数据包发送到某些设备。首先,我查看了 Python 模块 Pyping 的源代码:https://github.com/Akhavi/pyping/blob/master/pyping/core.py

我试图了解发送和构建数据包时发生的所有事情,但是我设法卡在了代码的一部分上,似乎无法确切地弄清楚它的功能和用途是什么。我一直在研究 ICMP 数据包,我知道它们包含 类型代码校验和和数据 现在让我感到困惑的代码是:

    self.own_id = os.getpid() & 0xFFFF

    header = struct.pack(
        "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
    )

    padBytes = []
    startVal = 0x42
    for i in range(startVal, startVal + (self.packet_size)):
        padBytes += [(i & 0xff)]  # Keep chars in the 0-255 range
    data = bytes(padBytes)

我的问题是:

  1. 在header中加上self.own_id和self.seq_number有什么用?
  2. for 循环中计算的是什么,为什么它的特定起始值是 0x42?

我是网络新手,非常感谢任何帮助。

【问题讨论】:

    标签: python networking byte icmp


    【解决方案1】:

    ICMP Echo Request 报文说明

    ICMP Echo Request PDU 看起来像这样:

        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |     Type(8)   |     Code(0)   |          Checksum             |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           Identifier          |        Sequence Number        |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                             Payload                           |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    以下是上面 wiki 链接中各个字段的描述:

    客户端可以使用标识符和序列号将回复与引起回复的请求相匹配。

    在实践中,大多数 Linux 系统为每个 ping 进程使用一个唯一标识符,并且在该进程中序列号是一个递增的数字。 Windows 使用固定标识符(随 Windows 版本而异)和仅在启动时重置的序列号。

    pyping代码说明

    标题生成

    查看send_one_ping 的完整函数体,这是您的代码的来源。我将用一些信息对其进行注释:

    def send_one_ping(self, current_socket):
        """
        Send one ICMP ECHO_REQUEST
        """
        # Header is type (8), code (8), checksum (16), id (16), sequence (16)
    
        # Annotation: the Type is 8 bits, the code is 8 bits, the
        # header checksum is 16 bits
    
        # Additional Header Information is 32-bits (identifier and sequence number)
    
        # After that is Payload, which is of arbitrary length.
    

    所以这一行

        header = struct.pack(
            "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
        )
    

    这一行使用struct 和布局!BBHHH 创建数据包头,这意味着:

    • B - 无符号字符(8 位)
    • B - 无符号字符(8 位)
    • H - 无符号短(16 位)
    • H - 无符号短(16 位)
    • H - 无符号短(16 位)

    所以标题看起来像这样:

        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |     ICMP_ECHO  |     0        |          checksum             |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           self.own_id         |        self.seq_number        |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    注意这一点:

    • self.own_id 设置发送此数据的应用程序的标识符。对于此代码,它仅使用程序的程序标识符号。
    • self.seq_number 设置序列号。如果您要连续发送多个,这可以帮助您识别这是哪个 ICMP 请求数据包。它可以帮助您执行计算 ICMP 数据包丢失等操作。

    客户端可以使用标识符和序列号字段组合来匹配回显回复和回显请求。

    有效载荷生成

    现在让我们转到有效负载部分。有效载荷的长度是任意的,但 Ping 类的这段代码默认取自 55 bytes 的总数据包有效载荷大小。

    所以下面的部分只是创建了一堆任意字节来填充到有效负载部分。

    padBytes = []
    startVal = 0x42
    
    # Annotation: 0x42 = 66 decimal
    # This loop would go from [66, 66 + packet_size],
    # which in default pyping means [66, 121)
    for i in range(startVal, startVal + (self.packet_size)):
        padBytes += [(i & 0xff)]  # Keep chars in the 0-255 range
    data = bytes(padBytes)
    

    最后,byte(padBytes) 实际上是这样的:

    >> bytes(padBytes)
    b'BCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwx'
    

    为什么选择0x42

    据我所知,0x42 作为有效载荷标识符没有实际意义,所以这似乎相当随意。这里的有效载荷实际上毫无意义。从 Payload Generation 部分可以看出,它只是生成一个连续的序列,实际上并没有任何意义。如果他们愿意,他们本可以决定用0x42 字节填充整个数据包有效负载。

    【讨论】:

    • 非常感谢!非常好和彻底的解释,这肯定会帮助我,干杯!
    • @SjoerdvdBelt 不客气!我做了一些编辑,因为我意识到我的旁注不正确(我误读了描述,说 code 的值是 8,但他们只是详细说明了它的内存使用情况),我添加了一些关于 @987654348 的更多细节@generation 以及结果的样子。另外添加了一些指向 Wiki 文章的链接,我从中获得了原始有效负载布局描述。
    • 使用'42'是程序员的习语。它是使用 'foo' 给出变量名称或字符串内容示例的数值等价物。即,作者给出的示例中的实际单词是任意的,如果您尝试从示例中实现自己的工作代码,最终将是您的选择。就“42”而言,它可以追溯到 D. Adams 的故事“银河系漫游指南”。
    【解决方案2】:

    使用 scapy http://www.secdev.org/projects/scapy/

    Scapy 是用 python 编写的数据包操作框架。 您可以伪造很多类型的数据包(http、tcp、ip、udp、icmp 等...)

    【讨论】:

      【解决方案3】:

      ICMP 回显请求和回显回复通常称为 ping。

      ID 和序列号允许将回复与请求相匹配。它们是必要的,因为 ICMP 协议没有 TCP 和 UDP 那样的源端口和目标端口,只有源 IP 地址和目标 IP 地址。多个进程可以 ping 同一个主机,并且必须将回复传递给正确的进程。出于这个原因,每个回复都包含从相应请求中复制的数据。 ID 标识发送请求的进程。序列号有助于将回复与流程中的请求相匹配。这是计算 RTT(往返时间)和检测未应答的 ping 所必需的。


      在循环中计算的数据是有效载荷,它也从字面上从请求复制到回复。有效负载是可选的,ping 实现可以根据需要使用它。


      为什么是 0x42?我猜作者可能是道格拉斯·亚当斯的粉丝。

      【讨论】:

        猜你喜欢
        • 2018-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-20
        • 1970-01-01
        相关资源
        最近更新 更多