diff --git a/lib/rex/proto/ntp/modes.rb b/lib/rex/proto/ntp/modes.rb index c2a986ca58..f395340bb1 100644 --- a/lib/rex/proto/ntp/modes.rb +++ b/lib/rex/proto/ntp/modes.rb @@ -1,4 +1,8 @@ # -*- coding: binary -*- + +require 'rubygems' +require 'bit-struct' + module Rex module Proto module NTP @@ -25,7 +29,7 @@ module NTP unsigned :stratum, 8, default: 0 unsigned :poll, 8, default: 0 unsigned :precision, 8, default: 0 - char :payload, 352 + rest :payload end # An NTP control message. Control messages are only specified for NTP @@ -62,11 +66,12 @@ module NTP # 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 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - # |00 | VN | 7 |A| Sequence | + # |R M| VN | 7 |A| Sequence | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Implementation| request code | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - unsigned :reserved, 2, default: 0 + unsigned :response, 1, default: 0 + unsigned :more, 1, default: 0 unsigned :version, 3, default: 0 unsigned :mode, 3, default: 7 unsigned :auth, 1, default: 0 diff --git a/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb index 3ef1d896a5..bb67817de1 100644 --- a/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb +++ b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb @@ -86,8 +86,8 @@ class Metasploit3 < Msf::Auxiliary check_and_set('MODE_7_REQUEST_CODES') connect_udp - fuzz_version_mode(ip) fuzz_version_mode(ip, true) + fuzz_version_mode(ip, false) fuzz_short(ip) fuzz_random(ip) fuzz_control(ip) if @modes.include?(6) @@ -144,6 +144,7 @@ class Metasploit3 < Msf::Auxiliary def fuzz_random(host) print_status("#{host}:#{rport} fuzzing random messages") 0.upto(5) do + # TODO: is there a better way to pick this size? Should more than one be tried? request = SecureRandom.random_bytes(48) what = "random #{request.size}-byte message" vprint_status("#{host}:#{rport} probing with #{what}") @@ -154,12 +155,17 @@ class Metasploit3 < Msf::Auxiliary end # Sends a series of different version + mode combinations - def fuzz_version_mode(host, short=false) + def fuzz_version_mode(host, short) print_status("#{host}:#{rport} fuzzing #{short ? 'short ' : nil}version and mode combinations") @versions.each do |version| @modes.each do |mode| - request = Rex::Proto::NTP.ntp_generic(version, mode) - request = request[0, 4] if short + request = Rex::Proto::NTP::NTPGeneric.new + request.version = version + request.mode = mode + unless short + # TODO: is there a better way to pick this size? Should more than one be tried? + request.payload = SecureRandom.random_bytes(16) + end what = "#{request.size}-byte #{short ? 'short ' : nil}version #{version} mode #{mode} message" vprint_status("#{host}:#{rport} probing with #{what}") responses = probe(host, datastore['RPORT'].to_i, request) diff --git a/spec/lib/rex/proto/ntp/modes_spec.rb b/spec/lib/rex/proto/ntp/modes_spec.rb new file mode 100644 index 0000000000..b0e6a75b66 --- /dev/null +++ b/spec/lib/rex/proto/ntp/modes_spec.rb @@ -0,0 +1,83 @@ +# -*- coding: binary -*- +# +require 'rex/proto/ntp/modes' + +describe "Rex::Proto::NTP mode message handling" do + before do + @payload = 'R7' * 7 + end + + describe Rex::Proto::NTP::NTPControl do + before do + @control_raw = "\x1e\x05\x12\x34\x12\x34\x12\x34\x00\x00\x00\x0e" + @payload + @control = Rex::Proto::NTP::NTPControl.new + @control.version = 3 + @control.response = 0 + @control.more = 0 + @control.operation = 5 + @control.sequence = 0x1234 + @control.association_id = 0x1234 + @control.status = 0x1234 + @control.payload_offset = 0 + @control.payload_size = 14 + @control.payload = @payload + end + + it 'Generates control NTP messages correctly' do + @control_raw.should == @control.to_s + end + + it 'Parses private NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPControl.new(@control_raw) + @control.should == parsed_raw + end + end + + describe Rex::Proto::NTP::NTPGeneric do + before do + @generic_raw = "\xcc\x12\x34\x56" + @payload + @generic = Rex::Proto::NTP::NTPGeneric.new + @generic.li = 3 + @generic.version = 1 + @generic.mode = 4 + @generic.stratum = 0x12 + @generic.poll = 0x34 + @generic.precision = 0x56 + @generic.payload = @payload + end + + it 'Generates generic NTP messages correctly' do + @generic_raw.should == @generic.to_s + end + + it 'Parses private NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPGeneric.new(@generic_raw) + @generic.should == parsed_raw + end + end + + describe Rex::Proto::NTP::NTPPrivate do + before do + @private_raw = "\x1f\x5a\x01\x99" + @payload + @private = Rex::Proto::NTP::NTPPrivate.new + @private.response = 0 + @private.more = 0 + @private.version = 3 + @private.mode = 7 + @private.auth = 0 + @private.sequence = 90 + @private.implementation = 1 + @private.request_code = 153 + @private.payload = @payload + end + + it 'Generates private NTP messages correctly' do + @private_raw.should == @private.to_s + end + + it 'Parses private NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPPrivate.new(@private_raw) + @private.should == parsed_raw + end + end +end