Path: blob/master/lib/msf/base/sessions/meterpreter.rb
21545 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 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 nil181end182extensions = datastore['AutoLoadExtensions']&.delete(' ').split(',') || []183184# BEGIN: This should be removed on MSF 7185# 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)186extensions.push('unhook') if datastore['AutoUnhookProcess'] && session.platform == 'windows'187extensions.push('stdapi') if datastore['AutoLoadStdapi']188extensions.push('priv') if datastore['AutoLoadStdapi'] && session.platform == 'windows'189extensions.push('android') if session.platform == 'android'190extensions = extensions.uniq191# END192original = console.disable_output193console.disable_output = true194# TODO: abstract this a little, perhaps a "post load" function that removes195# platform-specific stuff?196extensions.each do |extension|197begin198console.run_single("load #{extension}")199console.run_single('unhook_pe') if extension == 'unhook'200session.load_session_info if extension == 'stdapi' && datastore['AutoSystemInfo']201rescue => e202print_warning("Failed loading extension #{extension}")203end204end205console.disable_output = original206207['InitialAutoRunScript', 'AutoRunScript'].each do |key|208unless datastore[key].nil? || datastore[key].empty?209args = Shellwords.shellwords(datastore[key])210print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")211session.execute_script(args.shift, *args)212end213end214end215216##217# :category: Msf::Session::Provider::SingleCommandShell implementors218#219# Read from the command shell.220#221def shell_read(length=nil, timeout=1)222shell_init223224length = nil if length.nil? or length < 0225begin226rv = nil227# Meterpreter doesn't offer a way to timeout on the victim side, so228# we have to do it here. I'm concerned that this will cause loss229# of data.230Timeout.timeout(timeout) {231rv = @shell.channel.read(length)232}233framework.events.on_session_output(self, rv) if rv234return rv235rescue ::Timeout::Error236return nil237rescue ::Exception => e238shell_close239raise e240end241end242243##244# :category: Msf::Session::Provider::SingleCommandShell implementors245#246# Write to the command shell.247#248def shell_write(buf)249shell_init250251begin252framework.events.on_session_command(self, buf.strip)253len = @shell.channel.write("#{buf}\n")254rescue ::Exception => e255shell_close256raise e257end258259len260end261262##263# :category: Msf::Session::Provider::SingleCommandShell implementors264#265# Terminate the shell channel266#267def shell_close268@shell.close269@shell = nil270end271272def shell_command(cmd, timeout = 5)273# Send the shell channel's stdin.274shell_write(cmd + "\n")275276etime = ::Time.now.to_f + timeout277buff = ""278279# Keep reading data until no more data is available or the timeout is280# reached.281while (::Time.now.to_f < etime)282res = shell_read(-1, timeout)283break unless res284timeout = etime - ::Time.now.to_f285buff << res286end287288buff289end290291#292# Called by PacketDispatcher to resolve error codes to names.293# This is the default version (return the number itself)294#295def lookup_error(code)296"#{code}"297end298299##300# :category: Msf::Session overrides301#302# Cleans up the meterpreter client session.303#304def cleanup305cleanup_meterpreter306307super308end309310##311# :category: Msf::Session overrides312#313# Returns the session description.314#315def desc316"Meterpreter"317end318319320##321# :category: Msf::Session::Scriptable implementors322#323# Runs the Meterpreter script or resource file.324#325def execute_file(full_path, args)326# Infer a Meterpreter script by .rb extension327if File.extname(full_path) == '.rb'328Rex::Script::Meterpreter.new(self, full_path).run(args)329else330console.load_resource(full_path)331end332end333334335##336# :category: Msf::Session::Interactive implementors337#338# Initializes the console's I/O handles.339#340def init_ui(input, output)341self.user_input = input342self.user_output = output343console.init_ui(input, output)344console.set_log_source(log_source)345346super347end348349##350# :category: Msf::Session::Interactive implementors351#352# Resets the console's I/O handles.353#354def reset_ui355console.unset_log_source356console.reset_ui357end358359#360# Terminates the session361#362def kill(reason='')363begin364cleanup_meterpreter365self.sock.close if self.sock366rescue ::Exception367end368# deregister will actually trigger another cleanup369framework.sessions.deregister(self, reason)370end371372#373# Run the supplied command as if it came from suer input.374#375def queue_cmd(cmd)376console.queue_cmd(cmd)377end378379##380# :category: Msf::Session::Interactive implementors381#382# Explicitly runs a command in the meterpreter console.383#384def run_cmd(cmd,output_object=nil)385stored_output_state = nil386# If the user supplied an Output IO object, then we tell387# the console to use that, while saving it's previous output/388if output_object389stored_output_state = console.output390console.send(:output=, output_object)391end392success = console.run_single(cmd)393# If we stored the previous output object of the channel394# we restore it here to put everything back the way we found it395# We re-use the conditional above, because we expect in many cases for396# the stored state to actually be nil here.397if output_object398console.send(:output=,stored_output_state)399end400success401end402403#404# Load the stdapi extension.405#406def load_stdapi407original = console.disable_output408console.disable_output = true409console.run_single('load stdapi')410console.disable_output = original411end412413#414# Load the priv extension.415#416def load_priv417original = console.disable_output418console.disable_output = true419console.run_single('load priv')420console.disable_output = original421end422423def update_session_info424# sys.config.getuid, and fs.dir.getwd cache their results, so update them425begin426fs&.dir&.getwd427rescue Rex::Post::Meterpreter::RequestError => e428elog('failed retrieving working directory', error: e)429end430username = self.sys.config.getuid431sysinfo = self.sys.config.sysinfo432433# when updating session information, we need to make sure we update the platform434# in the UUID to match what the target is actually running on, but only for a435# subset of platforms.436if ['java', 'python', 'php'].include?(self.platform)437new_platform = guess_target_platform(sysinfo['OS'])438if self.platform != new_platform439self.payload_uuid.platform = new_platform440self.core.set_uuid(self.payload_uuid)441end442end443444safe_info = "#{username} @ #{sysinfo['Computer']}"445safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)446# Should probably be using Rex::Text.ascii_safe_hex but leave447# this as is for now since "\xNN" is arguably uglier than "_"448# showing up in various places in the UI.449safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")450self.info = safe_info451end452453def guess_target_platform(os)454case os455when /windows/i456Msf::Module::Platform::Windows.realname.downcase457when /darwin/i458Msf::Module::Platform::OSX.realname.downcase459when /mac os ?x/i460# this happens with java on OSX (for real!)461Msf::Module::Platform::OSX.realname.downcase462when /freebsd/i463Msf::Module::Platform::FreeBSD.realname.downcase464when /openbsd/i, /netbsd/i465Msf::Module::Platform::BSD.realname.downcase466else467Msf::Module::Platform::Linux.realname.downcase468end469end470471#472# Populate the session information.473#474# Also reports a session_fingerprint note for host os normalization.475#476def load_session_info477begin478::Timeout.timeout(60) do479update_session_info480481hobj = nil482483nhost = find_internet_connected_address484485original_session_host = self.session_host486# If we found a better IP address for this session, change it487# up. Only handle cases where the DB is not connected here488if nhost && !(framework.db && framework.db.active)489self.session_host = nhost490end491492# The rest of this requires a database, so bail if it's not493# there494return if !(framework.db && framework.db.active)495496::ApplicationRecord.connection_pool.with_connection {497wspace = framework.db.find_workspace(workspace)498499# Account for finding ourselves on a different host500if nhost and self.db_record501# Create or switch to a new host in the database502hobj = framework.db.report_host(:workspace => wspace, :host => nhost)503if hobj504self.session_host = nhost505self.db_record.host_id = hobj[:id]506end507end508509sysinfo = sys.config.sysinfo510host = Msf::Util::Host.normalize_host(self)511512framework.db.report_note({513:type => "host.os.session_fingerprint",514:host => host,515:workspace => wspace,516:data => {517:name => sysinfo["Computer"],518:os => sysinfo["OS"],519:arch => sysinfo["Architecture"],520}521})522523if self.db_record524framework.db.update_session(self)525end526527# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint528# framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)529530if nhost531framework.db.report_note({532:type => "host.nat.server",533:host => original_session_host,534:workspace => wspace,535:data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost },536:update => :unique_data537})538framework.db.report_host(:host => original_session_host, :purpose => 'firewall' )539540framework.db.report_note({541:type => "host.nat.client",542:host => nhost,543:workspace => wspace,544:data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host },545:update => :unique_data546})547framework.db.report_host(:host => nhost, :purpose => 'client' )548end549}550551end552rescue ::Interrupt553dlog("Interrupt while loading sysinfo: #{e.class}: #{e}")554raise $!555rescue ::Exception => e556# Log the error but otherwise ignore it so we don't kill the557# session if reporting failed for some reason558elog('Error loading sysinfo', error: e)559dlog("Call stack:\n#{e.backtrace.join("\n")}")560end561end562563##564# :category: Msf::Session::Interactive implementors565#566# Interacts with the meterpreter client at a user interface level.567#568def _interact569framework.events.on_session_interact(self)570571console.framework = framework572if framework.datastore['MeterpreterPrompt']573console.update_prompt(framework.datastore['MeterpreterPrompt'])574end575# Call the console interaction subsystem of the meterpreter client and576# pass it a block that returns whether or not we should still be577# interacting. This will allow the shell to abort if interaction is578# canceled.579console.interact { self.interacting != true }580console.framework = nil581582# If the stop flag has been set, then that means the user exited. Raise583# the EOFError so we can drop this handle like a bad habit.584raise EOFError if (console.stopped? == true)585end586587588##589# :category: Msf::Session::Comm implementors590#591# Creates a connection based on the supplied parameters and returns it to592# the caller. The connection is created relative to the remote machine on593# which the meterpreter server instance is running.594#595def create(param)596sock = nil597598# Notify handlers before we create the socket599notify_before_socket_create(self, param)600601sock = net.socket.create(param)602603# Notify now that we've created the socket604notify_socket_created(self, sock, param)605606# Return the socket to the caller607sock608end609610def supports_udp?611true612end613614#615# Get a string representation of the current session platform616#617def platform618if self.payload_uuid619# return the actual platform of the current session if it's there620self.payload_uuid.platform621else622# otherwise just use the base for the session type tied to this handler.623# If we don't do this, storage of sessions in the DB dies624self.base_platform625end626end627628#629# Get a string representation of the current session architecture630#631def arch632if self.payload_uuid633# return the actual arch of the current session if it's there634self.payload_uuid.arch635else636# otherwise just use the base for the session type tied to this handler.637# If we don't do this, storage of sessions in the DB dies638self.base_arch639end640end641642#643# Get a string representation of the architecture of the process in which the644# current session is running. This defaults to the same value of arch but can645# be overridden by specific meterpreter implementations to add support.646#647def native_arch648arch649end650651#652# Generate a binary suffix based on arch653#654def binary_suffix655# generate a file/binary suffix based on the current arch and platform.656# Platform-agnostic archs go first657case self.arch658when 'java'659['jar']660when 'php'661['php']662when 'python'663['py']664else665# otherwise we fall back to the platform666case self.platform667when 'windows'668["#{self.arch}.dll"]669when 'linux' , 'aix' , 'hpux' , 'irix' , 'unix'670['bin', 'elf']671when 'osx'672['elf']673when 'android', 'java'674['jar']675when 'php'676['php']677when 'python'678['py']679else680nil681end682end683end684685# These are the base arch/platform for the original payload, required for when the686# session is first created thanks to the fact that the DB session recording687# happens before the session is even established.688attr_accessor :base_arch689attr_accessor :base_platform690691attr_accessor :console # :nodoc:692attr_accessor :skip_ssl693attr_accessor :skip_cleanup694attr_accessor :target_id695attr_accessor :max_threads696697protected698699attr_accessor :rstream # :nodoc:700701# Rummage through this host's routes and interfaces looking for an702# address that it uses to talk to the internet.703#704# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces705# @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes706# @return [String] The address from which this host reaches the707# internet, as ASCII. e.g.: "192.168.100.156"708# @return [nil] If there is an interface with an address that matches709# {#session_host}710def find_internet_connected_address711712ifaces = self.net.config.get_interfaces().flatten rescue []713routes = self.net.config.get_routes().flatten rescue []714715# Try to match our visible IP to a real interface716found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } })717nhost = nil718719# If the host has no address that matches what we see, then one of720# us is behind NAT so we have to look harder.721if !found722# Grab all routes to the internet723default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" }724725default_routes.each do |route|726# Now try to find an interface whose network includes this727# Route's gateway, which means it's the one the host uses to get728# to the interweb.729ifaces.each do |i|730# Try all the addresses this interface has configured731addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask|732bits = Rex::Socket.net2bitmask( netmask )733range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil734735!!(range && range.valid? && range.include?(route.gateway))736end737if addr_and_mask738nhost = addr_and_mask[0]739break740end741end742break if nhost743end744745if !nhost746# No internal address matches what we see externally and no747# interface has a default route. Fall back to the first748# non-loopback address749non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" }750if non_loopback751nhost = non_loopback.ip752end753end754end755756nhost757end758759end760761end762end763764765