module Msf ### # # This module provides methods for scanning modules # ### module Auxiliary::Scanner # # Initializes an instance of a recon auxiliary module # def initialize(info = {}) super register_options([ OptAddressRange.new('RHOSTS', [ true, "The target address range or CIDR identifier"]), OptInt.new('THREADS', [ true, "The number of concurrent threads", 1 ] ) ], Auxiliary::Scanner) # RHOST should not be used in scanner modules, only RHOSTS deregister_options('RHOST') register_advanced_options([ OptBool.new('ShowProgress', [true, 'Display progress messages during a scan', true]), OptInt.new('ShowProgressPercent', [true, 'The interval in percent that progress should be shown', 10]) ], Auxiliary::Scanner) end # # The command handler when launched from the console # def run @show_progress = datastore['ShowProgress'] @show_percent = datastore['ShowProgressPercent'].to_i ar = Rex::Socket::RangeWalker.new(datastore['RHOSTS']) @range_count = ar.length || 0 @range_done = 0 @range_percent = 0 threads_max = datastore['THREADS'].to_i @tl = [] # # Sanity check threading on different platforms # if(Rex::Compat.is_windows) if(threads_max > 16) print_error("Warning: The Windows platform cannot reliably support more than 16 threads") print_error("Thread count has been adjusted to 16") threads_max = 16 end end if(Rex::Compat.is_cygwin) if(threads_max > 200) print_error("Warning: The Cygwin platform cannot reliably support more than 200 threads") print_error("Thread count has been adjusted to 200") threads_max = 200 end end begin if (self.respond_to?('run_range')) # No automated progress reporting for run_range return run_range(datastore['RHOSTS']) end if (self.respond_to?('run_host')) @tl = [] while (true) # Spawn threads for each host while (@tl.length < threads_max) ip = ar.next_ip break if not ip @tl << framework.threads.spawn("ScannerHost(#{self.refname})-#{ip}", false, ip.dup) do |tip| targ = tip nmod = self.replicant nmod.datastore['RHOST'] = targ begin nmod.run_host(targ) rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError raise $! rescue ::Exception => e print_status("Error: #{targ}: #{e.class} #{e.message}") elog("Error running against host #{targ}: #{e.message}\n#{e.backtrace.join("\n")}") ensure nmod.cleanup end end end # Exit once we run out of hosts if(@tl.length == 0) break end # Assume that the oldest thread will be one of the # first to finish and wait for it. After that's # done, remove any finished threads from the list # and continue on. This will open up at least one # spot for a new thread tla = @tl.length @tl.first.join @tl.delete_if { |t| not t.alive? } tlb = @tl.length @range_done += (tla - tlb) scanner_show_progress() if @show_progress end return end if (self.respond_to?('run_batch')) if (! self.respond_to?('run_batch_size')) print_status("This module needs to export run_batch_size()") return end size = run_batch_size() ar = Rex::Socket::RangeWalker.new(datastore['RHOSTS']) @tl = [] while(true) nohosts = false while (@tl.length < threads_max) batch = [] # Create batches from each set while (batch.length < size) ip = ar.next_ip if (not ip) nohosts = true break end batch << ip end # Create a thread for each batch if (batch.length > 0) thread = framework.threads.spawn("ScannerBatch(#{self.refname})", false, batch) do |bat| nmod = self.replicant mybatch = bat.dup begin nmod.run_batch(mybatch) rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError raise $! rescue ::Exception => e print_status("Error: #{mybatch[0]}-#{mybatch[-1]}: #{e}") ensure nmod.cleanup end end thread[:batch_size] = batch.length @tl << thread end # Exit once we run out of hosts if (@tl.length == 0 or nohosts) break end end # Exit if there are no more pending threads if (@tl.length == 0) break end # Assume that the oldest thread will be one of the # first to finish and wait for it. After that's # done, remove any finished threads from the list # and continue on. This will open up at least one # spot for a new thread tla = 0 @tl.map {|t| tla += t[:batch_size] } @tl.first.join @tl.delete_if { |t| not t.alive? } tlb = 0 @tl.map {|t| tlb += t[:batch_size] } @range_done += tla - tlb scanner_show_progress() if @show_progress end return end print_error("This module defined no run_host, run_range or run_batch methods") rescue ::Interrupt print_status("Caught interrupt from the console...") return ensure seppuko!() end end def seppuko! @tl.each do |t| begin t.kill if t.alive? rescue ::Exception end end end def scanner_progress return 0 unless @range_done and @range_count pct = (@range_done / @range_count.to_f) * 100 end def scanner_show_progress pct = scanner_progress 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)") end end end end