diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index 7f4db478d9..d15134f4ca 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -49,6 +49,74 @@ module Msf::Post::Common pid_list.include?(pid) end + + # Returns the target architecture. + # You should use this instead of session.platform, because of the following reasons: + # 1. session.platform doesn't always give you an arch. For example: a shell session. + # 2. session.platform doesn't mean the target platform/arch, it means whatever the session is. + # For example: you can use a python meterpreter on a Windows platform, and you will + # get 'python/python' as your arch/platform, and not 'x86/win32'. + # + # @returns [String] The archtecture recognizable by framework's ARCH_TYPES. + def get_target_arch + arch = nil + + case session.type + when 'meterpreter' + arch = get_recognizable_arch(client.sys.config.sysinfo['Architecture']) + when 'shell' + if session.platform =~ /win/ + arch = get_recognizable_arch(get_env('PROCESSOR_ARCHITECTURE').strip) + else + arch = get_recognizable_arch(get_env('MACHTYPE').strip) + end + end + + arch + end + + + # Returns the target OS. + # You should use this instead of session.platform, because of the following reasons: + # 1. session.platform doesn't always provide a consistent OS name. For example: for a Windows + # target, session.platform might return 'win32', which isn't recognized by Msf::Module::Platform. + # 2. session.platform doesn't mean the target platform/arch, it means whatever the session is. + # For example: You can use a python meterpreter on a Windows platform, and you will get + # 'python/python', as your arch/platform, and not 'windows'. + # + # @return [String] The OS name recognizable by Msf::Module::Platform. + def get_target_os + os = nil + info = '' + + case session.type + when 'meterpreter' + info = client.sys.config.sysinfo['OS'] + when 'shell' + if session.platform =~ /win/ + info = get_env('OS').strip + else + info = cmd_exec('uname -s').strip + end + end + + case info + when /windows/i + os = Msf::Module::Platform::Windows.realname.downcase + when /darwin/i + os = Msf::Module::Platform::OSX.realname.downcase + when /freebsd/i + os = Msf::Module::Platform::FreeBSD.realname.downcase + when /GENERIC\.MP/i, /netbsd/i + os = Msf::Module::Platform::BSD.realname.downcase + else + os = Msf::Module::Platform::Linux.realname.downcase + end + + + os + end + # # Executes +cmd+ on the remote system # @@ -216,5 +284,40 @@ module Msf::Post::Common nil end + private + + # Returns an architecture recognizable by ARCH_TYPES. + # + # @param [String] target_arch The arch. Example: x86 + # @return [String] One of the archs from ARCH_TYPES. + def get_recognizable_arch(target_arch) + arch = nil + + # Special handle some cases that ARCH_TYPES won't recognize. + # https://msdn.microsoft.com/en-us/library/aa384274.aspx + case target_arch + when /i386/, /i686/ + return ARCH_X86 + when /amd64/i, /ia64/i + return ARCH_X86_64 + end + + # Rely on ARCH_TYPES to tell us a framework-recognizable ARCH. + # Notice we're sorting ARCH_TYPES first, so that the longest string + # goes first. This step is used because sometimes let's say if the target + # is 'x86_64', and if the ARCH_X86 kicks in first, then we will get 'x86' + # instead of x86_64, which is inaccurate. + recognizable_archs = ARCH_TYPES + recognizable_archs = recognizable_archs.sort_by {|a| a.length}.reverse + recognizable_archs.each do |a| + if target_arch =~ /#{a}/ + arch = a + break + end + end + + arch + end + end diff --git a/modules/post/multi/recon/local_exploit_suggestor.rb b/modules/post/multi/recon/local_exploit_suggestor.rb index 15444782cd..b105ef2d33 100644 --- a/modules/post/multi/recon/local_exploit_suggestor.rb +++ b/modules/post/multi/recon/local_exploit_suggestor.rb @@ -17,54 +17,35 @@ class Metasploit3 < Msf::Post meterpreter. Additionally, the ShowDescription option can be set to 'true' to a detailed description on the suggested exploits. - It's important to note that not all local exploits will be fired. For - example, if a module doesn't have a default datastore option that is - required for it to run, then it will not come up in the suggestor's - results. + It's important to note that not all local exploits will be fired. + They are chosen based on these conditions: session type, + platform, architecture, and required default options. }, 'License' => MSF_LICENSE, - 'Author' => [ 'sinn3r', - 'Mo' - ], + 'Author' => [ 'sinn3r', 'Mo' ], 'Platform' => all_platforms, 'SessionTypes' => [ 'meterpreter', 'shell' ] )) register_options([ Msf::OptInt.new('SESSION', [ true, "The session to run this module on." ]), - Msf::OptBool.new('ShowDescription', [true, "Displays a detailed description for the available exploits", false]) + Msf::OptBool.new('SHOWDESCRIPTION', [true, "Displays a detailed description for the available exploits", false]) ],Msf::Post) end - def all_platforms Msf::Module::Platform.subclasses.collect {|c| c.realname.downcase } end def get_target_arch - @target_arch ||= lambda { - return '' unless session - arch = '' - arch = session.platform.split('/').first if session.platform.include?('/') - return '' if ARCH_TYPES.include?(arch) - return arch - }.call + @target_arch ||= super end - def get_target_platform - @target_platform ||= lambda { - return '' unless session - platform = session.platform.include?('/') ? session.platform.split('/').second : session.platform - - if platform =~ /^win/ - platform = 'win' - end - - return platform - }.call + def get_target_os + @target_os ||= super end @@ -88,9 +69,10 @@ class Metasploit3 < Msf::Post def is_module_platform?(mod) platform_obj = nil begin - platform_obj = Msf::Module::Platform.find_platform(get_target_platform) - rescue ArgumentError + platform_obj = Msf::Module::Platform.find_platform(get_target_os) + rescue ArgumentError => e # When not found, find_platform raises an ArgumentError + elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") return false end @@ -99,64 +81,82 @@ class Metasploit3 < Msf::Post end + def is_session_type_compat?(mod) + mod.session_compatible?(session.sid) + end + + def set_module_options(mod) mod.datastore.merge!(self.datastore) mod.datastore['SESSION'] = session.sid if !mod.datastore['SESSION'] && session end + def is_module_wanted?(mod) + ( + mod.kind_of?(Msf::Exploit::Local) && + mod.respond_to?(:check) && + is_session_type_compat?(mod) && + is_module_platform?(mod) && + is_module_arch?(mod) && + is_module_options_ready?(mod) + ) + end + + def setup - print_status "Collecting local exploits..." - #Initializes an array + print_status "Collecting local exploits for #{get_target_arch}/#{get_target_os}..." + # Initializes an array @local_exploits = [] # Collects exploits into an array framework.exploits.each do |name, obj| mod = framework.exploits.create(name) next unless mod set_module_options(mod) - if mod.kind_of?(Msf::Exploit::Local) && - mod.respond_to?(:check) && - is_module_platform?(mod) && - is_module_options_ready?(mod) - if !get_target_arch.empty? && is_module_arch?(mod) - @local_exploits << mod - else - @local_exploits << mod - end - end - end + @local_exploits << mod if is_module_wanted?(mod) + end + end + + def show_found_exploits + print_status("The following #{@local_exploits.length} exploit checks are being tried:") + @local_exploits.each do |x| + vprint_status(x.fullname) + end end def run - print_status("Number of exploits being tried: #{@local_exploits.length}") + if @local_exploits.length < 1 + print_error "No suggestions available." + return + end + + show_found_exploits + @local_exploits.each do |m| - begin - checkcode = m.check + begin + checkcode = m.check # See def is_check_interesting? if is_check_interesting?(checkcode) # Prints the full name and the checkcode message for the exploit print_good("#{m.fullname}: #{checkcode.second}") - #If the datastore option is true, a detailed description will show - if datastore['ShowDescription'] + # If the datastore option is true, a detailed description will show + if datastore['SHOWDESCRIPTION'] # Formatting for the description text print_line Rex::Text.wordwrap(Rex::Text.compress(m.description), 2, 70) - end - else - # Prints the full name and the checkcode message for the exploit - vprint_status("#{m.fullname}: #{checkcode.second}") end - # Creates a log record in framework.log - rescue Rex::Post::Meterpreter::RequestError => e - elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") - vprint_error("#{e.class} #{m.shortname} failled to run: #{e.message}") - end - if @local_exploits.length < 1 - print_error "No suggestions available. " - end + else + vprint_status("#{m.fullname}: #{checkcode.second}") + end + rescue Rex::Post::Meterpreter::RequestError => e + # Creates a log record in framework.log + elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") + vprint_error("#{e.class} #{m.shortname} failled to run: #{e.message}") + end end end + def is_check_interesting?(checkcode) [ Msf::Exploit::CheckCode::Vulnerable, @@ -165,15 +165,19 @@ class Metasploit3 < Msf::Post ].include?(checkcode) end + def print_status(msg='') super("#{session.session_host} - #{msg}") end + def print_good(msg='') super("#{session.session_host} - #{msg}") end + def print_error(msg='') super("#{session.session_host} - #{msg}") end + end