##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
  include Msf::Post::Android::Priv

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Multiplatform Installed Software Version Enumerator',
        'Description' => %q{
          This module, when run against a compromised machine, will gather details on all installed software,
          including their versions and if available, when they were installed, and will save it into a loot file for later use.
          Users can then use this loot file to determine what additional vulnerabilites may affect the target machine.

          Note that for Linux systems, software enumeration is done via package managers. As a result the results may
          not reflect all of the available software on the system simply because users may have installed additional
          software from alternative sources such as source code that these package managers are not aware of.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'gwillcox-r7' ],
        'Platform' => %w[win linux osx bsd solaris android],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )
  end

  def store_linux_loot(listing)
    file = store_loot('host.linux.software.versions', 'text/plain', session, listing, 'installed_software.txt', 'Installed Software and Versions')
    print_good("Stored information about the installed products to the loot file at #{file}")
  end

  def enumerate_android_packages
    if command_exists?('pm') == false
      print_error("The command 'pm' does not exist on the host")
      return nil
    end
    listing = cmd_exec('pm list packages -f').to_s
    if listing.empty?
      print_error('No results were returned when trying to get software installed on the Linux host. An error likely occured.')
      return nil
    end
    listing
  end

  # Run Method for when run command is issued
  def run
    case session.platform
    when 'windows'
      if command_exists?('wmic') == false
        print_error("The 'wmic' command doesn't exist on this host!") # wmic is technically marked as depreciated so this command could very well be removed in future releases.
        return
      end
      listing = cmd_exec('wmic product get Name, Description, Version, InstallDate', nil, 6000).to_s
      unless listing.include?('Description')
        print_error('Was unable to get a listing of installed products...')
        return
      end
      file = store_loot('host.windows.software.versions', 'text/plain', session, listing, 'installed_software.txt', 'Installed Software and Versions')
      print_good("Stored information about the installed products to the loot file at #{file}")
    when 'linux'
      # All of the following options were taken from https://distrowatch.com/dwres.php?resource=package-management
      # and further verified against VMs that were set up in testing labs.
      if command_exists?('apt') # Debian, Ubuntu, and Debian derived distros.
        cmd = %w[apt list --installed]
      elsif command_exists?('dpkg') # Alternative for Debian based systems
        cmd = %w[dpkg -l]
      elsif command_exists?('pacman') # Arch and Manjaro are two popular examples
        cmd = %w[pacman -Q]
      elsif command_exists?('zypper') # OpenSUSE is a popular example
        cmd = %w[zypper search -is]
      elsif command_exists?('rpm') # Fedora, Centos, RHEL
        cmd = %w[rpm -qa]
      elsif command_exists?('apk') # Apline
        cmd = %w[apk info -v]
      elsif command_exists?('qlist') # Gentoo
        cmd = %w[qlist -Iv]
      elsif command_exists?('pkg') # FreeBSD
        cmd = %w[pkg info]
      elsif command_exists?('equo') # Sabayon
        cmd = %w[equo q list installed -v]
      elsif command_exists?('nix-env')
        cmd = %w[nix-env -q]
      else
        print_error("The target system either doesn't have a package manager system, or does not use a known package manager system!")
        print_error('Unable to enumerate the software on the target system. Exiting...')
        return nil
      end

      if command_exists?((cmd[0]).to_s) == false
        print_error("The command #{cmd[0]} was not found on the target.")
        return
      else
        listing = cmd_exec(cmd.join(' ')).to_s
        if listing.empty?
          print_error('No results were returned when trying to get software installed on the Linux host. An error likely occured.')
          return
        end
        store_linux_loot(listing)
      end
    when 'bsd', 'solaris'
      if command_exists?('pkg') == false
        print_error("The command 'pkg' does not exist on the host")
        return
      end
      listing = cmd_exec('pkg info').to_s
      if listing.empty?
        print_error('No results were returned when trying to get software installed on the BSD/Solaris host. An error likely occured.')
        return
      end
      file = store_loot('host.bsd.solaris.software.versions', 'text/plain', session, listing, 'installed_software.txt', 'Installed Software and Versions')
      print_good("Stored information about the installed products to the loot file at #{file}")
    when 'osx'
      listing = ''
      if command_exists?('system_profiler') == false
        print_error("The command 'system_profiler' does not exist on the host! Something is seriously wrong!")
        return
      end
      command_result = cmd_exec('system_profiler SPApplicationsDataType').to_s
      if command_result.empty?
        print_error('No results were returned when trying to get software installed on the OSX host via system_profiler!')
        return
      end
      listing += command_result

      # Start enumerating other potential MacOS package managers now that
      # the main system app manager has been enumerated.
      if command_exists?('brew') # HomeBrew
        listing += "\n\n----------------Brew Packages----------------\n"
        listing += cmd_exec('brew list --versions')
      end

      if command_exists?('port') # MacPorts
        listing += "\n\n----------------MacPorts Packages----------------\n"
        listing += cmd_exec('port installed')
      end

      file = store_loot('host.osx.software.versions', 'text/plain', session, listing, 'installed_software.txt', 'Installed Software and Versions')
      print_good("Stored information about the installed products to the loot file at #{file}")
    when 'android'
      if is_root?
        if command_exists?('dumpsys') == false
          print_error("Something is odd with this Android device. You are root but the dumpsys command doesn't exist. Perhaps the device is too old?")
          return
        end
        listing = cmd_exec('dumpsys package packages').to_s
        if listing.empty?
          print_error('Something went wrong with the command and no output was returned!')
          return
        elsif listing =~ /android.permission.DUMP/
          print_warning('You do not have the permissions needed to dump the versions of software installed. Reverting to just enumerating what software is installed.')
          listing = enumerate_android_packages
          return if listing.nil?
        end
      else
        print_warning('You do not have the permissions needed to dump the versions of software installed. Reverting to just enumerating what software is installed.')
        listing = enumerate_android_packages
        return if listing.nil?
      end
      file = store_loot('host.android.software.versions', 'text/plain', session, listing, 'installed_software.txt', 'Installed Software and Versions')
      print_good("Stored information about the installed products to the loot file at #{file}")
    end
  end
end
