From 7e05f88399a0dd622260a2db7ab8138884f101fd Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 11 Nov 2014 15:06:43 -0600 Subject: [PATCH] Reapply PR #4113 (removed via #4175) --- .rubocop.yml | 11 +- lib/msf/core/auxiliary/scanner.rb | 4 +- lib/msf/core/auxiliary/udp_scanner.rb | 34 +++-- .../auxiliary/scanner/udp_scanner_template.rb | 123 ++++++++++++++++++ 4 files changed, 159 insertions(+), 13 deletions(-) create mode 100644 modules/auxiliary/scanner/udp_scanner_template.rb diff --git a/.rubocop.yml b/.rubocop.yml index c9ba4d1bb3..95aeb51d59 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,7 +8,7 @@ # inherit_from: .rubocop_todo.yml -Style/ClassLength: +Metrics/ClassLength: Description: 'Most Metasploit modules are quite large. This is ok.' Enabled: true Exclude: @@ -25,14 +25,14 @@ Style/Encoding: Description: 'We prefer binary to UTF-8.' EnforcedStyle: 'when_needed' -Style/LineLength: +Metrics/LineLength: Description: >- Metasploit modules often pattern match against very long strings when identifying targets. Enabled: true Max: 180 -Style/MethodLength: +Metrics/MethodLength: Enabled: true Description: >- While the style guide suggests 10 lines, exploit definitions @@ -44,6 +44,11 @@ Style/MethodLength: Style/Encoding: Enabled: false +# %q() is super useful for long strings split over multiple lines and +# is very common in module constructors for things like descriptions +Style/UnneededPercentQ: + Enabled: false + Style/NumericLiterals: Enabled: false Description: 'This often hurts readability for exploit-ish code.' diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb index fade61b0ea..d428daa6a1 100644 --- a/lib/msf/core/auxiliary/scanner.rb +++ b/lib/msf/core/auxiliary/scanner.rb @@ -241,10 +241,10 @@ end def scanner_show_progress pct = scanner_progress - if(pct >= (@range_percent + @show_percent)) + if pct >= (@range_percent + @show_percent) @range_percent = @range_percent + @show_percent tdlen = @range_count.to_s.length - print_status("Scanned #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)") + print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct)) end end diff --git a/lib/msf/core/auxiliary/udp_scanner.rb b/lib/msf/core/auxiliary/udp_scanner.rb index a7d654a362..27950bf9e7 100644 --- a/lib/msf/core/auxiliary/udp_scanner.rb +++ b/lib/msf/core/auxiliary/udp_scanner.rb @@ -8,33 +8,36 @@ module Msf # ### module Auxiliary::UDPScanner - include Auxiliary::Scanner + # A hash of results of a given batch run, keyed by host + attr_accessor :results + # # Initializes an instance of an auxiliary module that scans UDP # - def initialize(info = {}) super register_options( [ - Opt::CHOST, + Opt::RPORT, OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), + OptInt.new('THREADS', [true, "The number of concurrent threads", 10]) ], self.class) register_advanced_options( [ + Opt::CHOST, + Opt::CPORT, OptInt.new('ScannerRecvInterval', [true, 'The maximum numbers of sends before entering the processing loop', 30]), OptInt.new('ScannerMaxResends', [true, 'The maximum times to resend a packet when out of buffers', 10]), OptInt.new('ScannerRecvQueueLimit', [true, 'The maximum queue size before breaking out of the processing loop', 100]), - OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15]), + OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15]) ], self.class) end - # Define our batch size def run_batch_size datastore['BATCHSIZE'].to_i @@ -44,6 +47,7 @@ module Auxiliary::UDPScanner def run_batch(batch) @udp_sock = Rex::Socket::Udp.create({ 'LocalHost' => datastore['CHOST'] || nil, + 'LocalPort' => datastore['CPORT'] || 0, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } }) add_socket(@udp_sock) @@ -155,12 +159,25 @@ module Auxiliary::UDPScanner queue.length end + def cport + datastore['CPORT'] + end + + def rport + datastore['RPORT'] + end + # - # The including module override these methods + # The including module may override some of these methods # - # Called for each IP in the batch + # Builds and returns the probe to be sent + def build_probe + end + + # Called for each IP in the batch. This will send all necessary probes. def scan_host(ip) + scanner_send(build_probe, ip, rport) end # Called for each response packet @@ -169,11 +186,12 @@ module Auxiliary::UDPScanner # Called before the scan block def scanner_prescan(batch) + vprint_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @results = {} end # Called after the scan block def scanner_postscan(batch) end - end end diff --git a/modules/auxiliary/scanner/udp_scanner_template.rb b/modules/auxiliary/scanner/udp_scanner_template.rb new file mode 100644 index 0000000000..065e5f351b --- /dev/null +++ b/modules/auxiliary/scanner/udp_scanner_template.rb @@ -0,0 +1,123 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Auxiliary::UDPScanner + + def initialize(info = {}) + super( + update_info( + info, + # TODO: fill in all of this + 'Name' => 'UDP Scanner Example', + 'Description' => %q( + This module is an example of how to send probes to UDP services + en-masse, analyze any responses, and then report on any discovered + hosts, services, vulnerabilities or otherwise noteworthy things. + Simply address any of the TODOs. + ), + 'Author' => 'Joe Contributor ', + 'References' => + [ + ['URL', 'https://example.com/~jcontributor'] + ], + 'DisclosureDate' => 'Mar 15 2014', + 'License' => MSF_LICENSE + ) + ) + + register_options( + [ + # TODO: change to the port you need to scan + Opt::RPORT(12345) + ], self.class) + + # TODO: add any advanced, special options here, otherwise remove + register_advanced_options( + [ + OptBool.new('SPECIAL', [true, 'Try this special thing', false]) + ], self.class) + end + + def setup + super + # TODO: do any sort of preliminary sanity checking, like perhaps validating some options + # in the datastore, etc. + end + + # TODO: construct the appropriate probe here. + def build_probe + @probe ||= 'abracadabra!' + end + + # TODO: this is called before the scan block for each batch of hosts. Do any + # per-batch setup here, otherwise remove it. + def scanner_prescan(batch) + super + end + + # TODO: this is called for each IP in the batch. This will send all of the + # necessary probes. If something different must be done for each IP, do it + # here, otherwise remove it. + def scan_host(ip) + super + end + + # Called for each response packet + def scanner_process(response, src_host, _src_port) + # TODO: inspect each response, perhaps confirming that it is a valid + # response for the service/protocol in question and/or analyzing it more + # closely. In this case, we simply check to see that it is of reasonable + # size and storing a result for this host iff so. Note that src_port may + # not actually be the same as the original RPORT for some services if they + # respond back from different ports + return unless response.size >= 42 + @results[src_host] ||= [] + + # TODO: store something about this response, perhaps the response itself, + # some metadata obtained by analyzing it, the proof that it is vulnerable + # to something, etc. In this example, we simply look for any response + # with a sequence of 5 useful ASCII characters and, iff found, we store + # that sequence + /(?[\x20-\x7E]{5})/ =~ response && @results[src_host] << relevant + end + + # Called after the scan block + def scanner_postscan(_batch) + @results.each_pair do |host, relevant_responses| + peer = "#{host}:#{rport}" + + # report on the host + report_host(host: host) + + # report on the service, since it responded + report_service( + host: host, + proto: 'udp', + port: rport, + name: 'example', + # show at most 4 relevant responses + info: relevant_responses[0, 4].join(',') + ) + + if relevant_responses.empty? + vprint_status("#{peer} Not vulnerable to something") + else + print_good("#{peer} Vulnerable to something!") + report_vuln( + host: host, + port: rport, + proto: 'udp', + name: 'something!', + info: "Got #{relevant_responses.size} response(s)", + refs: references + ) + end + end + end +end