Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/base/sessions/meterpreter.rb
Views: 11784
# -*- coding: binary -*-1require 'rex/post/meterpreter/client'2require 'rex/post/meterpreter/ui/console'34module Msf5module Sessions67###8#9# This class represents a session compatible interface to a meterpreter server10# instance running on a remote machine. It provides the means of interacting11# with the server instance both at an API level as well as at a console level.12#13###1415class Meterpreter < Rex::Post::Meterpreter::Client1617include Msf::Session18#19# The meterpreter session is interactive20#21include Msf::Session::Interactive22include Msf::Session::Comm2324#25# This interface supports interacting with a single command shell.26#27include Msf::Session::Provider::SingleCommandShell2829include Msf::Sessions::Scriptable3031# Override for server implementations that can't do SSL32def supports_ssl?33true34end3536# Override for server implementations that can't do zlib37def supports_zlib?38true39end4041def tunnel_to_s42if self.pivot_session43"Pivot via [#{self.pivot_session.tunnel_to_s}]"44else45super46end47end4849#50# Initializes a meterpreter session instance using the supplied rstream51# that is to be used as the client's connection to the server.52#53def initialize(rstream, opts={})54super5556opts[:capabilities] = {57:ssl => supports_ssl?,58:zlib => supports_zlib?59}6061# The caller didn't request to skip ssl, so make sure we support it62if not opts[:skip_ssl]63opts.merge!(:skip_ssl => (not supports_ssl?))64end6566#67# Parse options passed in via the datastore68#6970# Extract the HandlerSSLCert option if specified by the user71if opts[:datastore] and opts[:datastore]['HandlerSSLCert']72opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert']73end7475# Extract the MeterpreterDebugBuild option if specified by the user76if opts[:datastore]77opts[:debug_build] = opts[:datastore]['MeterpreterDebugBuild']78end7980# Don't pass the datastore into the init_meterpreter method81opts.delete(:datastore)8283# Assume by default that 10 threads is a safe number for this session84self.max_threads ||= 108586#87# Initialize the meterpreter client88#89self.init_meterpreter(rstream, opts)9091#92# Create the console instance93#94self.console = Rex::Post::Meterpreter::Ui::Console.new(self)95end9697def exit98begin99self.core.shutdown100rescue StandardError101nil102end103self.shutdown_passive_dispatcher104self.console.stop105end106#107# Returns the session type as being 'meterpreter'.108#109def self.type110"meterpreter"111end112113#114# Calls the class method115#116def type117self.class.type118end119120def self.can_cleanup_files121true122end123124##125# :category: Msf::Session::Provider::SingleCommandShell implementors126#127# Create a channelized shell process on the target128#129def shell_init130return true if @shell131132# COMSPEC is special-cased on all meterpreters to return a viable133# shell.134sh = sys.config.getenv('COMSPEC')135@shell = sys.process.execute(sh, nil, { "Hidden" => true, "Channelized" => true })136137end138139def bootstrap(datastore = {}, handler = nil)140session = self141142# Configure unicode encoding before loading stdapi143session.encode_unicode = datastore['EnableUnicodeEncoding']144145session.init_ui(self.user_input, self.user_output)146147initialize_tlv_logging(datastore['SessionTlvLogging']) unless datastore['SessionTlvLogging'].nil?148149verification_timeout = datastore['AutoVerifySessionTimeout']&.to_i || session.comm_timeout150begin151session.tlv_enc_key = session.core.negotiate_tlv_encryption(timeout: verification_timeout)152rescue Rex::TimeoutError153end154155if session.tlv_enc_key.nil?156# Fail-closed if TLV encryption can't be negotiated (close the session as invalid)157dlog("Session #{session.sid} failed to negotiate TLV encryption")158print_error("Meterpreter session #{session.sid} is not valid and will be closed")159# Terminate the session without cleanup if it did not validate160session.skip_cleanup = true161session.kill162return nil163end164165# always make sure that the new session has a new guid if it's not already known166guid = session.session_guid167if guid == "\x00" * 16168guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')169session.core.set_session_guid(guid)170session.session_guid = guid171# TODO: New stageless session, do some account in the DB so we can track it later.172else173# TODO: This session was either staged or previously known, and so we should do some accounting here!174end175176session.commands.concat(session.core.get_loaded_extension_commands('core'))177if session.tlv_enc_key[:weak_key?]178print_warning("Meterpreter session #{session.sid} is using a weak encryption key.")179print_warning('Meterpreter start up operations have been aborted. Use the session at your own risk.')180return nil181end182# Unhook the process prior to loading stdapi to reduce logging/inspection by any AV/PSP183if datastore['AutoUnhookProcess'] == true184console.run_single('load unhook')185console.run_single('unhook_pe')186end187188unless datastore['AutoLoadStdapi'] == false189190session.load_stdapi191192unless datastore['AutoSystemInfo'] == false193session.load_session_info194end195196# only load priv on native windows197# TODO: abstract this too, to remove windows stuff198if session.platform == 'windows' && [ARCH_X86, ARCH_X64].include?(session.arch)199session.load_priv rescue nil200end201end202203# TODO: abstract this a little, perhaps a "post load" function that removes204# platform-specific stuff?205if session.platform == 'android'206session.load_android207end208209['InitialAutoRunScript', 'AutoRunScript'].each do |key|210unless datastore[key].nil? || datastore[key].empty?211args = Shellwords.shellwords(datastore[key])212print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")213session.execute_script(args.shift, *args)214end215end216end217218##219# :category: Msf::Session::Provider::SingleCommandShell implementors220#221# Read from the command shell.222#223def shell_read(length=nil, timeout=1)224shell_init225226length = nil if length.nil? or length < 0227begin228rv = nil229# Meterpreter doesn't offer a way to timeout on the victim side, so230# we have to do it here. I'm concerned that this will cause loss231# of data.232Timeout.timeout(timeout) {233rv = @shell.channel.read(length)234}235framework.events.on_session_output(self, rv) if rv236return rv237rescue ::Timeout::Error238return nil239rescue ::Exception => e240shell_close241raise e242end243end244245##246# :category: Msf::Session::Provider::SingleCommandShell implementors247#248# Write to the command shell.249#250def shell_write(buf)251shell_init252253begin254framework.events.on_session_command(self, buf.strip)255len = @shell.channel.write("#{buf}\n")256rescue ::Exception => e257shell_close258raise e259end260261len262end263264##265# :category: Msf::Session::Provider::SingleCommandShell implementors266#267# Terminate the shell channel268#269def shell_close270@shell.close271@shell = nil272end273274def shell_command(cmd, timeout = 5)275# Send the shell channel's stdin.276shell_write(cmd + "\n")277278etime = ::Time.now.to_f + timeout279buff = ""280281# Keep reading data until no more data is available or the timeout is282# reached.283while (::Time.now.to_f < etime)284res = shell_read(-1, timeout)285break unless res286timeout = etime - ::Time.now.to_f287buff << res288end289290buff291end292293#294# Called by PacketDispatcher to resolve error codes to names.295# This is the default version (return the number itself)296#297def lookup_error(code)298"#{code}"299end300301##302# :category: Msf::Session overrides303#304# Cleans up the meterpreter client session.305#306def cleanup307cleanup_meterpreter308309super310end311312##313# :category: Msf::Session overrides314#315# Returns the session description.316#317def desc318"Meterpreter"319end320321322##323# :category: Msf::Session::Scriptable implementors324#325# Runs the Meterpreter script or resource file.326#327def execute_file(full_path, args)328# Infer a Meterpreter script by .rb extension329if File.extname(full_path) == '.rb'330Rex::Script::Meterpreter.new(self, full_path).run(args)331else332console.load_resource(full_path)333end334end335336337##338# :category: Msf::Session::Interactive implementors339#340# Initializes the console's I/O handles.341#342def init_ui(input, output)343self.user_input = input344self.user_output = output345console.init_ui(input, output)346console.set_log_source(log_source)347348super349end350351##352# :category: Msf::Session::Interactive implementors353#354# Resets the console's I/O handles.355#356def reset_ui357console.unset_log_source358console.reset_ui359end360361#362# Terminates the session363#364def kill(reason='')365begin366cleanup_meterpreter367self.sock.close if self.sock368rescue ::Exception369end370# deregister will actually trigger another cleanup371framework.sessions.deregister(self, reason)372end373374#375# Run the supplied command as if it came from suer input.376#377def queue_cmd(cmd)378console.queue_cmd(cmd)379end380381##382# :category: Msf::Session::Interactive implementors383#384# Explicitly runs a command in the meterpreter console.385#386def run_cmd(cmd,output_object=nil)387stored_output_state = nil388# If the user supplied an Output IO object, then we tell389# the console to use that, while saving it's previous output/390if output_object391stored_output_state = console.output392console.send(:output=, output_object)393end394success = console.run_single(cmd)395# If we stored the previous output object of the channel396# we restore it here to put everything back the way we found it397# We re-use the conditional above, because we expect in many cases for398# the stored state to actually be nil here.399if output_object400console.send(:output=,stored_output_state)401end402success403end404405#406# Load the stdapi extension.407#408def load_stdapi409original = console.disable_output410console.disable_output = true411console.run_single('load stdapi')412console.disable_output = original413end414415#416# Load the priv extension.417#418def load_priv419original = console.disable_output420console.disable_output = true421console.run_single('load priv')422console.disable_output = original423end424425def update_session_info426# sys.config.getuid, and fs.dir.getwd cache their results, so update them427begin428fs&.dir&.getwd429rescue Rex::Post::Meterpreter::RequestError => e430elog('failed retrieving working directory', error: e)431end432username = self.sys.config.getuid433sysinfo = self.sys.config.sysinfo434435# when updating session information, we need to make sure we update the platform436# in the UUID to match what the target is actually running on, but only for a437# subset of platforms.438if ['java', 'python', 'php'].include?(self.platform)439new_platform = guess_target_platform(sysinfo['OS'])440if self.platform != new_platform441self.payload_uuid.platform = new_platform442self.core.set_uuid(self.payload_uuid)443end444end445446safe_info = "#{username} @ #{sysinfo['Computer']}"447safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)448# Should probably be using Rex::Text.ascii_safe_hex but leave449# this as is for now since "\xNN" is arguably uglier than "_"450# showing up in various places in the UI.451safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")452self.info = safe_info453end454455def guess_target_platform(os)456case os457when /windows/i458Msf::Module::Platform::Windows.realname.downcase459when /darwin/i460Msf::Module::Platform::OSX.realname.downcase461when /mac os ?x/i462# this happens with java on OSX (for real!)463Msf::Module::Platform::OSX.realname.downcase464when /freebsd/i465Msf::Module::Platform::FreeBSD.realname.downcase466when /openbsd/i, /netbsd/i467Msf::Module::Platform::BSD.realname.downcase468else469Msf::Module::Platform::Linux.realname.downcase470end471end472473#474# Populate the session information.475#476# Also reports a session_fingerprint note for host os normalization.477#478def load_session_info479begin480::Timeout.timeout(60) do481update_session_info482483hobj = nil484485nhost = find_internet_connected_address486487original_session_host = self.session_host488# If we found a better IP address for this session, change it489# up. Only handle cases where the DB is not connected here490if nhost && !(framework.db && framework.db.active)491self.session_host = nhost492end493494# The rest of this requires a database, so bail if it's not495# there496return if !(framework.db && framework.db.active)497498::ApplicationRecord.connection_pool.with_connection {499wspace = framework.db.find_workspace(workspace)500501# Account for finding ourselves on a different host502if nhost and self.db_record503# Create or switch to a new host in the database504hobj = framework.db.report_host(:workspace => wspace, :host => nhost)505if hobj506self.session_host = nhost507self.db_record.host_id = hobj[:id]508end509end510511sysinfo = sys.config.sysinfo512host = Msf::Util::Host.normalize_host(self)513514framework.db.report_note({515:type => "host.os.session_fingerprint",516:host => host,517:workspace => wspace,518:data => {519:name => sysinfo["Computer"],520:os => sysinfo["OS"],521:arch => sysinfo["Architecture"],522}523})524525if self.db_record526framework.db.update_session(self)527end528529# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint530# framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)531532if nhost533framework.db.report_note({534:type => "host.nat.server",535:host => original_session_host,536:workspace => wspace,537:data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost },538:update => :unique_data539})540framework.db.report_host(:host => original_session_host, :purpose => 'firewall' )541542framework.db.report_note({543:type => "host.nat.client",544:host => nhost,545:workspace => wspace,546:data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host },547:update => :unique_data548})549framework.db.report_host(:host => nhost, :purpose => 'client' )550end551}552553end554rescue ::Interrupt555dlog("Interrupt while loading sysinfo: #{e.class}: #{e}")556raise $!557rescue ::Exception => e558# Log the error but otherwise ignore it so we don't kill the559# session if reporting failed for some reason560elog('Error loading sysinfo', error: e)561dlog("Call stack:\n#{e.backtrace.join("\n")}")562end563end564565##566# :category: Msf::Session::Interactive implementors567#568# Interacts with the meterpreter client at a user interface level.569#570def _interact571framework.events.on_session_interact(self)572573console.framework = framework574if framework.datastore['MeterpreterPrompt']575console.update_prompt(framework.datastore['MeterpreterPrompt'])576end577# Call the console interaction subsystem of the meterpreter client and578# pass it a block that returns whether or not we should still be579# interacting. This will allow the shell to abort if interaction is580# canceled.581console.interact { self.interacting != true }582console.framework = nil583584# If the stop flag has been set, then that means the user exited. Raise585# the EOFError so we can drop this handle like a bad habit.586raise EOFError if (console.stopped? == true)587end588589590##591# :category: Msf::Session::Comm implementors592#593# Creates a connection based on the supplied parameters and returns it to594# the caller. The connection is created relative to the remote machine on595# which the meterpreter server instance is running.596#597def create(param)598sock = nil599600# Notify handlers before we create the socket601notify_before_socket_create(self, param)602603sock = net.socket.create(param)604605# Notify now that we've created the socket606notify_socket_created(self, sock, param)607608# Return the socket to the caller609sock610end611612def supports_udp?613true614end615616#617# Get a string representation of the current session platform618#619def platform620if self.payload_uuid621# return the actual platform of the current session if it's there622self.payload_uuid.platform623else624# otherwise just use the base for the session type tied to this handler.625# If we don't do this, storage of sessions in the DB dies626self.base_platform627end628end629630#631# Get a string representation of the current session architecture632#633def arch634if self.payload_uuid635# return the actual arch of the current session if it's there636self.payload_uuid.arch637else638# otherwise just use the base for the session type tied to this handler.639# If we don't do this, storage of sessions in the DB dies640self.base_arch641end642end643644#645# Get a string representation of the architecture of the process in which the646# current session is running. This defaults to the same value of arch but can647# be overridden by specific meterpreter implementations to add support.648#649def native_arch650arch651end652653#654# Generate a binary suffix based on arch655#656def binary_suffix657# generate a file/binary suffix based on the current arch and platform.658# Platform-agnostic archs go first659case self.arch660when 'java'661['jar']662when 'php'663['php']664when 'python'665['py']666else667# otherwise we fall back to the platform668case self.platform669when 'windows'670["#{self.arch}.dll"]671when 'linux' , 'aix' , 'hpux' , 'irix' , 'unix'672['bin', 'elf']673when 'osx'674['elf']675when 'android', 'java'676['jar']677when 'php'678['php']679when 'python'680['py']681else682nil683end684end685end686687# These are the base arch/platform for the original payload, required for when the688# session is first created thanks to the fact that the DB session recording689# happens before the session is even established.690attr_accessor :base_arch691attr_accessor :base_platform692693attr_accessor :console # :nodoc:694attr_accessor :skip_ssl695attr_accessor :skip_cleanup696attr_accessor :target_id697attr_accessor :max_threads698699protected700701attr_accessor :rstream # :nodoc:702703# Rummage through this host's routes and interfaces looking for an704# address that it uses to talk to the internet.705#706# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces707# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes708# @return [String] The address from which this host reaches the709# internet, as ASCII. e.g.: "192.168.100.156"710# @return [nil] If there is an interface with an address that matches711# {#session_host}712def find_internet_connected_address713714ifaces = self.net.config.get_interfaces().flatten rescue []715routes = self.net.config.get_routes().flatten rescue []716717# Try to match our visible IP to a real interface718found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } })719nhost = nil720721# If the host has no address that matches what we see, then one of722# us is behind NAT so we have to look harder.723if !found724# Grab all routes to the internet725default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" }726727default_routes.each do |route|728# Now try to find an interface whose network includes this729# Route's gateway, which means it's the one the host uses to get730# to the interweb.731ifaces.each do |i|732# Try all the addresses this interface has configured733addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask|734bits = Rex::Socket.net2bitmask( netmask )735range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil736737!!(range && range.valid? && range.include?(route.gateway))738end739if addr_and_mask740nhost = addr_and_mask[0]741break742end743end744break if nhost745end746747if !nhost748# No internal address matches what we see externally and no749# interface has a default route. Fall back to the first750# non-loopback address751non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" }752if non_loopback753nhost = non_loopback.ip754end755end756end757758nhost759end760761end762763end764end765766767