Path: blob/master/lib/msf/core/auxiliary/scanner.rb
19664 views
# -*- coding: binary -*-12module Msf34###5#6# This module provides methods for scanning modules7#8###910module Auxiliary::Scanner1112include Msf::Auxiliary::MultipleTargetHosts1314class AttemptFailed < Msf::Auxiliary::Failed15end1617#18# Initializes an instance of a recon auxiliary module19#20def initialize(info = {})21super2223register_options([24Opt::RHOSTS,25OptInt.new('THREADS', [ true, "The number of concurrent threads (max one per host)", 1 ] )26], Auxiliary::Scanner)2728register_advanced_options([29OptBool.new('ShowProgress', [true, 'Display progress messages during a scan', true]),30OptInt.new('ShowProgressPercent', [true, 'The interval in percent that progress should be shown', 10])31], Auxiliary::Scanner)3233end3435def peer36# IPv4 addr can be 16 chars + 1 for : and + 5 for port37super.ljust(21)38end3940#41# The command handler when launched from the console42#43def run44@show_progress = datastore['ShowProgress']45@show_percent = datastore['ShowProgressPercent'].to_i4647if self.respond_to?(:session) && session48datastore['RHOSTS'] = session.address49end5051rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum52@range_count = rhosts_walker.count || 053@range_done = 054@range_percent = 05556threads_max = datastore['THREADS'].to_i57@thread_list = []58@scan_errors = []5960res = Queue.new61results = Hash.new6263#64# Sanity check threading given different conditions65#6667if datastore['CPORT'].to_i != 0 && threads_max > 168print_error("Warning: A maximum of one thread is possible when a source port is set (CPORT)")69print_error("Thread count has been adjusted to 1")70threads_max = 171end7273if(Rex::Compat.is_windows)74if(threads_max > 16)75print_error("Warning: The Windows platform cannot reliably support more than 16 threads")76print_error("Thread count has been adjusted to 16")77threads_max = 1678end79end8081if(Rex::Compat.is_cygwin)82if(threads_max > 200)83print_error("Warning: The Cygwin platform cannot reliably support more than 200 threads")84print_error("Thread count has been adjusted to 200")85threads_max = 20086end87end8889begin9091if (self.respond_to?('run_host'))92loop do93# Stop scanning if we hit a fatal error94break if has_fatal_errors?9596# Spawn threads for each host97while (@thread_list.length < threads_max)9899# Stop scanning if we hit a fatal error100break if has_fatal_errors?101102begin103datastore = rhosts_walker.next104rescue StopIteration105datastore = nil106end107break unless datastore108109@thread_list << framework.threads.spawn("ScannerHost(#{self.refname})-#{datastore['RHOST']}", false, datastore.dup) do |thr_datastore|110targ = thr_datastore['RHOST']111nmod = self.replicant112nmod.datastore = thr_datastore113114begin115res << { targ => nmod.run_host(targ) }116rescue ::Rex::BindFailed117if datastore['CHOST']118@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"119end120rescue Msf::Auxiliary::Scanner::AttemptFailed => e121nmod.vprint_error("#{e}")122rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError123rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError124raise $!125rescue ::Exception => e126print_status("Error: #{targ}: #{e.class} #{e.message}")127elog("Error running against host #{targ}", error: e)128ensure129nmod.cleanup130end131end132end133134# Do as much of this work as possible while other threads are running135while !res.empty?136results.merge! res.pop137end138139# Stop scanning if we hit a fatal error140break if has_fatal_errors?141142# Exit once we run out of hosts143if(@thread_list.length == 0)144break145end146147# Attempt to wait for the oldest thread for a second,148# remove any finished threads from the list149# and continue on.150tla = @thread_list.length151@thread_list.first.join(1)152@thread_list.delete_if { |t| not t.alive? }153tlb = @thread_list.length154155@range_done += (tla - tlb)156scanner_show_progress() if @show_progress157end158159scanner_handle_fatal_errors160return results161end162163if (self.respond_to?('run_batch'))164165if (! self.respond_to?('run_batch_size'))166print_status("This module needs to export run_batch_size()")167return168end169170size = run_batch_size()171172rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum173174while(true)175nohosts = false176177# Stop scanning if we hit a fatal error178break if has_fatal_errors?179180while (@thread_list.length < threads_max)181182batch = []183184# Create batches from each set185while (batch.length < size)186begin187datastore = rhosts_walker.next188rescue StopIteration189datastore = nil190end191if (not datastore)192nohosts = true193break194end195batch << datastore['RHOST']196end197198# Create a thread for each batch199if (batch.length > 0)200thread = framework.threads.spawn("ScannerBatch(#{self.refname})", false, batch) do |bat|201nmod = self.replicant202mybatch = bat.dup203begin204nmod.run_batch(mybatch)205rescue ::Rex::BindFailed206if datastore['CHOST']207@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"208end209rescue Msf::Auxiliary::Scanner::AttemptFailed => e210print_error("#{e}")211rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error212rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError213raise $!214rescue ::Exception => e215print_status("Error: #{mybatch[0]}-#{mybatch[-1]}: #{e}")216ensure217nmod.cleanup218end219end220thread[:batch_size] = batch.length221@thread_list << thread222end223224# Exit once we run out of hosts225if (@thread_list.length == 0 or nohosts)226break227end228end229230# Stop scanning if we hit a fatal error231break if has_fatal_errors?232233# Exit if there are no more pending threads234if (@thread_list.length == 0)235break236end237238# Attempt to wait for the oldest thread for a second,239# remove any finished threads from the list240# and continue on.241tla = 0242@thread_list.map {|t| tla += t[:batch_size] if t[:batch_size] }243@thread_list.first.join(1)244@thread_list.delete_if { |t| not t.alive? }245tlb = 0246@thread_list.map {|t| tlb += t[:batch_size] if t[:batch_size] }247248@range_done += tla - tlb249scanner_show_progress() if @show_progress250end251252scanner_handle_fatal_errors253return254end255256print_error("This module defined no run_host or run_batch methods")257258rescue ::Interrupt259print_status("Caught interrupt from the console...")260return261ensure262seppuko!()263end264end265266def seppuko!267@thread_list.each do |t|268begin269t.kill if t.alive?270rescue ::Exception271end272end273end274275def has_fatal_errors?276@scan_errors && !@scan_errors.empty?277end278279def scanner_handle_fatal_errors280return unless has_fatal_errors?281return unless @thread_list282283# First kill any running threads284@thread_list.each {|t| t.kill if t.alive? }285286# Show the unique errors triggered by the scan287uniq_errors = @scan_errors.uniq288uniq_errors.each do |emsg|289print_error("Fatal: #{emsg}")290end291print_error("Scan terminated due to #{uniq_errors.size} fatal error(s)")292end293294def scanner_progress295return 0 unless @range_done and @range_count296pct = (@range_done / @range_count.to_f) * 100297end298299def scanner_show_progress300# it should already be in the process of shutting down if there are fatal errors301return if has_fatal_errors?302pct = scanner_progress303if pct >= (@range_percent + @show_percent)304@range_percent = @range_percent + @show_percent305tdlen = @range_count.to_s.length306print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct))307end308end309310def add_delay_jitter(_delay, _jitter)311# Introduce the delay312delay_value = _delay.to_i313original_value = delay_value314jitter_value = _jitter.to_i315316# Retrieve the jitter value and delay value317# Delay = number of milliseconds to wait between each request318# Jitter = percentage modifier. For example:319# Delay is 1000ms (i.e. 1 second), Jitter is 50.320# 50/100 = 0.5; 0.5*1000 = 500. Therefore, the per-request321# delay will be 1000 +/- a maximum of 500ms.322if delay_value > 0323if jitter_value > 0324rnd = Random.new325if (rnd.rand(2) == 0)326delay_value += rnd.rand(jitter_value)327else328delay_value -= rnd.rand(jitter_value)329end330if delay_value < 0331delay_value = 0332end333end334final_delay = delay_value.to_f / 1000.0335vprint_status("Delaying for #{final_delay} second(s) (#{original_value}ms +/- #{jitter_value}ms)")336sleep final_delay337end338end339340def fail_with(reason, msg = nil, abort: false)341if abort342# raising Failed will case the run to be aborted343raise Msf::Auxiliary::Failed, "#{reason.to_s}: #{msg}"344else345# raising AttemptFailed will cause the run_host / run_batch to be aborted346raise Msf::Auxiliary::Scanner::AttemptFailed, "#{reason.to_s}: #{msg}"347end348end349350end351end352353354