scapy icon indicating copy to clipboard operation
scapy copied to clipboard

NTP parsing of status response is incorrect

Open nrathaus opened this issue 8 months ago • 1 comments

Brief description

Parsing of NTP packets with Control response is incomplete/incorrect

Scapy version

github version

Python version

3.11

Operating system

Ubuntu 24

Additional environment information

No response

How to reproduce

The following packet parsing (of the NTP layer)

raw = bytes.fromhex("""
    16 81 00 0f 00 14 00 00 00 00 00 38 45 74 00 11
    45 73 00 11 45 72 00 11 45 71 00 11 45 70 36 1a
    45 6f 34 14 45 6e 33 14 45 6d 34 14 45 6c 34 1a
    45 6b 34 14 45 6a 88 11 45 69 88 11 45 68 88 11
    45 67 88 11
""".replace(" ", "").replace("\n", ""))
    
ntp_control = packet = (
        scapy.layers.l2.Ether()
        / scapy.layers.inet.IP()
        / scapy.layers.inet.UDP(dport=123)
        / scapy.layers.ntp.NTP(raw)
    )

Should return multiple items (56 to be exact), instead only one item is returned and the rest is shown as raw:

###[ Control message ]###
  zeros     = 0
  version   = 2
  mode      = NTP control message
  response  = 1
  err       = 0
  more      = 0
  op_code   = CTL_OP_READSTAT
  sequence  = 15
  \status_word\
   |###[ system status ]###
   |  leap_indicator= no warning
   |  clock_source= unspecified or unknown
   |  system_event_counter= 1
   |  system_event_code= system new synchronization source or stratum (sys.peer or sys.stratum change)
  association_id= 0
  offset    = 0
  count     = 56
  \data      \
   |###[ data / peer status ]###
   |  association_id= 17780
   |  \peer_status\
   |   |###[ peer status ]###
   |   |  configured= 0
   |   |  auth_enabled= 0
   |   |  authentic = 0
   |   |  reachability= 0
   |   |  reserved  = 0
   |   |  peer_sel  = rejected
   |   |  peer_event_counter= 1
   |   |  peer_event_code= peer IP error
   |###[ Raw ]###
   |     load      = b'Es\x00\x11Er\x00\x11Eq\x00\x11Ep6\x1aEo4\x14En3\x14Em4\x14El4\x1aEk4\x14Ej\x88\x11Ei\x88\x11Eh\x88\x11Eg\x88\x11'
  authenticator= b''

Actual result

No response

Expected result

No response

Related resources

No response

nrathaus avatar Apr 09 '25 11:04 nrathaus

A potential fix in ntp.py:

class NTPControlDataPacketLenField(PacketLenField):

    """
    PacketField handling the "data" field of NTP control messages.
    """

    def m2i(self, pkt, m):
        ret = None
        if not m:
            return ret

        # op_code == CTL_OP_READSTAT
        if pkt.op_code == 1:
            if pkt.association_id == 0:
                # Data contains association ID and peer status
                ret = Packet()
                ntp_peer_status_data_count = int(pkt.count / 4)
                if len(m) < pkt.count:
                    # Data too small
                    raise

                for idx in range(ntp_peer_status_data_count):
                    ret.add_payload(NTPPeerStatusDataPacket(m[idx*4: idx*4 + 4]))
            else:
                ret = conf.raw_layer(m)
        else:
            ret = conf.raw_layer(m)

        return ret

Caveats:

  1. I am not sure if this is the way to create additional layers of NTPPeerStatusDataPacket
  2. I am not sure if raise is the right way to do things if something is bad going to happen (insufficient data)
  3. I a, not sure if m[idx...] is the way to take sub portions of data

nrathaus avatar Apr 09 '25 11:04 nrathaus

Thanks for taking care of this

nrathaus avatar Jul 17 '25 04:07 nrathaus

Thanks for raising this issue !

gpotter2 avatar Jul 17 '25 08:07 gpotter2