Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/base/sessions/meterpreter.rb
33056 views
1
# -*- coding: binary -*-
2
require 'rex/post/meterpreter/client'
3
require 'rex/post/meterpreter/ui/console'
4
5
module Msf
6
module Sessions
7
8
###
9
#
10
# This class represents a session compatible interface to a meterpreter server
11
# instance running on a remote machine. It provides the means of interacting
12
# with the server instance both at an API level as well as at a console level.
13
#
14
###
15
16
class Meterpreter < Rex::Post::Meterpreter::Client
17
18
include Msf::Session
19
#
20
# The meterpreter session is interactive
21
#
22
include Msf::Session::Interactive
23
include Msf::Session::Comm
24
25
#
26
# This interface supports interacting with a single command shell.
27
#
28
include Msf::Session::Provider::SingleCommandShell
29
30
include Msf::Sessions::Scriptable
31
32
# Override for server implementations that can't do SSL
33
def supports_ssl?
34
true
35
end
36
37
# Override for server implementations that can't do zlib
38
def supports_zlib?
39
true
40
end
41
42
def tunnel_to_s
43
if self.pivot_session
44
"Pivot via [#{self.pivot_session.tunnel_to_s}]"
45
else
46
super
47
end
48
end
49
50
#
51
# Initializes a meterpreter session instance using the supplied rstream
52
# that is to be used as the client's connection to the server.
53
#
54
def initialize(rstream, opts={})
55
super
56
57
opts[:capabilities] = {
58
:ssl => supports_ssl?,
59
:zlib => supports_zlib?
60
}
61
62
# The caller didn't request to skip ssl, so make sure we support it
63
if not opts[:skip_ssl]
64
opts.merge!(:skip_ssl => (not supports_ssl?))
65
end
66
67
#
68
# Parse options passed in via the datastore
69
#
70
71
# Extract the HandlerSSLCert option if specified by the user
72
if opts[:datastore] and opts[:datastore]['HandlerSSLCert']
73
opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert']
74
end
75
76
# Extract the MeterpreterDebugBuild option if specified by the user
77
if opts[:datastore]
78
opts[:debug_build] = opts[:datastore]['MeterpreterDebugBuild']
79
end
80
81
# Don't pass the datastore into the init_meterpreter method
82
opts.delete(:datastore)
83
84
# Assume by default that 10 threads is a safe number for this session
85
self.max_threads ||= 10
86
87
#
88
# Initialize the meterpreter client
89
#
90
self.init_meterpreter(rstream, opts)
91
92
#
93
# Create the console instance
94
#
95
self.console = Rex::Post::Meterpreter::Ui::Console.new(self)
96
end
97
98
def exit
99
begin
100
self.core.shutdown
101
rescue StandardError
102
nil
103
end
104
self.shutdown_passive_dispatcher
105
self.console.stop
106
end
107
#
108
# Returns the session type as being 'meterpreter'.
109
#
110
def self.type
111
"meterpreter"
112
end
113
114
#
115
# Calls the class method
116
#
117
def type
118
self.class.type
119
end
120
121
def self.can_cleanup_files
122
true
123
end
124
125
##
126
# :category: Msf::Session::Provider::SingleCommandShell implementors
127
#
128
# Create a channelized shell process on the target
129
#
130
def shell_init
131
return true if @shell
132
133
# COMSPEC is special-cased on all meterpreters to return a viable
134
# shell.
135
sh = sys.config.getenv('COMSPEC')
136
@shell = sys.process.execute(sh, nil, { "Hidden" => true, "Channelized" => true })
137
138
end
139
140
def load_embedded_extensions
141
# Rex::Post::Meterpreter::ExtensionMapper.get_extension_klasses
142
143
# First of all, let's see if we have stdapi.
144
commands = self.core.get_loaded_extension_commands('stdapi')
145
console.run_single("load stdapi") if commands.length > 0
146
147
return if self.platform != 'windows'
148
149
exts = Set.new
150
exts.merge(binary_suffix.map { |suffix| MetasploitPayloads.list_meterpreter_extensions(suffix) }.flatten)
151
exts = exts.sort.uniq
152
153
exts.each { |e|
154
commands = self.core.get_loaded_extension_commands(e.downcase)
155
if commands.length > 0 && !e.downcase.starts_with?('stdapi')
156
console.run_single("load #{e.downcase}")
157
end
158
}
159
end
160
161
def bootstrap(datastore = {}, handler = nil)
162
session = self
163
164
# Configure unicode encoding before loading stdapi
165
session.encode_unicode = datastore['EnableUnicodeEncoding']
166
167
session.init_ui(self.user_input, self.user_output)
168
169
initialize_tlv_logging(datastore['SessionTlvLogging']) unless datastore['SessionTlvLogging'].nil?
170
171
verification_timeout = datastore['AutoVerifySessionTimeout']&.to_i || session.comm_timeout
172
begin
173
session.tlv_enc_key = session.core.negotiate_tlv_encryption(timeout: verification_timeout)
174
rescue Rex::TimeoutError
175
end
176
177
if session.tlv_enc_key.nil?
178
# Fail-closed if TLV encryption can't be negotiated (close the session as invalid)
179
dlog("Session #{session.sid} failed to negotiate TLV encryption")
180
print_error("Meterpreter session #{session.sid} is not valid and will be closed")
181
# Terminate the session without cleanup if it did not validate
182
session.skip_cleanup = true
183
session.kill
184
return nil
185
end
186
187
# always make sure that the new session has a new guid if it's not already known
188
guid = session.session_guid
189
if guid == "\x00" * 16
190
guid = [SecureRandom.uuid.gsub('-', '')].pack('H*')
191
session.core.set_session_guid(guid)
192
session.session_guid = guid
193
# TODO: New stageless session, do some account in the DB so we can track it later.
194
else
195
# TODO: This session was either staged or previously known, and so we should do some accounting here!
196
end
197
198
session.commands.concat(session.core.get_loaded_extension_commands('core'))
199
if session.tlv_enc_key[:weak_key?]
200
print_warning("Meterpreter session #{session.sid} is using a weak encryption key.")
201
print_warning('Meterpreter start up operations have been aborted. Use the session at your own risk.')
202
return nil
203
end
204
205
original = console.disable_output
206
console.disable_output = true
207
208
load_embedded_extensions
209
210
extensions = datastore['AutoLoadExtensions']&.delete(' ')&.split(',') || []
211
212
# BEGIN: This should be removed on MSF 7
213
# Unhook the process prior to loading stdapi to reduce logging/inspection by any AV/PSP (by default unhook is first, see meterpreter_options/windows.rb)
214
# The unhook extension is broken. reference: https://github.com/rapid7/metasploit-framework/pull/20514
215
216
#extensions.push('unhook') if datastore['AutoUnhookProcess'] && session.platform == 'windows'
217
extensions.push('stdapi') if datastore['AutoLoadStdapi']
218
extensions.push('priv') if datastore['AutoLoadStdapi'] && session.platform == 'windows'
219
extensions.push('android') if session.platform == 'android'
220
extensions = extensions.uniq
221
# END
222
# TODO: abstract this a little, perhaps a "post load" function that removes
223
# platform-specific stuff?
224
extensions.each do |extension|
225
begin
226
console.run_single("load #{extension}")
227
# console.run_single('unhook_pe') if extension == 'unhook'
228
session.load_session_info if extension == 'stdapi' && datastore['AutoSystemInfo']
229
rescue => e
230
print_warning("Failed loading extension #{extension}")
231
end
232
end
233
console.disable_output = original
234
235
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
236
unless datastore[key].nil? || datastore[key].empty?
237
args = Shellwords.shellwords(datastore[key])
238
print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
239
session.execute_script(args.shift, *args)
240
end
241
end
242
end
243
244
##
245
# :category: Msf::Session::Provider::SingleCommandShell implementors
246
#
247
# Read from the command shell.
248
#
249
def shell_read(length=nil, timeout=1)
250
shell_init
251
252
length = nil if length.nil? or length < 0
253
begin
254
rv = nil
255
# Meterpreter doesn't offer a way to timeout on the victim side, so
256
# we have to do it here. I'm concerned that this will cause loss
257
# of data.
258
Timeout.timeout(timeout) {
259
rv = @shell.channel.read(length)
260
}
261
framework.events.on_session_output(self, rv) if rv
262
return rv
263
rescue ::Timeout::Error
264
return nil
265
rescue ::Exception => e
266
shell_close
267
raise e
268
end
269
end
270
271
##
272
# :category: Msf::Session::Provider::SingleCommandShell implementors
273
#
274
# Write to the command shell.
275
#
276
def shell_write(buf)
277
shell_init
278
279
begin
280
framework.events.on_session_command(self, buf.strip)
281
len = @shell.channel.write("#{buf}\n")
282
rescue ::Exception => e
283
shell_close
284
raise e
285
end
286
287
len
288
end
289
290
##
291
# :category: Msf::Session::Provider::SingleCommandShell implementors
292
#
293
# Terminate the shell channel
294
#
295
def shell_close
296
@shell.close
297
@shell = nil
298
end
299
300
def shell_command(cmd, timeout = 5)
301
# Send the shell channel's stdin.
302
shell_write(cmd + "\n")
303
304
etime = ::Time.now.to_f + timeout
305
buff = ""
306
307
# Keep reading data until no more data is available or the timeout is
308
# reached.
309
while (::Time.now.to_f < etime)
310
res = shell_read(-1, timeout)
311
break unless res
312
timeout = etime - ::Time.now.to_f
313
buff << res
314
end
315
316
buff
317
end
318
319
#
320
# Called by PacketDispatcher to resolve error codes to names.
321
# This is the default version (return the number itself)
322
#
323
def lookup_error(code)
324
"#{code}"
325
end
326
327
##
328
# :category: Msf::Session overrides
329
#
330
# Cleans up the meterpreter client session.
331
#
332
def cleanup
333
cleanup_meterpreter
334
335
super
336
end
337
338
##
339
# :category: Msf::Session overrides
340
#
341
# Returns the session description.
342
#
343
def desc
344
"Meterpreter"
345
end
346
347
348
##
349
# :category: Msf::Session::Scriptable implementors
350
#
351
# Runs the Meterpreter script or resource file.
352
#
353
def execute_file(full_path, args)
354
# Infer a Meterpreter script by .rb extension
355
if File.extname(full_path) == '.rb'
356
Rex::Script::Meterpreter.new(self, full_path).run(args)
357
else
358
console.load_resource(full_path)
359
end
360
end
361
362
363
##
364
# :category: Msf::Session::Interactive implementors
365
#
366
# Initializes the console's I/O handles.
367
#
368
def init_ui(input, output)
369
self.user_input = input
370
self.user_output = output
371
console.init_ui(input, output)
372
console.set_log_source(log_source)
373
374
super
375
end
376
377
##
378
# :category: Msf::Session::Interactive implementors
379
#
380
# Resets the console's I/O handles.
381
#
382
def reset_ui
383
console.unset_log_source
384
console.reset_ui
385
end
386
387
#
388
# Terminates the session
389
#
390
def kill(reason='')
391
begin
392
cleanup_meterpreter
393
self.sock.close if self.sock
394
rescue ::Exception
395
end
396
# deregister will actually trigger another cleanup
397
framework.sessions.deregister(self, reason)
398
end
399
400
#
401
# Run the supplied command as if it came from suer input.
402
#
403
def queue_cmd(cmd)
404
console.queue_cmd(cmd)
405
end
406
407
##
408
# :category: Msf::Session::Interactive implementors
409
#
410
# Explicitly runs a command in the meterpreter console.
411
#
412
def run_cmd(cmd,output_object=nil)
413
stored_output_state = nil
414
# If the user supplied an Output IO object, then we tell
415
# the console to use that, while saving it's previous output/
416
if output_object
417
stored_output_state = console.output
418
console.send(:output=, output_object)
419
end
420
success = console.run_single(cmd)
421
# If we stored the previous output object of the channel
422
# we restore it here to put everything back the way we found it
423
# We re-use the conditional above, because we expect in many cases for
424
# the stored state to actually be nil here.
425
if output_object
426
console.send(:output=,stored_output_state)
427
end
428
success
429
end
430
431
#
432
# Load the stdapi extension.
433
#
434
def load_stdapi
435
original = console.disable_output
436
console.disable_output = true
437
console.run_single('load stdapi')
438
console.disable_output = original
439
end
440
441
#
442
# Load the priv extension.
443
#
444
def load_priv
445
original = console.disable_output
446
console.disable_output = true
447
console.run_single('load priv')
448
console.disable_output = original
449
end
450
451
def update_session_info
452
# sys.config.getuid, and fs.dir.getwd cache their results, so update them
453
begin
454
fs&.dir&.getwd
455
rescue Rex::Post::Meterpreter::RequestError => e
456
elog('failed retrieving working directory', error: e)
457
end
458
username = self.sys.config.getuid
459
sysinfo = self.sys.config.sysinfo
460
461
# when updating session information, we need to make sure we update the platform
462
# in the UUID to match what the target is actually running on, but only for a
463
# subset of platforms.
464
if ['java', 'python', 'php'].include?(self.platform)
465
new_platform = guess_target_platform(sysinfo['OS'])
466
if self.platform != new_platform
467
self.payload_uuid.platform = new_platform
468
self.core.set_uuid(self.payload_uuid)
469
end
470
end
471
472
safe_info = "#{username} @ #{sysinfo['Computer']}"
473
safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)
474
# Should probably be using Rex::Text.ascii_safe_hex but leave
475
# this as is for now since "\xNN" is arguably uglier than "_"
476
# showing up in various places in the UI.
477
safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")
478
self.info = safe_info
479
end
480
481
def guess_target_platform(os)
482
case os
483
when /windows/i
484
Msf::Module::Platform::Windows.realname.downcase
485
when /darwin/i
486
Msf::Module::Platform::OSX.realname.downcase
487
when /mac os ?x/i
488
# this happens with java on OSX (for real!)
489
Msf::Module::Platform::OSX.realname.downcase
490
when /freebsd/i
491
Msf::Module::Platform::FreeBSD.realname.downcase
492
when /openbsd/i, /netbsd/i
493
Msf::Module::Platform::BSD.realname.downcase
494
else
495
Msf::Module::Platform::Linux.realname.downcase
496
end
497
end
498
499
#
500
# Populate the session information.
501
#
502
# Also reports a session_fingerprint note for host os normalization.
503
#
504
def load_session_info
505
begin
506
::Timeout.timeout(60) do
507
update_session_info
508
509
hobj = nil
510
511
nhost = find_internet_connected_address
512
513
original_session_host = self.session_host
514
# If we found a better IP address for this session, change it
515
# up. Only handle cases where the DB is not connected here
516
if nhost && !(framework.db && framework.db.active)
517
self.session_host = nhost
518
end
519
520
# The rest of this requires a database, so bail if it's not
521
# there
522
return if !(framework.db && framework.db.active)
523
524
::ApplicationRecord.connection_pool.with_connection {
525
wspace = framework.db.find_workspace(workspace)
526
527
# Account for finding ourselves on a different host
528
if nhost and self.db_record
529
# Create or switch to a new host in the database
530
hobj = framework.db.report_host(:workspace => wspace, :host => nhost)
531
if hobj
532
self.session_host = nhost
533
self.db_record.host_id = hobj[:id]
534
end
535
end
536
537
sysinfo = sys.config.sysinfo
538
host = Msf::Util::Host.normalize_host(self)
539
540
framework.db.report_note({
541
:type => "host.os.session_fingerprint",
542
:host => host,
543
:workspace => wspace,
544
:data => {
545
:name => sysinfo["Computer"],
546
:os => sysinfo["OS"],
547
:arch => sysinfo["Architecture"],
548
}
549
})
550
551
if self.db_record
552
framework.db.update_session(self)
553
end
554
555
# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint
556
# framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)
557
558
if nhost
559
framework.db.report_note({
560
:type => "host.nat.server",
561
:host => original_session_host,
562
:workspace => wspace,
563
:data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost },
564
:update => :unique_data
565
})
566
framework.db.report_host(:host => original_session_host, :purpose => 'firewall' )
567
568
framework.db.report_note({
569
:type => "host.nat.client",
570
:host => nhost,
571
:workspace => wspace,
572
:data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host },
573
:update => :unique_data
574
})
575
framework.db.report_host(:host => nhost, :purpose => 'client' )
576
end
577
}
578
579
end
580
rescue ::Interrupt
581
dlog("Interrupt while loading sysinfo: #{e.class}: #{e}")
582
raise $!
583
rescue ::Exception => e
584
# Log the error but otherwise ignore it so we don't kill the
585
# session if reporting failed for some reason
586
elog('Error loading sysinfo', error: e)
587
dlog("Call stack:\n#{e.backtrace.join("\n")}")
588
end
589
end
590
591
##
592
# :category: Msf::Session::Interactive implementors
593
#
594
# Interacts with the meterpreter client at a user interface level.
595
#
596
def _interact
597
framework.events.on_session_interact(self)
598
599
console.framework = framework
600
if framework.datastore['MeterpreterPrompt']
601
console.update_prompt(framework.datastore['MeterpreterPrompt'])
602
end
603
# Call the console interaction subsystem of the meterpreter client and
604
# pass it a block that returns whether or not we should still be
605
# interacting. This will allow the shell to abort if interaction is
606
# canceled.
607
console.interact { self.interacting != true }
608
console.framework = nil
609
610
# If the stop flag has been set, then that means the user exited. Raise
611
# the EOFError so we can drop this handle like a bad habit.
612
raise EOFError if (console.stopped? == true)
613
end
614
615
616
##
617
# :category: Msf::Session::Comm implementors
618
#
619
# Creates a connection based on the supplied parameters and returns it to
620
# the caller. The connection is created relative to the remote machine on
621
# which the meterpreter server instance is running.
622
#
623
def create(param)
624
sock = nil
625
626
# Notify handlers before we create the socket
627
notify_before_socket_create(self, param)
628
629
sock = net.socket.create(param)
630
631
# Notify now that we've created the socket
632
notify_socket_created(self, sock, param)
633
634
# Return the socket to the caller
635
sock
636
end
637
638
def supports_udp?
639
true
640
end
641
642
#
643
# Get a string representation of the current session platform
644
#
645
def platform
646
if self.payload_uuid
647
# return the actual platform of the current session if it's there
648
self.payload_uuid.platform
649
else
650
# otherwise just use the base for the session type tied to this handler.
651
# If we don't do this, storage of sessions in the DB dies
652
self.base_platform
653
end
654
end
655
656
#
657
# Get a string representation of the current session architecture
658
#
659
def arch
660
if self.payload_uuid
661
# return the actual arch of the current session if it's there
662
self.payload_uuid.arch
663
else
664
# otherwise just use the base for the session type tied to this handler.
665
# If we don't do this, storage of sessions in the DB dies
666
self.base_arch
667
end
668
end
669
670
#
671
# Get a string representation of the architecture of the process in which the
672
# current session is running. This defaults to the same value of arch but can
673
# be overridden by specific meterpreter implementations to add support.
674
#
675
def native_arch
676
arch
677
end
678
679
#
680
# Generate a binary suffix based on arch
681
#
682
def binary_suffix
683
# generate a file/binary suffix based on the current arch and platform.
684
# Platform-agnostic archs go first
685
case self.arch
686
when 'java'
687
['jar']
688
when 'php'
689
['php']
690
when 'python'
691
['py']
692
else
693
# otherwise we fall back to the platform
694
case self.platform
695
when 'windows'
696
["#{self.arch}.dll"]
697
when 'linux' , 'aix' , 'hpux' , 'irix' , 'unix'
698
['bin', 'elf']
699
when 'osx'
700
['elf']
701
when 'android', 'java'
702
['jar']
703
when 'php'
704
['php']
705
when 'python'
706
['py']
707
else
708
nil
709
end
710
end
711
end
712
713
# These are the base arch/platform for the original payload, required for when the
714
# session is first created thanks to the fact that the DB session recording
715
# happens before the session is even established.
716
attr_accessor :base_arch
717
attr_accessor :base_platform
718
719
attr_accessor :console # :nodoc:
720
attr_accessor :skip_ssl
721
attr_accessor :skip_cleanup
722
attr_accessor :target_id
723
attr_accessor :max_threads
724
725
protected
726
727
attr_accessor :rstream # :nodoc:
728
729
# Rummage through this host's routes and interfaces looking for an
730
# address that it uses to talk to the internet.
731
#
732
# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces
733
# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes
734
# @return [String] The address from which this host reaches the
735
# internet, as ASCII. e.g.: "192.168.100.156"
736
# @return [nil] If there is an interface with an address that matches
737
# {#session_host}
738
def find_internet_connected_address
739
740
ifaces = self.net.config.get_interfaces().flatten rescue []
741
routes = self.net.config.get_routes().flatten rescue []
742
743
# Try to match our visible IP to a real interface
744
found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } })
745
nhost = nil
746
747
# If the host has no address that matches what we see, then one of
748
# us is behind NAT so we have to look harder.
749
if !found
750
# Grab all routes to the internet
751
default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" }
752
753
default_routes.each do |route|
754
# Now try to find an interface whose network includes this
755
# Route's gateway, which means it's the one the host uses to get
756
# to the interweb.
757
ifaces.each do |i|
758
# Try all the addresses this interface has configured
759
addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask|
760
bits = Rex::Socket.net2bitmask( netmask )
761
range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil
762
763
!!(range && range.valid? && range.include?(route.gateway))
764
end
765
if addr_and_mask
766
nhost = addr_and_mask[0]
767
break
768
end
769
end
770
break if nhost
771
end
772
773
if !nhost
774
# No internal address matches what we see externally and no
775
# interface has a default route. Fall back to the first
776
# non-loopback address
777
non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" }
778
if non_loopback
779
nhost = non_loopback.ip
780
end
781
end
782
end
783
784
nhost
785
end
786
787
end
788
789
end
790
end
791
792