|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +require 'bindata' |
| 4 | +require 'bigdecimal' |
| 5 | +require 'bigdecimal/util' |
| 6 | + |
| 7 | +module Rex |
| 8 | +module Proto |
| 9 | +module NTP::Header |
| 10 | + |
| 11 | + class NTPShort < BinData::Primitive |
| 12 | + # see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 |
| 13 | + endian :big |
| 14 | + |
| 15 | + uint16 :seconds |
| 16 | + uint16 :fraction |
| 17 | + |
| 18 | + def set(value) |
| 19 | + value = value.to_d |
| 20 | + seconds = value.floor |
| 21 | + self.seconds = seconds |
| 22 | + self.fraction = ((value - seconds) * BigDecimal(2**16)).round |
| 23 | + end |
| 24 | + |
| 25 | + def get |
| 26 | + BigDecimal(seconds.value) + (BigDecimal(fraction.value) / BigDecimal(2**16)) |
| 27 | + end |
| 28 | + end |
| 29 | + |
| 30 | + class NTPTimestamp < BinData::Primitive |
| 31 | + UNIX_EPOCH = Time.utc(1900, 1, 1) |
| 32 | + # see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 |
| 33 | + endian :big |
| 34 | + |
| 35 | + uint32 :seconds |
| 36 | + uint32 :fraction |
| 37 | + |
| 38 | + def get |
| 39 | + return nil if seconds == 0 && fraction == 0 |
| 40 | + |
| 41 | + time_in_seconds = seconds + BigDecimal(fraction.to_s) / BigDecimal((2**32).to_s) |
| 42 | + (UNIX_EPOCH + time_in_seconds).utc |
| 43 | + end |
| 44 | + |
| 45 | + def set(time) |
| 46 | + if time.nil? |
| 47 | + seconds = fraction = 0 |
| 48 | + else |
| 49 | + seconds_since_epoch = time.to_r - UNIX_EPOCH.to_r |
| 50 | + seconds = seconds_since_epoch.to_i |
| 51 | + fraction = ((seconds_since_epoch - seconds) * (2**32)).to_i |
| 52 | + end |
| 53 | + |
| 54 | + self.seconds = seconds |
| 55 | + self.fraction = fraction |
| 56 | + end |
| 57 | + end |
| 58 | + |
| 59 | + class NTPExtension < BinData::Record |
| 60 | + endian :big |
| 61 | + |
| 62 | + uint16 :ext_type |
| 63 | + uint16 :ext_length |
| 64 | + uint8_array :ext_value, initial_length: :ext_length |
| 65 | + end |
| 66 | + |
| 67 | + # A unified structure capable of representing NTP versions 1-4 |
| 68 | + class NTPHeader < BinData::Record |
| 69 | + # see: https://datatracker.ietf.org/doc/html/rfc958 (NTP v0 - unsupported) |
| 70 | + # see: https://datatracker.ietf.org/doc/html/rfc1059 (NTP v1) |
| 71 | + # see: https://datatracker.ietf.org/doc/html/rfc1119 (NTP v2) |
| 72 | + # see: https://datatracker.ietf.org/doc/html/rfc1305 (NTP v3) |
| 73 | + # see: https://datatracker.ietf.org/doc/html/rfc5905 (NTP v4) |
| 74 | + endian :big |
| 75 | + hide :bytes_remaining_0, :bytes_remaining_1 |
| 76 | + |
| 77 | + bit2 :leap_indicator |
| 78 | + bit3 :version_number, initial_value: 4, assert: -> { version_number.between?(1, 4) } |
| 79 | + bit3 :mode, onlyif: -> { version_number > 1 } |
| 80 | + resume_byte_alignment |
| 81 | + uint8 :stratum |
| 82 | + int8 :poll |
| 83 | + int8 :precision |
| 84 | + ntp_short :root_delay |
| 85 | + ntp_short :root_dispersion |
| 86 | + string :reference_id, length: 4, trim_padding: true |
| 87 | + ntp_timestamp :reference_timestamp |
| 88 | + ntp_timestamp :origin_timestamp |
| 89 | + ntp_timestamp :receive_timestamp |
| 90 | + ntp_timestamp :transmit_timestamp |
| 91 | + count_bytes_remaining :bytes_remaining_0 |
| 92 | + buffer :extensions, length: -> { bytes_remaining_0 - 20 }, onlyif: :has_extensions? do |
| 93 | + array :extensions, type: :ntp_extension, read_until: :eof |
| 94 | + end |
| 95 | + count_bytes_remaining :bytes_remaining_1 |
| 96 | + uint32 :key_identifier, onlyif: :has_key_identifier? |
| 97 | + uint8_array :message_digest, initial_length: OpenSSL::Digest::MD5.new.digest_length, onlyif: :has_message_digest? |
| 98 | + |
| 99 | + private |
| 100 | + |
| 101 | + def has_extensions? |
| 102 | + # -20 for the length of the key identifier and message digest which are required when extensions are present |
| 103 | + bytes_remaining_0 - 20 > 0 && version_number > 3 |
| 104 | + end |
| 105 | + |
| 106 | + def has_key_identifier? |
| 107 | + bytes_remaining_1 > 0 || !key_identifier.clear? |
| 108 | + end |
| 109 | + |
| 110 | + def has_message_digest? |
| 111 | + bytes_remaining_1 > 4 || !message_digest.clear? |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | +end |
| 116 | +end |
| 117 | +end |
0 commit comments