## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Post include Msf::Auxiliary::Report def initialize(info = {}) super( update_info( info, 'Name' => 'Multi Recon Local Exploit Suggester', 'Description' => %q{ This module suggests local meterpreter exploits that can be used. The exploits are suggested based on the architecture and platform that the user has a shell opened as well as the available exploits in meterpreter. It's important to note that not all local exploits will be fired. Exploits are chosen based on these conditions: session type, platform, architecture, and required default options. }, 'License' => MSF_LICENSE, '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]) ] register_advanced_options( [ Msf::OptBool.new('ValidateArch', [true, 'Validate architecture', true]), Msf::OptBool.new('ValidatePlatform', [true, 'Validate platform', true]), Msf::OptBool.new('ValidateMeterpreterCommands', [true, 'Validate Meterpreter commands', false]), Msf::OptString.new('Colors', [false, 'Valid, Invalid and Ignored colors for module checks (unset to disable)', 'grn/red/blu']) ] ) end def all_platforms Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase } end def session_arch # Prefer calling native arch when available, as most LPEs will require this (e.g. x86, x64) as opposed to Java/Python Meterpreter's values (e.g. Java, Python) session.respond_to?(:native_arch) ? session.native_arch : session.arch end def is_module_arch?(mod) mod_arch = mod.target.arch || mod.arch mod_arch.include?(session_arch) end def is_module_wanted?(mod) mod[:result][:incompatibility_reasons].empty? end def is_session_type?(mod) # There are some modules that do not define any compatible session types. # We could assume that means the module can run on all session types, # Or we could consider that as incorrect module metadata. mod.session_types.include?(session.type) end def is_module_platform?(mod) platform_obj = Msf::Module::Platform.find_platform session.platform return false if mod.target.nil? module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms module_platforms.include? platform_obj rescue ArgumentError => e # When not found, find_platform raises an ArgumentError elog('Could not find a platform', error: e) return false end def has_required_module_options?(mod) get_all_missing_module_options(mod).empty? end def get_all_missing_module_options(mod) missing_options = [] mod.options.each_pair do |option_name, option| missing_options << option_name if option.required && option.default.nil? && mod.datastore[option_name].blank? end missing_options end def valid_incompatibility_reasons(mod, verify_reasons) # As we can potentially ignore some `reasons` (e.g. accepting arch values which are, on paper, not compatible), # this keeps track of valid reasons why we will not consider the module that we are evaluating to be valid. valid_reasons = [] valid_reasons << "Missing required module options (#{get_all_missing_module_options(mod).join('. ')})" unless verify_reasons[:has_required_module_options] incompatible_opts = [] incompatible_opts << 'architecture' unless verify_reasons[:is_module_arch] incompatible_opts << 'platform' unless verify_reasons[:is_module_platform] incompatible_opts << 'session type' unless verify_reasons[:is_session_type] valid_reasons << "Not Compatible (#{incompatible_opts.join(', ')})" if incompatible_opts.any? valid_reasons << 'Missing/unloadable Meterpreter commands' if verify_reasons[:missing_meterpreter_commands].any? valid_reasons end def set_module_options(mod) datastore.each_pair do |k, v| mod.datastore[k] = v end if !mod.datastore['SESSION'] && session.present? mod.datastore['SESSION'] = session.sid end end def set_module_target(mod) session_platform = Msf::Module::Platform.find_platform(session.platform) target_index = mod.targets.find_index do |target| # If the target doesn't define its own compatible platforms or architectures, default to the parent (module) values. target_platforms = target.platform&.platforms || mod.platform.platforms target_architectures = target.arch || mod.arch target_platforms.include?(session_platform) && target_architectures.include?(session_arch) end mod.datastore['Target'] = target_index if target_index end def setup return unless session print_status "Collecting local exploits for #{session.session_type}..." setup_validation_options setup_color_options # Collects exploits into an array @local_exploits = [] exploit_refnames = framework.exploits.module_refnames exploit_refnames.each_with_index do |name, index| print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r" mod = framework.exploits.create name next unless mod set_module_options mod set_module_target mod verify_result = verify_mod(mod) @local_exploits << { module: mod, result: verify_result } if verify_result[:has_check] end end def verify_mod(mod) return { has_check: false } unless mod.is_a?(Msf::Exploit::Local) && mod.has_check? result = { has_check: true, is_module_platform: (@validate_platform ? is_module_platform?(mod) : true), is_module_arch: (@validate_arch ? is_module_arch?(mod) : true), has_required_module_options: has_required_module_options?(mod), missing_meterpreter_commands: (@validate_meterpreter_commands && session.type == 'meterpreter') ? meterpreter_session_incompatibility_reasons(session) : [], is_session_type: is_session_type?(mod) } result[:incompatibility_reasons] = valid_incompatibility_reasons(mod, result) result end def setup_validation_options @validate_arch = datastore['ValidateArch'] @validate_platform = datastore['ValidatePlatform'] @validate_meterpreter_commands = datastore['ValidateMeterpreterCommands'] end def setup_color_options @valid_color, @invalid_color, @ignored_color = (datastore['Colors'] || '').split('/') @valid_color = "%#{@valid_color}" unless @valid_color.blank? @invalid_color = "%#{@invalid_color}" unless @invalid_color.blank? @ignored_color = "%#{@ignored_color}" unless @ignored_color.blank? end def show_found_exploits unless datastore['VERBOSE'] print_status "#{@local_exploits.length} exploit checks are being tried..." return end vprint_status "The following #{@local_exploits.length} exploit checks are being tried:" @local_exploits.each do |x| vprint_status x[:module].fullname end end def run runnable_exploits = @local_exploits.select { |mod| is_module_wanted?(mod) } if runnable_exploits.empty? print_error 'No suggestions available.' vprint_line vprint_session_info vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) }) return end show_found_exploits results = runnable_exploits.map.with_index do |mod, index| print "%bld%blu[*]%clr Running check method for exploit #{index + 1} / #{runnable_exploits.count}\r" begin checkcode = mod[:module].check rescue StandardError => e elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e) vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}" next { module: mod[:module], errors: ['The check raised an exception.'] } end if checkcode.nil? vprint_error "Check failed with #{mod[:module].fullname} for unknown reasons" next { module: mod[:module], errors: ['The check failed for unknown reasons.'] } end # See def is_check_interesting? unless is_check_interesting? checkcode vprint_status "#{mod[:module].fullname}: #{checkcode.message}" next { module: mod[:module], errors: [checkcode.message] } end # Prints the full name and the checkcode message for the exploit print_good "#{mod[:module].fullname}: #{checkcode.message}" # If the datastore option is true, a detailed description will show if datastore['SHOWDESCRIPTION'] # Formatting for the description text Rex::Text.wordwrap(Rex::Text.compress(mod[:module].description), 2, 70).split(/\n/).each do |line| print_line line end end next { module: mod[:module], checkcode: checkcode.message } end print_line print_status valid_modules_table(results) vprint_line vprint_session_info vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) }) report_data = [] results.each do |result| report_data << [result[:module].fullname, result[:checkcode]] if result[:checkcode] end report_note( host: session.session_host, type: 'local.suggested_exploits', data: report_data ) end def valid_modules_table(results) name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new check_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new # Split all the results by their checkcode. # We want the modules that returned a checkcode to be at the top. checkcode_rows, without_checkcode_rows = results.partition { |result| result[:checkcode] } rows = (checkcode_rows + without_checkcode_rows).map.with_index do |result, index| color = result[:checkcode] ? @valid_color : @invalid_color check_res = result.fetch(:checkcode) { result[:errors].join('. ') } name_styler.merge!({ result[:module].fullname => color }) check_styler.merge!({ check_res => color }) [ index + 1, result[:module].fullname, result[:checkcode] ? 'Yes' : 'No', check_res ] end Rex::Text::Table.new( 'Header' => "Valid modules for session #{session.sid}:", 'Indent' => 1, 'Columns' => [ '#', 'Name', 'Potentially Vulnerable?', 'Check Result' ], 'SortIndex' => -1, 'WordWrap' => false, # Don't wordwrap as it messes up coloured output when it is broken up into more than one line 'ColProps' => { 'Name' => { 'Stylers' => [name_styler] }, 'Potentially Vulnerable?' => { 'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => @valid_color, 'No' => @invalid_color })] }, 'Check Result' => { 'Stylers' => [check_styler] } }, 'Rows' => rows ) end def unwanted_modules_table(unwanted_modules) arch_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new platform_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new session_type_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new rows = unwanted_modules.map.with_index do |mod, index| platforms = mod[:module].target.platform&.platforms&.any? ? mod[:module].target.platform.platforms : mod[:module].platform.platforms platforms ||= [] arch = mod[:module].target.arch&.any? ? mod[:module].target.arch : mod[:module].arch arch ||= [] arch.each do |a| if a != session_arch if @validate_arch color = @invalid_color else color = @ignored_color end else color = @valid_color end arch_styler.merge!({ a.to_s => color }) end platforms.each do |module_platform| if module_platform != ::Msf::Module::Platform.find_platform(session.platform) if @validate_platform color = @invalid_color else color = @ignored_color end else color = @valid_color end platform_styler.merge!({ module_platform.realname => color }) end mod[:module].session_types.each do |session_type| color = session_type == session.type ? @valid_color : @invalid_color session_type_styler.merge!(session_type.to_s => color) end [ index + 1, mod[:module].fullname, mod[:result][:incompatibility_reasons].join('. '), platforms.map(&:realname).sort.join(', '), arch.any? ? arch.sort.join(', ') : 'No defined architectures', mod[:module].session_types.any? ? mod[:module].session_types.sort.join(', ') : 'No defined session types' ] end Rex::Text::Table.new( 'Header' => "Incompatible modules for session #{session.sid}:", 'Indent' => 1, 'Columns' => [ '#', 'Name', 'Reasons', 'Platform', 'Architecture', 'Session Type' ], 'WordWrap' => false, 'ColProps' => { 'Architecture' => { 'Stylers' => [arch_styler] }, 'Platform' => { 'Stylers' => [platform_styler] }, 'Session Type' => { 'Stylers' => [session_type_styler] } }, 'Rows' => rows ) end def vprint_session_info vprint_status 'Current Session Info:' vprint_status "Session Type: #{session.type}" vprint_status "Architecture: #{session_arch}" vprint_status "Platform: #{session.platform}" end def is_check_interesting?(checkcode) [ Msf::Exploit::CheckCode::Vulnerable, Msf::Exploit::CheckCode::Appears, Msf::Exploit::CheckCode::Detected ].include? checkcode end def print_status(msg = '') super(session ? "#{session.session_host} - #{msg}" : msg) end def print_good(msg = '') super(session ? "#{session.session_host} - #{msg}" : msg) end def print_error(msg = '') super(session ? "#{session.session_host} - #{msg}" : msg) end end