Path: blob/master/lib/msf/base/sessions/meterpreter.rb
33056 views
# -*- 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 load_embedded_extensions140# Rex::Post::Meterpreter::ExtensionMapper.get_extension_klasses141142# First of all, let's see if we have stdapi.143commands = self.core.get_loaded_extension_commands('stdapi')144console.run_single("load stdapi") if commands.length > 0145146return if self.platform != 'windows'147148exts = Set.new149exts.merge(binary_suffix.map { |suffix| MetasploitPayloads.list_meterpreter_extensions(suffix) }.flatten)150exts = exts.sort.uniq151152exts.each { |e|153commands = self.core.get_loaded_extension_commands(e.downcase)154if commands.length > 0 && !e.downcase.starts_with?('stdapi')155console.run_single("load #{e.downcase}")156end157}158end159160def bootstrap(datastore = {}, handler = nil)161session = self162163# Configure unicode encoding before loading stdapi164session.encode_unicode = datastore['EnableUnicodeEncoding']165166session.init_ui(self.user_input, self.user_output)167168initialize_tlv_logging(datastore['SessionTlvLogging']) unless datastore['SessionTlvLogging'].nil?169170verification_timeout = datastore['AutoVerifySessionTimeout']&.to_i || session.comm_timeout171begin172session.tlv_enc_key = session.core.negotiate_tlv_encryption(timeout: verification_timeout)173rescue Rex::TimeoutError174end175176if session.tlv_enc_key.nil?177# Fail-closed if TLV encryption can't be negotiated (close the session as invalid)178dlog("Session #{session.sid} failed to negotiate TLV encryption")179print_error("Meterpreter session #{session.sid} is not valid and will be closed")180# Terminate the session without cleanup if it did not validate181session.skip_cleanup = true182session.kill183return nil184end185186# always make sure that the new session has a new guid if it's not already known187guid = session.session_guid188if guid == "\x00" * 16189guid = [SecureRandom.uuid.gsub('-', '')].pack('H*')190session.core.set_session_guid(guid)191session.session_guid = guid192# TODO: New stageless session, do some account in the DB so we can track it later.193else194# TODO: This session was either staged or previously known, and so we should do some accounting here!195end196197session.commands.concat(session.core.get_loaded_extension_commands('core'))198if session.tlv_enc_key[:weak_key?]199print_warning("Meterpreter session #{session.sid} is using a weak encryption key.")200print_warning('Meterpreter start up operations have been aborted. Use the session at your own risk.')201return nil202end203204original = console.disable_output205console.disable_output = true206207load_embedded_extensions208209extensions = datastore['AutoLoadExtensions']&.delete(' ')&.split(',') || []210211# BEGIN: This should be removed on MSF 7212# 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)213# The unhook extension is broken. reference: https://github.com/rapid7/metasploit-framework/pull/20514214215#extensions.push('unhook') if datastore['AutoUnhookProcess'] && session.platform == 'windows'216extensions.push('stdapi') if datastore['AutoLoadStdapi']217extensions.push('priv') if datastore['AutoLoadStdapi'] && session.platform == 'windows'218extensions.push('android') if session.platform == 'android'219extensions = extensions.uniq220# END221# TODO: abstract this a little, perhaps a "post load" function that removes222# platform-specific stuff?223extensions.each do |extension|224begin225console.run_single("load #{extension}")226# console.run_single('unhook_pe') if extension == 'unhook'227session.load_session_info if extension == 'stdapi' && datastore['AutoSystemInfo']228rescue => e229print_warning("Failed loading extension #{extension}")230end231end232console.disable_output = original233234['InitialAutoRunScript', 'AutoRunScript'].each do |key|235unless datastore[key].nil? || datastore[key].empty?236args = Shellwords.shellwords(datastore[key])237print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")238session.execute_script(args.shift, *args)239end240end241end242243##244# :category: Msf::Session::Provider::SingleCommandShell implementors245#246# Read from the command shell.247#248def shell_read(length=nil, timeout=1)249shell_init250251length = nil if length.nil? or length < 0252begin253rv = nil254# Meterpreter doesn't offer a way to timeout on the victim side, so255# we have to do it here. I'm concerned that this will cause loss256# of data.257Timeout.timeout(timeout) {258rv = @shell.channel.read(length)259}260framework.events.on_session_output(self, rv) if rv261return rv262rescue ::Timeout::Error263return nil264rescue ::Exception => e265shell_close266raise e267end268end269270##271# :category: Msf::Session::Provider::SingleCommandShell implementors272#273# Write to the command shell.274#275def shell_write(buf)276shell_init277278begin279framework.events.on_session_command(self, buf.strip)280len = @shell.channel.write("#{buf}\n")281rescue ::Exception => e282shell_close283raise e284end285286len287end288289##290# :category: Msf::Session::Provider::SingleCommandShell implementors291#292# Terminate the shell channel293#294def shell_close295@shell.close296@shell = nil297end298299def shell_command(cmd, timeout = 5)300# Send the shell channel's stdin.301shell_write(cmd + "\n")302303etime = ::Time.now.to_f + timeout304buff = ""305306# Keep reading data until no more data is available or the timeout is307# reached.308while (::Time.now.to_f < etime)309res = shell_read(-1, timeout)310break unless res311timeout = etime - ::Time.now.to_f312buff << res313end314315buff316end317318#319# Called by PacketDispatcher to resolve error codes to names.320# This is the default version (return the number itself)321#322def lookup_error(code)323"#{code}"324end325326##327# :category: Msf::Session overrides328#329# Cleans up the meterpreter client session.330#331def cleanup332cleanup_meterpreter333334super335end336337##338# :category: Msf::Session overrides339#340# Returns the session description.341#342def desc343"Meterpreter"344end345346347##348# :category: Msf::Session::Scriptable implementors349#350# Runs the Meterpreter script or resource file.351#352def execute_file(full_path, args)353# Infer a Meterpreter script by .rb extension354if File.extname(full_path) == '.rb'355Rex::Script::Meterpreter.new(self, full_path).run(args)356else357console.load_resource(full_path)358end359end360361362##363# :category: Msf::Session::Interactive implementors364#365# Initializes the console's I/O handles.366#367def init_ui(input, output)368self.user_input = input369self.user_output = output370console.init_ui(input, output)371console.set_log_source(log_source)372373super374end375376##377# :category: Msf::Session::Interactive implementors378#379# Resets the console's I/O handles.380#381def reset_ui382console.unset_log_source383console.reset_ui384end385386#387# Terminates the session388#389def kill(reason='')390begin391cleanup_meterpreter392self.sock.close if self.sock393rescue ::Exception394end395# deregister will actually trigger another cleanup396framework.sessions.deregister(self, reason)397end398399#400# Run the supplied command as if it came from suer input.401#402def queue_cmd(cmd)403console.queue_cmd(cmd)404end405406##407# :category: Msf::Session::Interactive implementors408#409# Explicitly runs a command in the meterpreter console.410#411def run_cmd(cmd,output_object=nil)412stored_output_state = nil413# If the user supplied an Output IO object, then we tell414# the console to use that, while saving it's previous output/415if output_object416stored_output_state = console.output417console.send(:output=, output_object)418end419success = console.run_single(cmd)420# If we stored the previous output object of the channel421# we restore it here to put everything back the way we found it422# We re-use the conditional above, because we expect in many cases for423# the stored state to actually be nil here.424if output_object425console.send(:output=,stored_output_state)426end427success428end429430#431# Load the stdapi extension.432#433def load_stdapi434original = console.disable_output435console.disable_output = true436console.run_single('load stdapi')437console.disable_output = original438end439440#441# Load the priv extension.442#443def load_priv444original = console.disable_output445console.disable_output = true446console.run_single('load priv')447console.disable_output = original448end449450def update_session_info451# sys.config.getuid, and fs.dir.getwd cache their results, so update them452begin453fs&.dir&.getwd454rescue Rex::Post::Meterpreter::RequestError => e455elog('failed retrieving working directory', error: e)456end457username = self.sys.config.getuid458sysinfo = self.sys.config.sysinfo459460# when updating session information, we need to make sure we update the platform461# in the UUID to match what the target is actually running on, but only for a462# subset of platforms.463if ['java', 'python', 'php'].include?(self.platform)464new_platform = guess_target_platform(sysinfo['OS'])465if self.platform != new_platform466self.payload_uuid.platform = new_platform467self.core.set_uuid(self.payload_uuid)468end469end470471safe_info = "#{username} @ #{sysinfo['Computer']}"472safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)473# Should probably be using Rex::Text.ascii_safe_hex but leave474# this as is for now since "\xNN" is arguably uglier than "_"475# showing up in various places in the UI.476safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")477self.info = safe_info478end479480def guess_target_platform(os)481case os482when /windows/i483Msf::Module::Platform::Windows.realname.downcase484when /darwin/i485Msf::Module::Platform::OSX.realname.downcase486when /mac os ?x/i487# this happens with java on OSX (for real!)488Msf::Module::Platform::OSX.realname.downcase489when /freebsd/i490Msf::Module::Platform::FreeBSD.realname.downcase491when /openbsd/i, /netbsd/i492Msf::Module::Platform::BSD.realname.downcase493else494Msf::Module::Platform::Linux.realname.downcase495end496end497498#499# Populate the session information.500#501# Also reports a session_fingerprint note for host os normalization.502#503def load_session_info504begin505::Timeout.timeout(60) do506update_session_info507508hobj = nil509510nhost = find_internet_connected_address511512original_session_host = self.session_host513# If we found a better IP address for this session, change it514# up. Only handle cases where the DB is not connected here515if nhost && !(framework.db && framework.db.active)516self.session_host = nhost517end518519# The rest of this requires a database, so bail if it's not520# there521return if !(framework.db && framework.db.active)522523::ApplicationRecord.connection_pool.with_connection {524wspace = framework.db.find_workspace(workspace)525526# Account for finding ourselves on a different host527if nhost and self.db_record528# Create or switch to a new host in the database529hobj = framework.db.report_host(:workspace => wspace, :host => nhost)530if hobj531self.session_host = nhost532self.db_record.host_id = hobj[:id]533end534end535536sysinfo = sys.config.sysinfo537host = Msf::Util::Host.normalize_host(self)538539framework.db.report_note({540:type => "host.os.session_fingerprint",541:host => host,542:workspace => wspace,543:data => {544:name => sysinfo["Computer"],545:os => sysinfo["OS"],546:arch => sysinfo["Architecture"],547}548})549550if self.db_record551framework.db.update_session(self)552end553554# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint555# framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)556557if nhost558framework.db.report_note({559:type => "host.nat.server",560:host => original_session_host,561:workspace => wspace,562:data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost },563:update => :unique_data564})565framework.db.report_host(:host => original_session_host, :purpose => 'firewall' )566567framework.db.report_note({568:type => "host.nat.client",569:host => nhost,570:workspace => wspace,571:data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host },572:update => :unique_data573})574framework.db.report_host(:host => nhost, :purpose => 'client' )575end576}577578end579rescue ::Interrupt580dlog("Interrupt while loading sysinfo: #{e.class}: #{e}")581raise $!582rescue ::Exception => e583# Log the error but otherwise ignore it so we don't kill the584# session if reporting failed for some reason585elog('Error loading sysinfo', error: e)586dlog("Call stack:\n#{e.backtrace.join("\n")}")587end588end589590##591# :category: Msf::Session::Interactive implementors592#593# Interacts with the meterpreter client at a user interface level.594#595def _interact596framework.events.on_session_interact(self)597598console.framework = framework599if framework.datastore['MeterpreterPrompt']600console.update_prompt(framework.datastore['MeterpreterPrompt'])601end602# Call the console interaction subsystem of the meterpreter client and603# pass it a block that returns whether or not we should still be604# interacting. This will allow the shell to abort if interaction is605# canceled.606console.interact { self.interacting != true }607console.framework = nil608609# If the stop flag has been set, then that means the user exited. Raise610# the EOFError so we can drop this handle like a bad habit.611raise EOFError if (console.stopped? == true)612end613614615##616# :category: Msf::Session::Comm implementors617#618# Creates a connection based on the supplied parameters and returns it to619# the caller. The connection is created relative to the remote machine on620# which the meterpreter server instance is running.621#622def create(param)623sock = nil624625# Notify handlers before we create the socket626notify_before_socket_create(self, param)627628sock = net.socket.create(param)629630# Notify now that we've created the socket631notify_socket_created(self, sock, param)632633# Return the socket to the caller634sock635end636637def supports_udp?638true639end640641#642# Get a string representation of the current session platform643#644def platform645if self.payload_uuid646# return the actual platform of the current session if it's there647self.payload_uuid.platform648else649# otherwise just use the base for the session type tied to this handler.650# If we don't do this, storage of sessions in the DB dies651self.base_platform652end653end654655#656# Get a string representation of the current session architecture657#658def arch659if self.payload_uuid660# return the actual arch of the current session if it's there661self.payload_uuid.arch662else663# otherwise just use the base for the session type tied to this handler.664# If we don't do this, storage of sessions in the DB dies665self.base_arch666end667end668669#670# Get a string representation of the architecture of the process in which the671# current session is running. This defaults to the same value of arch but can672# be overridden by specific meterpreter implementations to add support.673#674def native_arch675arch676end677678#679# Generate a binary suffix based on arch680#681def binary_suffix682# generate a file/binary suffix based on the current arch and platform.683# Platform-agnostic archs go first684case self.arch685when 'java'686['jar']687when 'php'688['php']689when 'python'690['py']691else692# otherwise we fall back to the platform693case self.platform694when 'windows'695["#{self.arch}.dll"]696when 'linux' , 'aix' , 'hpux' , 'irix' , 'unix'697['bin', 'elf']698when 'osx'699['elf']700when 'android', 'java'701['jar']702when 'php'703['php']704when 'python'705['py']706else707nil708end709end710end711712# These are the base arch/platform for the original payload, required for when the713# session is first created thanks to the fact that the DB session recording714# happens before the session is even established.715attr_accessor :base_arch716attr_accessor :base_platform717718attr_accessor :console # :nodoc:719attr_accessor :skip_ssl720attr_accessor :skip_cleanup721attr_accessor :target_id722attr_accessor :max_threads723724protected725726attr_accessor :rstream # :nodoc:727728# Rummage through this host's routes and interfaces looking for an729# address that it uses to talk to the internet.730#731# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces732# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes733# @return [String] The address from which this host reaches the734# internet, as ASCII. e.g.: "192.168.100.156"735# @return [nil] If there is an interface with an address that matches736# {#session_host}737def find_internet_connected_address738739ifaces = self.net.config.get_interfaces().flatten rescue []740routes = self.net.config.get_routes().flatten rescue []741742# Try to match our visible IP to a real interface743found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } })744nhost = nil745746# If the host has no address that matches what we see, then one of747# us is behind NAT so we have to look harder.748if !found749# Grab all routes to the internet750default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" }751752default_routes.each do |route|753# Now try to find an interface whose network includes this754# Route's gateway, which means it's the one the host uses to get755# to the interweb.756ifaces.each do |i|757# Try all the addresses this interface has configured758addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask|759bits = Rex::Socket.net2bitmask( netmask )760range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil761762!!(range && range.valid? && range.include?(route.gateway))763end764if addr_and_mask765nhost = addr_and_mask[0]766break767end768end769break if nhost770end771772if !nhost773# No internal address matches what we see externally and no774# interface has a default route. Fall back to the first775# non-loopback address776non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" }777if non_loopback778nhost = non_loopback.ip779end780end781end782783nhost784end785786end787788end789end790791792