Skip to content

Commit 47a659f

Browse files
committed
Land rapid7#8185, Convert ntp modules to bindata
2 parents 3293a8f + 652e237 commit 47a659f

File tree

11 files changed

+83
-76
lines changed

11 files changed

+83
-76
lines changed

lib/rex/proto/ntp/modes.rb

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: binary -*-
22

3-
require 'bit-struct'
3+
require 'bindata'
44

55
module Rex
66
module Proto
@@ -16,24 +16,25 @@ module NTP
1616
# pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf
1717
# http://tools.ietf.org/html/rfc1305#appendix-D
1818
# http://tools.ietf.org/html/rfc5905#page-19
19-
class NTPGeneric < BitStruct
19+
class NTPGeneric < BinData::Record
2020
# 0 1 2 3
2121
# 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
2222
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2323
# |LI | VN | mode| Stratum | Poll | Precision |
2424
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25-
unsigned :li, 2, default: 0
26-
unsigned :version, 3, default: 0
27-
unsigned :mode, 3, default: 0
28-
unsigned :stratum, 8, default: 0
29-
unsigned :poll, 8, default: 0
30-
unsigned :precision, 8, default: 0
31-
rest :payload
25+
endian :big
26+
bit2 :li
27+
bit3 :version
28+
bit3 :mode
29+
uint8 :stratum
30+
uint8 :poll
31+
uint8 :precision
32+
rest :payload
3233
end
3334

3435
# An NTP control message. Control messages are only specified for NTP
3536
# versions 2-4, but this is a fuzzer so why not try them all...
36-
class NTPControl < BitStruct
37+
class NTPControl < BinData::Record
3738
# 0 1 2 3
3839
# 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
3940
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -43,70 +44,73 @@ class NTPControl < BitStruct
4344
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4445
# | offset | count |
4546
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
46-
unsigned :reserved, 2, default: 0
47-
unsigned :version, 3, default: 0
48-
unsigned :mode, 3, default: 6
49-
unsigned :response, 1, default: 0
50-
unsigned :error, 1, default: 0
51-
unsigned :more, 1, default: 0
52-
unsigned :operation, 5, default: 0
53-
unsigned :sequence, 16, default: 0
54-
unsigned :status, 16, default: 0
55-
unsigned :association_id, 16, default: 0
47+
endian :big
48+
bit2 :reserved
49+
bit3 :version
50+
bit3 :mode, initial_value: 6
51+
bit1 :response
52+
bit1 :error
53+
bit1 :more
54+
bit5 :operation
55+
uint16 :sequence
56+
uint16 :status
57+
uint16 :association_id
5658
# TODO: there *must* be bugs in the handling of these next two fields!
57-
unsigned :payload_offset, 16, default: 0
58-
unsigned :payload_size, 16, default: 0
59-
rest :payload
59+
uint16 :payload_offset
60+
uint16 :payload_size
61+
rest :payload
6062
end
6163

6264
# An NTP "private" message. Private messages are only specified for NTP
6365
# versions 2-4, but this is a fuzzer so why not try them all...
64-
class NTPPrivate < BitStruct
66+
class NTPPrivate < BinData::Record
6567
# 0 1 2 3
6668
# 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
6769
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6870
# |R M| VN | 7 |A| Sequence | Implementation| Req code |
6971
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7072
# | err | Number of data items | MBZ | Size of data item |
7173
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72-
unsigned :response, 1, default: 0
73-
unsigned :more, 1, default: 0
74-
unsigned :version, 3, default: 0
75-
unsigned :mode, 3, default: 7
76-
unsigned :auth, 1, default: 0
77-
unsigned :sequence, 7, default: 0
78-
unsigned :implementation, 8, default: 0
79-
unsigned :request_code, 8, default: 0
80-
unsigned :error, 4, default: 0
81-
unsigned :record_count, 12, default: 0
82-
unsigned :mbz, 4, default: 0
83-
unsigned :record_size, 12, default: 0
84-
rest :payload
74+
endian :big
75+
bit1 :response
76+
bit1 :more
77+
bit3 :version
78+
bit3 :mode, initial_value: 7
79+
bit1 :auth
80+
bit7 :sequence
81+
uint8 :implementation
82+
uint8 :request_code
83+
bit4 :error
84+
bit12 :record_count
85+
bit4 :mbz
86+
bit12 :record_size
87+
rest :payload
8588

8689
def records
8790
records = []
8891
1.upto(record_count) do |record_num|
89-
records << payload[record_size*(record_num-1), record_size]
92+
records << payload[record_size * (record_num - 1), record_size]
9093
end
9194
records
9295
end
9396
end
9497

95-
class NTPSymmetric < BitStruct
96-
unsigned :li, 2, default: 0
97-
unsigned :version, 3, default: 3
98-
unsigned :mode, 3, default: 0
99-
unsigned :stratum, 8, default: 0
100-
unsigned :poll, 8, default: 0
101-
unsigned :precision, 8, default: 0
102-
unsigned :root_delay, 32, default: 0
103-
unsigned :root_dispersion, 32, default: 0
104-
unsigned :reference_id, 32, default: 0
105-
unsigned :reference_timestamp, 64, default: 0
106-
unsigned :origin_timestamp, 64, default: 0
107-
unsigned :receive_timestamp, 64, default: 0
108-
unsigned :transmit_timestamp, 64, default: 0
109-
rest :payload
98+
class NTPSymmetric < BinData::Record
99+
endian :big
100+
bit2 :li
101+
bit3 :version, initial_value: 3
102+
bit3 :mode
103+
uint8 :stratum
104+
uint8 :poll
105+
uint8 :precision
106+
uint32 :root_delay
107+
uint32 :root_dispersion
108+
uint32 :reference_id
109+
uint64 :reference_timestamp
110+
uint64 :origin_timestamp
111+
uint64 :receive_timestamp
112+
uint64 :transmit_timestamp
113+
rest :payload
110114
end
111115

