##
# 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])
    ]
  end

  def all_platforms
    Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase }
  end

  def is_module_arch?(mod)
    mod_arch = mod.target.arch || mod.arch
    mod_arch.include? session.arch
  end

  def is_module_options_ready?(mod)
    mod.options.each_pair do |option_name, option|
      return false if option.required && option.default.nil? && mod.datastore[option_name].blank?
    end

    true
  end

  def is_module_platform?(mod)
    platform_obj = Msf::Module::Platform.find_platform session.platform
    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 is_session_type_compat?(mod)
    mod.session_compatible? session.sid
  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 is_module_wanted?(mod)
    (
      mod.kind_of?(Msf::Exploit::Local) &&
      mod.has_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 for #{session.session_type}..."
    # 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
      next unless is_module_wanted? mod
      @local_exploits << mod
    end
  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.fullname
    end
  end

  def run
    if @local_exploits.empty?
      print_error 'No suggestions available.'
      return
    end

    show_found_exploits
    results = []
    @local_exploits.each do |m|
      begin
        checkcode = m.check

        if checkcode.nil?
          vprint_error "#{m.fullname}: Check failed"
          next
        end

        # See def is_check_interesting?
        unless is_check_interesting? checkcode
          vprint_status "#{m.fullname}: #{checkcode.message}"
          next
        end

        # Prints the full name and the checkcode message for the exploit
        print_good "#{m.fullname}: #{checkcode.message}"
        results << [m.fullname, checkcode.message]

        # If the datastore option is true, a detailed description will show
        next unless datastore['SHOWDESCRIPTION']

        # Formatting for the description text
        Rex::Text.wordwrap(Rex::Text.compress(m.description), 2, 70).split(/\n/).each do |line|
          print_line line
        end
      rescue Rex::Post::Meterpreter::RequestError => e
        # Creates a log record in framework.log
        elog("#{m.shortname} failed to run", error: e)
        vprint_error "#{e.class} #{m.shortname} failed to run: #{e.message}"
      end
    end

    report_note(
      :host => rhost,
      :type => 'local.suggested_exploits',
      :data => results
    )
  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_host} - #{msg}")
  end

  def print_good(msg='')
    super("#{session.session_host} - #{msg}")
  end

  def print_error(msg='')
    super("#{session.session_host} - #{msg}")
  end
end
