# -*- coding: binary -*- module Msf module Ui module Console module CommandDispatcher ### # # Exploit module command dispatcher. # ### class Exploit include Msf::Ui::Console::ModuleCommandDispatcher include Msf::Ui::Console::ModuleOptionTabCompletion @@exploit_opts = Rex::Parser::Arguments.new( "-e" => [ true, "The payload encoder to use. If none is specified, ENCODER is used." ], "-f" => [ false, "Force the exploit to run regardless of the value of MinimumRank." ], "-h" => [ false, "Help banner." ], "-j" => [ false, "Run in the context of a job." ], "-J" => [ false, "Force running in the foreground, even if passive." ], "-n" => [ true, "The NOP generator to use. If none is specified, NOP is used." ], "-o" => [ true, "A comma separated list of options in VAR=VAL format." ], "-p" => [ true, "The payload to use. If none is specified, PAYLOAD is used." ], "-t" => [ true, "The target index to use. If none is specified, TARGET is used." ], "-z" => [ false, "Do not interact with the session after successful exploitation." ]) # # Returns the hash of exploit module specific commands. # def commands super.update({ "exploit" => "Launch an exploit attempt", "rcheck" => "Reloads the module and checks if the target is vulnerable", "rexploit" => "Reloads the module and launches an exploit attempt", "run" => "Alias for exploit", "recheck" => "Alias for rcheck", "rerun" => "Alias for rexploit", "reload" => "Just reloads the module" }) end # # Returns the name of the command dispatcher. # def name "Exploit" end # # Launches an exploitation single attempt. # def exploit_single(mod, opts) begin session = mod.exploit_simple(opts) rescue ::Interrupt raise $! rescue ::Exception => e print_error("Exploit exception (#{mod.refname}): #{e.class} #{e}") if e.class.to_s != 'Msf::OptionValidateError' print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple/ print_error(" #{line}") end end end return session end # # Tab completion for the run command # def cmd_run_tabs(str, words) fmt = { '-e' => [ framework.encoders.map { |refname, mod| refname } ], '-f' => [ nil ], '-h' => [ nil ], '-j' => [ nil ], '-J' => [ nil ], '-n' => [ framework.nops.map { |refname, mod| refname } ], '-o' => [ true ], '-p' => [ framework.payloads.map { |refname, mod| refname } ], '-t' => [ true ], '-z' => [ nil ] } flags = tab_complete_generic(fmt, str, words) options = tab_complete_option(active_module, str, words) flags + options end # # Tab completion for the exploit command # alias cmd_exploit_tabs cmd_run_tabs # # Launches exploitation attempts. # def cmd_exploit(*args) force = false module_opts = [] any_session = false opts = { 'Encoder' => mod.datastore['ENCODER'], 'Payload' => mod.datastore['PAYLOAD'], 'Target' => mod.datastore['TARGET'], 'Nop' => mod.datastore['NOP'], 'OptionStr' => nil, 'LocalInput' => driver.input, 'LocalOutput' => driver.output, 'RunAsJob' => false, 'Background' => false, 'Force' => false } if mod.passive? opts['RunAsJob'] = true end @@exploit_opts.parse(args) do |opt, idx, val| case opt when '-e' opts['Encoder'] = val when '-f' force = true when '-j' opts['RunAsJob'] = true when '-J' opts['RunAsJob'] = false when '-n' opts['Nop'] = val when '-o' module_opts.push(val) when '-p' opts['Payload'] = val when '-t' opts['Target'] = val.to_i when '-z' opts['Background'] = true when '-h' cmd_exploit_help return false else if val[0] != '-' && val.match?('=') module_opts.push(val) else cmd_exploit_help return false end end end unless module_opts.empty? opts['OptionStr'] = module_opts.join(',') end minrank = RankingName.invert[framework.datastore['MinimumRank']] || 0 if minrank > mod.rank if force print_status("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'") ilog("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'", 'core') else print_error("This exploit is below the minimum rank, '#{framework.datastore['MinimumRank']}'.") print_error("If you really want to run it, do 'exploit -f' or") print_error("setg MinimumRank to something lower ('manual' is") print_error("the lowest and would allow running all exploits).") return end end rhosts = mod.datastore['RHOSTS'] if rhosts rhosts_opt = Msf::OptAddressRange.new('RHOSTS') rhosts_range = Rex::Socket::RangeWalker.new(rhosts_opt.normalize(rhosts)) end # For multiple targets exploit attempts. if rhosts_range && rhosts_range.length.to_i > 1 opts[:multi] = true rhosts_range.each do |rhost| nmod = mod.replicant nmod.datastore['RHOST'] = rhost nmod.datastore['VHOST'] = rhosts if (!Rex::Socket.is_ip_addr?(rhosts) && nmod.is_a?(Msf::Exploit::Remote::HttpClient) && nmod.datastore['VHOST'].nil?) # If rhost is the last target, let exploit handler stop. opts["multi"] = false if rhost == (Rex::Socket.addr_itoa(rhosts_range.ranges.first.stop)) # Catch the interrupt exception to stop the whole module during exploit begin print_status("Exploiting target #{rhost}") session = exploit_single(nmod, opts) rescue ::Interrupt print_status("Stopping exploiting current target #{rhost}...") print_status("Control-C again to force quit exploiting all targets.") begin Rex.sleep(1) rescue ::Interrupt raise $! end end # If we were given a session, report it. if session print_status("Session #{session.sid} created in the background.") any_session = true end end # For single target or no rhosts option. else # avoid bug when the cidr of rhosts is 32, like 8.8.8.8/32 if rhosts_range && rhosts_range.length == 1 mod.datastore['RHOST'] = (Rex::Socket.addr_itoa(rhosts_range.ranges.first.start)) end session = exploit_single(mod, opts) # If we were given a session, let's see what we can do with it if session any_session = true if !opts['Background'] && session.interactive? # If we aren't told to run in the background and the session can be # interacted with, start interacting with it by issuing the session # interaction command. print_line driver.run_single("sessions -q -i #{session.sid}") # Otherwise, log that we created a session else # Otherwise, log that we created a session print_status("Session #{session.sid} created in the background.") end elsif opts['RunAsJob'] && mod.job_id # Indicate if he exploit as a job, indicate such so the user doesn't # wonder what's up. print_status("Exploit running as background job #{mod.job_id}.") # Worst case, the exploit ran but we got no session, bummer. end end # If we didn't get any session and exploit ended luanch. unless any_session # If we didn't run a payload handler for this exploit it doesn't # make sense to complain to the user that we didn't get a session unless mod.datastore["DisablePayloadHandler"] fail_msg = 'Exploit completed, but no session was created.' print_status(fail_msg) begin framework.events.on_session_fail(fail_msg) rescue ::Exception => e wlog("Exception in on_session_open event handler: #{e.class}: #{e}") wlog("Call Stack\n#{e.backtrace.join("\n")}") end end end end alias cmd_run cmd_exploit def cmd_exploit_help print_line "Usage: exploit [options]" print_line print_line "Launches an exploitation attempt." print @@exploit_opts.usage end alias cmd_run_help cmd_exploit_help # # Reloads an exploit module and checks the target to see if it's # vulnerable. # def cmd_rcheck(*args) reload() cmd_check(*args) end alias cmd_recheck cmd_rcheck # # Reloads an exploit module and launches an exploit. # def cmd_rexploit(*args) return cmd_rexploit_help if args.include? "-h" # Stop existing job and reload the module if reload(true) # Delegate to the exploit command unless the reload failed cmd_exploit(*args) end end alias cmd_rerun cmd_rexploit def cmd_rexploit_help print_line "Usage: rexploit [options]" print_line print_line "Reloads a module, stopping any associated job, and launches an exploitation attempt." print @@exploit_opts.usage end alias cmd_rerun_help cmd_rexploit_help # Select a reasonable default payload and minimally configure it # TODO: Move this somewhere better or make it more dynamic? def self.choose_payload(mod) compatible_payloads = mod.compatible_payloads( excluded_platforms: ['Multi'] # We don't want to select a multi payload ).map(&:first) # XXX: Determine LHOST based on RHOST or an arbitrary internet address lhost = Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50') configure_payload = lambda do |payload| mod.datastore['PAYLOAD'] = payload # Set LHOST if this is a reverse payload if payload.index('reverse') mod.datastore['LHOST'] = lhost end payload end # If there is only one compatible payload, return it immediately if compatible_payloads.length == 1 return configure_payload.call(compatible_payloads.first) end # XXX: This approach is subpar, and payloads should really be ranked! preferred_payloads = [ # These payloads are generally reliable and common enough in practice '/meterpreter/reverse_tcp', '/shell/reverse_tcp', 'cmd/unix/reverse_netcat', 'cmd/windows/powershell_reverse_tcp', # Fall back on a generic payload to autoselect a specific payload 'generic/shell_reverse_tcp', 'generic/shell_bind_tcp' ] # XXX: This is not efficient in the slightest preferred_payloads.each do |type| payload = compatible_payloads.find { |name| name.end_with?(type) } next unless payload return configure_payload.call(payload) end nil end end end end end end