module Rex::Post::Meterpreter::PacketDispatcher

Handles packet transmission, reception, and correlation, and processing

Constants

PacketTimeout

Attributes

comm_mutex[RW]

Synchronization

passive_service[RW]

Passive Dispatching

recv_queue[RW]

Passive Dispatching

send_queue[RW]

Passive Dispatching

Public Instance Methods

add_response_waiter(request, completion_routine = nil, completion_param = nil) click to toggle source

Adds a waiter association with the supplied request packet.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 427
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
deregister_inbound_handler(handler) click to toggle source

Deregisters a previously registered inbound packet handler.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 530
def deregister_inbound_handler(handler)
  @inbound_handlers.delete(handler)
end
dispatch_inbound_packet(packet, client = nil) click to toggle source

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.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 477
def dispatch_inbound_packet(packet, client = nil)
  handled = false

  # If no client context was provided, return self as PacketDispatcher
  # is a mixin for the Client instance
  if (client == nil)
    client = self
  end

  # If the packet is a response, try to notify any potential
  # waiters
  if ((resp = 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 ! resp
      handled = handler.request_handler(client, packet)
    else
      handled = handler.response_handler(client, 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
initialize_inbound_handlers() click to toggle source

Initializes the inbound handlers.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 467
def initialize_inbound_handlers
  @inbound_handlers = []
end
initialize_passive_dispatcher() click to toggle source
# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 62
def initialize_passive_dispatcher
  self.send_queue = []
  self.recv_queue = []
  self.waiters    = []
  self.alive      = true

  self.passive_service = self.passive_dispatcher
  self.passive_service.remove_resource("/" + self.conn_id  + "/")
  self.passive_service.add_resource("/" + self.conn_id + "/",
    'Proc'             => Proc.new { |cli, req| on_passive_request(cli, req) },
    'VirtualDirectory' => true
  )
end
monitor_socket() click to toggle source

Reception

Monitors the PacketDispatcher’s sock for data in its own thread context and parsers all inbound packets.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 236
def monitor_socket

  # Skip if we are using a passive dispatcher
  return if self.passive_service

  self.comm_mutex = ::Mutex.new

  self.waiters = []

  @pqueue = []
  @finish = false
  @last_recvd = Time.now
  @ping_sent = false

  self.alive = true

  # 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, 0.25)
        ping_time = 60
        # If there's nothing to read, and it's been awhile since we
        # saw a packet, we need to send a ping.  We wait
        # ping_time*2 seconds before deciding a session is dead.
        if (not rv and self.send_keepalives and Time.now - @last_recvd > ping_time)
          # If the queue is empty and we've already sent a
          # keepalive without getting a reply, then this
          # session is hosed, and we should give up on it.
          if @ping_sent and @pqueue.empty? and (Time.now - @last_recvd > ping_time * 2)
            dlog("No response to ping, session #{self.sid} is dead", LEV_3)
            self.alive = false
            @finish = true
            break
          end
          # Let the packet queue processor finish up before
          # we send a ping.
          if not @ping_sent and @pqueue.empty?
            # Our 'ping' is actually just 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.
            pkt = Packet.create_request('core_channel_eof')
            pkt.add_tlv(TLV_TYPE_CHANNEL_ID, 0)
            waiter = Proc.new { |response, param|
                @ping_sent = false
                @last_recvd = Time.now
              }
            send_packet(pkt, waiter)
            @ping_sent = true
          end
          next
        end
        next if not rv
        packet = receive_packet
        @pqueue << packet if packet
        @last_recvd = Time.now
      rescue ::Exception
        dlog("Exception caught in monitor_socket: #{$!}", 'meterpreter', LEV_1)
        @finish = true
        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
    # Whether we're finished or not is determined by the receiver
    # thread above.
    while(not @finish)
      if(@pqueue.empty?)
        ::IO.select(nil, nil, nil, 0.10)
        next
      end

      incomplete = []
      backlog    = []

      while(@pqueue.length > 0)
        backlog << @pqueue.shift
      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)
          # Only requeue packets newer than the timeout
          if (::Time.now.to_i - pkt.created_at.to_i < PacketTimeout)
            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

      @pqueue.unshift(*incomplete)

      if(@pqueue.length > 100)
        dlog("Backlog has grown to over 100 in monitor_socket, dropping older packets: #{@pqueue[0 .. 25].map{|x| x.inspect}.join(" - ")}", 'meterpreter', LEV_1)
        @pqueue = @pqueue[25 .. 100]
      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
monitor_stop() click to toggle source

Stop the monitor

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 406
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
notify_response_waiter(response) click to toggle source

Notifies a whomever is waiting for a the supplied response, if anyone.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 439
def notify_response_waiter(response)
  self.waiters.each() { |waiter|
    if (waiter.waiting_for?(response))
      waiter.notify(response)

      remove_response_waiter(waiter)

      break
    end
  }
end
on_passive_request(cli, req) click to toggle source
# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 88
def on_passive_request(cli, req)

  begin

  resp = Rex::Proto::Http::Response.new(200, "OK")
  resp['Content-Type'] = 'application/octet-stream'
  resp['Connection']   = 'close'

  # If the first 4 bytes are "RECV", return the oldest packet from the outbound queue
  if req.body[0,4] == "RECV"
    rpkt = send_queue.pop
    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

  # Force a closure for older WinInet implementations
  self.passive_service.close_client( cli )

  rescue ::Exception => e
    elog("Exception handling request: #{cli.inspect} #{req.inspect} #{e.class} #{e} #{e.backtrace}")
  end
end
receive_packet() click to toggle source

Parses data from the dispatcher’s sock and returns a Packet context once a full packet has been received.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 399
def receive_packet
  return parser.recv(self.sock)
end
register_inbound_handler(handler) click to toggle source

Registers an inbound packet handler that implements the InboundPacketHandler interface.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 523
def register_inbound_handler(handler)
  @inbound_handlers << handler
end
remove_response_waiter(waiter) click to toggle source

Removes a waiter from the list of waiters.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 454
def remove_response_waiter(waiter)
  self.waiters.delete(waiter)
end
send_packet(packet, completion_routine = nil, completion_param = nil) click to toggle source

Sends a packet without waiting for a response.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 133
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)

    # This mutex is used to lock out new commands during an
    # active migration.

    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

      # Indicate that the dispatcher should shut down too
      @finish = true

      # Reraise the error to the top-level caller
      raise err if err
    end
  end

  return bytes
end
send_packet_wait_response(packet, t) click to toggle source

Transmits a packet and waits for a response.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 205
def send_packet_wait_response(packet, t)
  # First, add the waiter association for the supplied packet
  waiter = add_response_waiter(packet)

  # Transmit the packet
  if (send_packet(packet).to_i <= 0)
    # Remove the waiter if we failed to send the packet.
    remove_response_waiter(waiter)
    return nil
  end

  # Wait for the supplied time interval
  waiter.wait(t)

  # Remove the waiter from the list of waiters in case it wasn't
  # removed
  remove_response_waiter(waiter)

  # Return the response packet, if any
  return waiter.response
end
send_request(packet, t = self.response_timeout) click to toggle source

Sends a packet and waits for a timeout for the given time interval.

# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 179
def send_request(packet, t = self.response_timeout)

  if not t
    send_packet(packet)
    return nil
  end

  response = send_packet_wait_response(packet, t)

  if (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
shutdown_passive_dispatcher() click to toggle source
# File lib/rex/post/meterpreter/packet_dispatcher.rb, line 76
def shutdown_passive_dispatcher
  return if not self.passive_service
  self.passive_service.remove_resource("/" + self.conn_id  + "/")

  self.alive      = false
  self.send_queue = []
  self.recv_queue = []
  self.waiters    = []

  self.passive_service = nil
end