4
4
##
5
5
6
6
require 'msf/core'
7
- require 'bit-struct '
7
+ require 'rex/proto/ntp '
8
8
require 'securerandom'
9
9
10
10
class Metasploit3 < Msf ::Auxiliary
@@ -13,12 +13,6 @@ class Metasploit3 < Msf::Auxiliary
13
13
include Msf ::Exploit ::Remote ::Udp
14
14
include Msf ::Auxiliary ::Scanner
15
15
16
- NTP_SUPPORTED_VERSIONS = ( 0 ..7 ) . to_a
17
- NTP_SUPPORTED_MODES = ( 0 ..7 ) . to_a
18
- NTP_SUPPORTED_MODE_6_OPERATIONS = ( 0 ..31 ) . to_a
19
- NTP_SUPPORTED_MODE_7_IMPLEMENTATIONS = ( 0 ..255 ) . to_a
20
- NTP_SUPPORTED_MODE_7_REQUEST_CODES = ( 0 ..255 ) . to_a
21
-
22
16
def initialize
23
17
super (
24
18
'Name' => 'NTP Protocol Fuzzer' ,
@@ -52,142 +46,44 @@ def initialize
52
46
register_options (
53
47
[
54
48
Opt ::RPORT ( 123 ) ,
55
- OptString . new ( 'VERSIONS' , [ true , 'Versions to fuzz' , [ 3 , 2 , 4 ] ] ) ,
56
- OptString . new ( 'MODES' , [ true , 'Modes to fuzz' , NTP_SUPPORTED_MODES ] ) ,
57
- OptString . new ( 'MODE_6_OPERATIONS' , [ true , 'Mode 6 operations to fuzz' , NTP_SUPPORTED_MODE_6_OPERATIONS ] ) ,
58
- OptString . new ( 'MODE_7_IMPLEMENTATIONS' , [ true , 'Mode 7 implementations to fuzz' , [ 3 , 2 , 0 ] ] ) ,
59
- OptString . new ( 'MODE_7_REQUEST_CODES' , [ true , 'Mode 7 request codes to fuzz' , NTP_SUPPORTED_MODE_7_REQUEST_CODES ] ) ,
60
49
OptInt . new ( 'SLEEP' , [ true , 'Sleep for this many ms between requests' , 0 ] ) ,
61
- OptInt . new ( 'WAIT' , [ true , 'Wait this many ms for responses' , 500 ] )
50
+ OptInt . new ( 'WAIT' , [ true , 'Wait this many ms for responses' , 250 ] )
62
51
] , self . class )
63
- end
64
52
65
- # A very generic NTP message
66
- #
67
- # Uses the common/similar parts from versions 1-4 and considers everything
68
- # after to be just one big field. For the particulars on the different versions,
69
- # see:
70
- # http://tools.ietf.org/html/rfc958#appendix-B
71
- # http://tools.ietf.org/html/rfc1059#appendix-B
72
- # pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf
73
- # http://tools.ietf.org/html/rfc1305#appendix-D
74
- # http://tools.ietf.org/html/rfc5905#page-19
75
- class NTPGeneric < BitStruct
76
- # 0 1 2 3
77
- # 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
78
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79
- # |LI | VN | mode| Stratum | Poll | Precision |
80
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
81
- unsigned :li , 2 , default : 0
82
- unsigned :version , 3 , default : 0
83
- unsigned :mode , 3 , default : 0
84
- unsigned :stratum , 8 , default : 0
85
- unsigned :poll , 8 , default : 0
86
- unsigned :precision , 8 , default : 0
87
- char :payload , 352
88
- end
89
-
90
- # An NTP control message. Control messages are only specified for NTP
91
- # versions 2-4, but this is a fuzzer so why not try them all...
92
- class NTPControl < BitStruct
93
- # 0 1 2 3
94
- # 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
95
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96
- # |00 | VN | 6 |R E M| op | Sequence |
97
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
98
- # | status | association id |
99
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
100
- # | offset | count |
101
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
102
- unsigned :reserved , 2 , default : 0
103
- unsigned :version , 3 , default : 0
104
- unsigned :mode , 3 , default : 6
105
- unsigned :response , 1 , default : 0
106
- unsigned :error , 1 , default : 0
107
- unsigned :more , 1 , default : 0
108
- unsigned :operation , 5 , default : 0
109
- unsigned :sequence , 16 , default : 0
110
- unsigned :status , 16 , default : 0
111
- unsigned :association_id , 16 , default : 0
112
- # TODO: there *must* be bugs in the handling of these next two fields!
113
- unsigned :payload_offset , 16 , default : 0
114
- unsigned :payload_size , 16 , default : 0
115
- rest :payload
53
+ register_advanced_options (
54
+ [
55
+ OptString . new ( 'VERSIONS' , [ false , 'Specific versions to fuzz (csv)' , nil ] ) ,
56
+ OptString . new ( 'MODES' , [ false , 'Modes to fuzz (csv)' , nil ] ) ,
57
+ OptString . new ( 'MODE_6_OPERATIONS' , [ false , 'Mode 6 operations to fuzz (csv)' , nil ] ) ,
58
+ OptString . new ( 'MODE_7_IMPLEMENTATIONS' , [ false , 'Mode 7 implementations to fuzz (csv)' , nil ] ) ,
59
+ OptString . new ( 'MODE_7_REQUEST_CODES' , [ false , 'Mode 7 request codes to fuzz (csv)' , nil ] )
60
+ ] , self . class )
116
61
end
117
62
118
- # An NTP "private" message. Private messages are only specified for NTP
119
- # versions 2-4, but this is a fuzzer so why not try them all...
120
- class NTPPrivate < BitStruct
121
- # 0 1 2 3
122
- # 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
123
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
124
- # |00 | VN | 7 |A| Sequence |
125
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
126
- # | Implementation| request code |
127
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
128
- unsigned :reserved , 2 , default : 0
129
- unsigned :version , 3 , default : 0
130
- unsigned :mode , 3 , default : 7
131
- unsigned :auth , 1 , default : 0
132
- unsigned :sequence , 7 , default : 0
133
- unsigned :implementation , 8 , default : 0
134
- unsigned :request_code , 8 , default : 0
135
- rest :payload
63
+ def sleep_time
64
+ datastore [ 'SLEEP' ] / 1000.0
136
65
end
137
66
138
- def build_ntp_control ( version , operation , payload = nil )
139
- n = NTPControl . new
140
- n . version = version
141
- n . operation = operation
142
- if payload
143
- n . payload_offset = 0
144
- n . payload_size = payload . size
145
- n . payload = payload
67
+ def check_and_set ( setting )
68
+ thing = setting . upcase
69
+ const_name = thing . to_sym
70
+ var_name = thing . downcase
71
+ if datastore . key? ( thing )
72
+ instance_variable_set ( "@#{ var_name } " , datastore [ thing ] . split ( /[^\d ]/ ) . select { |v | !v . empty? } . map { |v | v . to_i } )
73
+ unsupported_things = instance_variable_get ( "@#{ var_name } " ) - Rex ::Proto ::NTP . const_get ( const_name )
74
+ fail "Unsupported #{ thing } : #{ unsupported_things } " unless unsupported_things . empty?
75
+ else
76
+ instance_variable_set ( "@#{ var_name } " , Rex ::Proto ::NTP ::const_get ( const_name ) )
146
77
end
147
- n . to_s
148
- end
149
-
150
- def build_ntp_private ( version , implementation , request_code , payload = nil )
151
- n = NTPPrivate . new
152
- n . version = version
153
- n . implementation = implementation
154
- n . request_code = request_code
155
- n . payload = payload if payload
156
- n . to_s
157
- end
158
-
159
- def build_ntp_generic ( version , mode )
160
- n = NTPGeneric . new
161
- n . version = version
162
- n . mode = mode
163
- n . to_s
164
- end
165
-
166
- def sleep_time
167
- datastore [ 'SLEEP' ] / 1000.0
168
78
end
169
79
170
80
def run_host ( ip )
171
- # parse and sanity check versions
172
- @versions = datastore [ 'VERSIONS' ] . split ( /[^\d ]/ ) . select { |v | !v . empty? } . map { |v | v . to_i }
173
- unsupported_versions = @versions - NTP_SUPPORTED_VERSIONS
174
- fail "Unsupported NTP versions: #{ unsupported_versions } " unless unsupported_versions . empty?
175
- # parse and sanity check modes
176
- @modes = datastore [ 'MODES' ] . split ( /[^\d ]/ ) . select { |m | !m . empty? } . map { |v | v . to_i }
177
- unsupported_modes = @modes - NTP_SUPPORTED_MODES
178
- fail "Unsupported NTP modes: #{ unsupported_modes } " unless unsupported_modes . empty?
179
- # parse and sanity check mode 6 operations
180
- @mode_6_operations = datastore [ 'MODE_6_OPERATIONS' ] . split ( /[^\d ]/ ) . select { |m | !m . empty? } . map { |v | v . to_i }
181
- unsupported_ops = @mode_6_operations - NTP_SUPPORTED_MODE_6_OPERATIONS
182
- fail "Unsupported NTP mode 6 operations: #{ unsupported_ops } " unless unsupported_ops . empty?
183
- # parse and sanity check mode 7 implementations
184
- @mode_7_implementations = datastore [ 'MODE_7_IMPLEMENTATIONS' ] . split ( /[^\d ]/ ) . select { |m | !m . empty? } . map { |v | v . to_i }
185
- unsupported_implementations = @mode_7_implementations - NTP_SUPPORTED_MODE_7_IMPLEMENTATIONS
186
- fail "Unsupported NTP mode 7 implementations: #{ unsupported_implementations } " unless unsupported_implementations . empty?
187
- # parse and sanity check mode 7 request codes
188
- @mode_7_request_codes = datastore [ 'MODE_7_REQUEST_CODES' ] . split ( /[^\d ]/ ) . select { |m | !m . empty? } . map { |v | v . to_i }
189
- unsupported_request_codes = @mode_7_request_codes - NTP_SUPPORTED_MODE_7_REQUEST_CODES
190
- fail "Unsupported NTP mode 7 request codes: #{ unsupported_request_codes } " unless unsupported_request_codes . empty?
81
+ # check and set the optional advanced options
82
+ check_and_set ( 'VERSIONS' )
83
+ check_and_set ( 'MODES' )
84
+ check_and_set ( 'MODE_6_OPERATIONS' )
85
+ check_and_set ( 'MODE_7_IMPLEMENTATIONS' )
86
+ check_and_set ( 'MODE_7_REQUEST_CODES' )
191
87
192
88
connect_udp
193
89
fuzz_version_mode ( ip )
@@ -204,7 +100,7 @@ def fuzz_control(host)
204
100
@versions . each do |version |
205
101
print_status ( "#{ host } :#{ rport } fuzzing version #{ version } control messages (mode 6)" )
206
102
@mode_6_operations . each do |op |
207
- request = build_ntp_control ( version , op )
103
+ request = Rex :: Proto :: NTP . ntp_control ( version , op )
208
104
what = "#{ request . size } -byte version #{ version } mode 6 op #{ op } message"
209
105
vprint_status ( "#{ host } :#{ rport } probing with #{ request . size } -byte #{ what } " )
210
106
responses = probe ( host , datastore [ 'RPORT' ] . to_i , request )
@@ -220,7 +116,7 @@ def fuzz_private(host)
220
116
print_status ( "#{ host } :#{ rport } fuzzing version #{ version } private messages (mode 7)" )
221
117
@mode_7_implementations . each do |implementation |
222
118
@mode_7_request_codes . each do |request_code |
223
- request = build_ntp_private ( version , implementation , request_code , "\x00 " *188 )
119
+ request = Rex :: Proto :: NTP . ntp_private ( version , implementation , request_code , "\x00 " *188 )
224
120
what = "#{ request . size } -byte version #{ version } mode 7 imp #{ implementation } req #{ request_code } message"
225
121
vprint_status ( "#{ host } :#{ rport } probing with #{ request . size } -byte #{ what } " )
226
122
responses = probe ( host , datastore [ 'RPORT' ] . to_i , request )
@@ -262,7 +158,7 @@ def fuzz_version_mode(host, short=false)
262
158
print_status ( "#{ host } :#{ rport } fuzzing #{ short ? 'short ' : nil } version and mode combinations" )
263
159
@versions . each do |version |
264
160
@modes . each do |mode |
265
- request = build_ntp_generic ( version , mode )
161
+ request = Rex :: Proto :: NTP . ntp_generic ( version , mode )
266
162
request = request [ 0 , 4 ] if short
267
163
what = "#{ request . size } -byte #{ short ? 'short ' : nil } version #{ version } mode #{ mode } message"
268
164
vprint_status ( "#{ host } :#{ rport } probing with #{ what } " )
@@ -283,23 +179,17 @@ def probe(host, port, message)
283
179
replies
284
180
end
285
181
286
- # Parses the given message and provides a description about the NTP message inside
287
- def describe ( message )
288
- ntp = NTPGeneric . new ( message )
289
- "#{ message . size } -byte version #{ ntp . version } mode #{ ntp . mode } reply"
290
- end
291
-
292
182
def handle_responses ( host , request , responses , what )
293
183
problems = [ ]
294
184
descriptions = [ ]
295
185
responses . select! { |r | r [ 1 ] }
296
186
return if responses . empty?
297
187
responses . each do |response |
298
188
data = response [ 0 ]
299
- descriptions << describe ( data )
189
+ descriptions << Rex :: Proto :: NTP . describe ( data )
300
190
problems << 'large response' if request . size < data . size
301
- ntp_req = NTPGeneric . new ( request )
302
- ntp_resp = NTPGeneric . new ( data )
191
+ ntp_req = Rex :: Proto :: NTP :: NTPGeneric . new ( request )
192
+ ntp_resp = Rex :: Proto :: NTP :: NTPGeneric . new ( data )
303
193
problems << 'version mismatch' if ntp_req . version != ntp_resp . version
304
194
end
305
195
0 commit comments