# -*- coding: binary -*- require 'rex/post/meterpreter/packet_response_waiter' require 'rex/logging' require 'rex/exceptions' module Rex module Post module Meterpreter ### # # Exception thrown when a request fails. # ### class RequestError < ArgumentError def initialize(method, einfo, ecode=nil) @method = method @result = einfo @code = ecode || einfo end def to_s "#{@method}: Operation failed: #{@result}" end # The method that failed. attr_reader :method # The error result that occurred, typically a windows error message. attr_reader :result # The error result that occurred, typically a windows error code. attr_reader :code end ### # # Handles packet transmission, reception, and correlation, # and processing # ### module PacketDispatcher # Defualt time, in seconds, to wait for a response after sending a packet PACKET_TIMEOUT = 600 # Number of seconds to wait without getting any packets before we try to # send a liveness check. A minute should be generous even on the highest # latency networks # # @see #keepalive PING_TIME = 60 # This mutex is used to lock out new commands during an # active migration. Unused if this is a passive dispatcher attr_accessor :comm_mutex # Passive Dispatching # # @return [Rex::ServiceManager] # @return [nil] if this is not a passive dispatcher attr_accessor :passive_service # @return [Array] attr_accessor :send_queue # @return [Array] attr_accessor :recv_queue def initialize_passive_dispatcher self.send_queue = [] self.recv_queue = [] self.waiters = [] self.alive = true # Ensure that there is only one leading and trailing slash on the URI resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/" self.passive_service = self.passive_dispatcher self.passive_service.remove_resource(resource_uri) self.passive_service.add_resource(resource_uri, 'Proc' => Proc.new { |cli, req| on_passive_request(cli, req) }, 'VirtualDirectory' => true ) end def shutdown_passive_dispatcher return if not self.passive_service # Ensure that there is only one leading and trailing slash on the URI resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/" self.passive_service.remove_resource(resource_uri) # If there are no more resources registered on the service, stop it entirely if self.passive_service.resources.empty? Rex::ServiceManager.stop_service(self.passive_service) end self.alive = false self.send_queue = [] self.recv_queue = [] self.waiters = [] self.passive_service = nil end def on_passive_request(cli, req) begin resp = Rex::Proto::Http::Response.new(200, "OK") resp['Content-Type'] = 'application/octet-stream' resp['Connection'] = 'close' self.last_checkin = Time.now if req.method == 'GET' rpkt = send_queue.shift resp.body = rpkt || '' begin cli.send_response(resp) rescue ::Exception => e send_queue.unshift(rpkt) if rpkt elog("Exception sending a reply to the reader request: #{cli.inspect} #{e.class} #{e} #{e.backtrace}") end else resp.body = "" if req.body and req.body.length > 0 packet = Packet.new(0) packet.from_r(req.body) dispatch_inbound_packet(packet) end cli.send_response(resp) end rescue ::Exception => e elog("Exception handling request: #{cli.inspect} #{req.inspect} #{e.class} #{e} #{e.backtrace}") end end ## # # Transmission # ## # # Sends a packet without waiting for a response. # def send_packet(packet, completion_routine = nil, completion_param = nil) if (completion_routine) add_response_waiter(packet, completion_routine, completion_param) end bytes = 0 raw = packet.to_r err = nil # Short-circuit send when using a passive dispatcher if self.passive_service send_queue.push(raw) return raw.size # Lie! end if (raw) self.comm_mutex.synchronize do begin bytes = self.sock.write(raw) rescue ::Exception => e err = e end end if bytes.to_i == 0 # Mark the session itself as dead self.alive = false # Reraise the error to the top-level caller raise err if err end end return bytes end # # Sends a packet and waits for a timeout for the given time interval. # # @param packet [Packet] request to send # @param timeout [Fixnum,nil] seconds to wait for response, or nil to ignore the # response and return immediately # @return (see #send_packet_wait_response) def send_request(packet, timeout = self.response_timeout) response = send_packet_wait_response(packet, timeout) if timeout.nil? return nil elsif response.nil? raise TimeoutError.new("Send timed out") elsif (response.result != 0) einfo = lookup_error(response.result) e = RequestError.new(packet.method, einfo, response.result) e.set_backtrace(caller) raise e end return response end # # Transmits a packet and waits for a response. # # @param packet [Packet] the request packet to send # @param timeout [Fixnum,nil] number of seconds to wait, or nil to wait # forever def send_packet_wait_response(packet, timeout) # First, add the waiter association for the supplied packet waiter = add_response_waiter(packet) bytes_written = send_packet(packet) # Transmit the packet if (bytes_written.to_i <= 0) # Remove the waiter if we failed to send the packet. remove_response_waiter(waiter) return nil end if not timeout return nil end # Wait for the supplied time interval response = waiter.wait(timeout) # Remove the waiter from the list of waiters in case it wasn't # removed. This happens if the waiter timed out above. remove_response_waiter(waiter) # Return the response packet, if any return response end # Send a ping to the server. # # Our 'ping' is a check for eof on channel id 0. This method has no side # effects and always returns an answer (regardless of the existence of chan # 0), which is all that's needed for a liveness check. The answer itself is # unimportant and is ignored. # # @return [void] def keepalive if @ping_sent if Time.now.to_i - last_checkin.to_i > PING_TIME*2 dlog("No response to ping, session #{self.sid} is dead", LEV_3) self.alive = false end else pkt = Packet.create_request('core_channel_eof') pkt.add_tlv(TLV_TYPE_CHANNEL_ID, 0) add_response_waiter(pkt, Proc.new { @ping_sent = false }) send_packet(pkt) @ping_sent = true end end ## # # Reception # ## # # Monitors the PacketDispatcher's sock for data in its own # thread context and parsers all inbound packets. # def monitor_socket # Skip if we are using a passive dispatcher return if self.passive_service self.comm_mutex = ::Mutex.new self.waiters = [] @pqueue = ::Queue.new @ping_sent = false # Spawn a thread for receiving packets self.receiver_thread = Rex::ThreadFactory.spawn("MeterpreterReceiver", false) do while (self.alive) begin rv = Rex::ThreadSafe.select([ self.sock.fd ], nil, nil, PING_TIME) if rv packet = receive_packet @pqueue << packet if packet elsif self.send_keepalives && @pqueue.empty? keepalive end rescue ::Exception => e dlog("Exception caught in monitor_socket: #{e.class}: #{e}", 'meterpreter', LEV_1) dlog("Call stack: #{e.backtrace.join("\n")}", 'meterpreter', LEV_2) self.alive = false break end end end # Spawn a new thread that monitors the socket self.dispatcher_thread = Rex::ThreadFactory.spawn("MeterpreterDispatcher", false) do begin while (self.alive) incomplete = [] backlog = [] backlog << @pqueue.pop while(@pqueue.length > 0) backlog << @pqueue.pop end # # Prioritize message processing here # 1. Close should always be processed at the end # 2. Command responses always before channel data # tmp_command = [] tmp_channel = [] tmp_close = [] backlog.each do |pkt| if(pkt.response?) tmp_command << pkt next end if(pkt.method == "core_channel_close") tmp_close << pkt next end tmp_channel << pkt end backlog = [] backlog.push(*tmp_command) backlog.push(*tmp_channel) backlog.push(*tmp_close) # # Process the message queue # backlog.each do |pkt| begin if ! dispatch_inbound_packet(pkt) # Keep Packets in the receive queue until a handler is registered # for them. Packets will live in the receive queue for up to # PACKET_TIMEOUT seconds, after which they will be dropped. # # A common reason why there would not immediately be a handler for # a received Packet is in channels, where a connection may # open and receive data before anything has asked to read. if (::Time.now.to_i - pkt.created_at.to_i < PACKET_TIMEOUT) incomplete << pkt end end rescue ::Exception => e dlog("Dispatching exception with packet #{pkt}: #{e} #{e.backtrace}", 'meterpreter', LEV_1) end end # If the backlog and incomplete arrays are the same, it means # dispatch_inbound_packet wasn't able to handle any of the # packets. When that's the case, we can get into a situation # where @pqueue is not empty and, since nothing else bounds this # loop, we spin CPU trying to handle packets that can't be # handled. Sleep here to treat that situation as though the # queue is empty. if (backlog.length > 0 && backlog.length == incomplete.length) ::IO.select(nil, nil, nil, 0.10) end while incomplete.length > 0 @pqueue << incomplete.shift end if(@pqueue.length > 100) removed = [] (1..25).each { removed << @pqueue.pop } dlog("Backlog has grown to over 100 in monitor_socket, dropping older packets: #{removed.map{|x| x.inspect}.join(" - ")}", 'meterpreter', LEV_1) end end rescue ::Exception => e dlog("Exception caught in monitor_socket dispatcher: #{e.class} #{e} #{e.backtrace}", 'meterpreter', LEV_1) ensure self.receiver_thread.kill if self.receiver_thread end end end # # Parses data from the dispatcher's sock and returns a Packet context # once a full packet has been received. # def receive_packet return parser.recv(self.sock) end # # Stop the monitor # def monitor_stop if(self.receiver_thread) self.receiver_thread.kill self.receiver_thread = nil end if(self.dispatcher_thread) self.dispatcher_thread.kill self.dispatcher_thread = nil end end ## # # Waiter registration # ## # # Adds a waiter association with the supplied request packet. # def add_response_waiter(request, completion_routine = nil, completion_param = nil) waiter = PacketResponseWaiter.new(request.rid, completion_routine, completion_param) self.waiters << waiter return waiter end # # Notifies a whomever is waiting for a the supplied response, # if anyone. # def notify_response_waiter(response) handled = false self.waiters.each() { |waiter| if (waiter.waiting_for?(response)) waiter.notify(response) remove_response_waiter(waiter) handled = true break end } return handled end # # Removes a waiter from the list of waiters. # def remove_response_waiter(waiter) self.waiters.delete(waiter) end ## # # Dispatching # ## # # Initializes the inbound handlers. # def initialize_inbound_handlers @inbound_handlers = [] end # # Dispatches and processes an inbound packet. If the packet is a # response that has an associated waiter, the waiter is notified. # Otherwise, the packet is passed onto any registered dispatch # handlers until one returns success. # def dispatch_inbound_packet(packet) handled = false # Update our last reply time self.last_checkin = Time.now # If the packet is a response, try to notify any potential # waiters if packet.response? if (notify_response_waiter(packet)) return true end end # Enumerate all of the inbound packet handlers until one handles # the packet @inbound_handlers.each { |handler| handled = nil begin if packet.response? handled = handler.response_handler(self, packet) else handled = handler.request_handler(self, packet) end rescue ::Exception => e dlog("Exception caught in dispatch_inbound_packet: handler=#{handler} #{e.class} #{e} #{e.backtrace}", 'meterpreter', LEV_1) return true end if (handled) break end } return handled end # # Registers an inbound packet handler that implements the # InboundPacketHandler interface. # def register_inbound_handler(handler) @inbound_handlers << handler end # # Deregisters a previously registered inbound packet handler. # def deregister_inbound_handler(handler) @inbound_handlers.delete(handler) end protected attr_accessor :receiver_thread # :nodoc: attr_accessor :dispatcher_thread # :nodoc: attr_accessor :waiters # :nodoc: end end; end; end