CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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