112116
def self.ntp_control(version, operation, payload = nil)
@@ -139,7 +143,7 @@ def self.ntp_generic(version, mode)
139143

140144
# Parses the given message and provides a description about the NTP message inside
141145
def self.describe(message)
142-
ntp = NTPGeneric.new(message)
146+
ntp = NTPGeneric.new.read(message)
143147
"#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply"
144148
end
145149
end

modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def fuzz_private(host)
115115
print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)")
116116
@mode_7_implementations.each do |implementation|
117117
@mode_7_request_codes.each do |request_code|
118-
request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\x00" * 188)
118+
request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\0" * 188)
119119
what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message"
120120
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
121121
responses = probe(host, datastore['RPORT'].to_i, request)
@@ -176,6 +176,7 @@ def fuzz_version_mode(host, short)
176176

177177
# Sends +message+ to +host+ on UDP port +port+, returning all replies
178178
def probe(host, port, message)
179+
message = message.to_binary_s if message.respond_to?('to_binary_s')
179180
replies = []
180181
begin
181182
udp_sock.sendto(message, host, port, 0)
@@ -193,14 +194,15 @@ def probe(host, port, message)
193194
def handle_responses(host, request, responses, what)
194195
problems = []
195196
descriptions = []
197+
request = request.to_binary_s if request.respond_to?('to_binary_s')
196198
responses.select! { |r| r[1] }
197199
return if responses.empty?
198200
responses.each do |response|
199201
data = response[0]
200202
descriptions << Rex::Proto::NTP.describe(data)
201203
problems << 'large response' if request.size < data.size
202-
ntp_req = Rex::Proto::NTP::NTPGeneric.new(request)
203-
ntp_resp = Rex::Proto::NTP::NTPGeneric.new(data)
204+
ntp_req = Rex::Proto::NTP::NTPGeneric.new.read(request)
205+
ntp_resp = Rex::Proto::NTP::NTPGeneric.new.read(data)
204206
problems << 'version mismatch' if ntp_req.version != ntp_resp.version
205207
end
206208

modules/auxiliary/scanner/ntp/ntp_monlist.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ def initialize
4747
# Called for each response packet
4848
def scanner_process(data, shost, sport)
4949
@results[shost] ||= { messages: [], peers: [] }
50-
@results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data)
50+
@results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
5151
@results[shost][:peers] << extract_peer_tuples(data)
5252
end
5353

5454
# Called before the scan block
5555
def scanner_prescan(batch)
5656
@results = {}
5757
@aliases = {}
58-
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42)
58+
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42, "\0" * 40).to_binary_s
5959
end
6060

6161
# Called after the scan block

modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ def check
8181
probe = build_crypto_nak(canary_timestamp)
8282
udp_sock.put(probe)
8383

84-
expected_length = probe.length - probe.payload.length
84+
expected_length = probe.to_binary_s.length - probe.payload.length
8585
response = udp_sock.timed_read(expected_length)
8686
disconnect_udp
8787
if response.length == expected_length
88-
ntp_symmetric = Rex::Proto::NTP::NTPSymmetric.new(response)
88+
ntp_symmetric = Rex::Proto::NTP::NTPSymmetric.new.read(response)
8989
if ntp_symmetric.mode == 2 && ntp_symmetric.origin_timestamp == canary_timestamp
9090
vprint_good("#{rhost}:#{rport} - NTP - VULNERABLE: Accepted a NTP symmetric active association")
9191
report_vuln(

modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ def initialize
3636
# Called before the scan block
3737
def scanner_prescan(batch)
3838
@results = {}
39-
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 0)
39+
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 0).to_binary_s
4040
end
4141

4242
# Called for each response packet
4343
def scanner_process(data, shost, sport)
4444
@results[shost] ||= []
45-
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
45+
@results[shost] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
4646
end
4747

4848
# Called after the scan block

modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ def initialize
3636
# Called for each response packet
3737
def scanner_process(data, shost, sport)
3838
@results[shost] ||= []
39-
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
39+
@results[shost] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
4040
end
4141

4242
# Called before the scan block
4343
def scanner_prescan(batch)
4444
@results = {}
45-
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 1)
45+
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 1).to_binary_s
4646
end
4747

4848
# Called after the scan block

modules/auxiliary/scanner/ntp/ntp_readvar.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def initialize(info = {})
3535

3636
def scanner_process(data, shost, _sport)
3737
@results[shost] ||= []
38-
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
38+
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
3939
end
4040

4141
def scan_host(ip)

modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def initialize
3737
# Called for each response packet
3838
def scanner_process(data, shost, sport)
3939
@results[shost] ||= []
40-
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
40+
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
4141
end
4242

4343
# Called before the scan block

modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,14 @@ def initialize
3838
# Called for each response packet
3939
def scanner_process(data, shost, sport)
4040
@results[shost] ||= []
41-
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
41+
privmsg = Rex::Proto::NTP::NTPPrivate.new.read(data)
42+
@results[shost] << privmsg.to_binary_s
4243
end
4344

4445
# Called before the scan block
4546
def scanner_prescan(batch)
4647
@results = {}
47-
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 16)
48+
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 16).to_binary_s
4849
end
4950

5051
# Called after the scan block

modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def initialize
3636
# Called for each response packet
3737
def scanner_process(data, shost, sport)
3838
@results[shost] ||= []
39-
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
39+
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
4040
end
4141

4242
# Called before the scan block

0 commit comments

Comments
 (0)