Land #6304, simplify Meterpreter livelness checks
commit
eec6a6f905
|
@ -112,6 +112,7 @@ class Client
|
||||||
self.target_id = opts[:target_id]
|
self.target_id = opts[:target_id]
|
||||||
self.capabilities = opts[:capabilities] || {}
|
self.capabilities = opts[:capabilities] || {}
|
||||||
self.commands = []
|
self.commands = []
|
||||||
|
self.last_checkin = Time.now
|
||||||
|
|
||||||
self.conn_id = opts[:conn_id]
|
self.conn_id = opts[:conn_id]
|
||||||
self.url = opts[:url]
|
self.url = opts[:url]
|
||||||
|
|
|
@ -42,23 +42,32 @@ end
|
||||||
###
|
###
|
||||||
module PacketDispatcher
|
module PacketDispatcher
|
||||||
|
|
||||||
PacketTimeout = 600
|
# 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
|
||||||
#
|
#
|
||||||
# Synchronization
|
# @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
|
attr_accessor :comm_mutex
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Passive Dispatching
|
# Passive Dispatching
|
||||||
#
|
#
|
||||||
##
|
# @return [Rex::ServiceManager]
|
||||||
attr_accessor :passive_service, :send_queue, :recv_queue
|
# @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
|
def initialize_passive_dispatcher
|
||||||
self.send_queue = []
|
self.send_queue = []
|
||||||
|
@ -159,9 +168,6 @@ module PacketDispatcher
|
||||||
|
|
||||||
if (raw)
|
if (raw)
|
||||||
|
|
||||||
# This mutex is used to lock out new commands during an
|
|
||||||
# active migration.
|
|
||||||
|
|
||||||
self.comm_mutex.synchronize do
|
self.comm_mutex.synchronize do
|
||||||
begin
|
begin
|
||||||
bytes = self.sock.write(raw)
|
bytes = self.sock.write(raw)
|
||||||
|
@ -174,9 +180,6 @@ module PacketDispatcher
|
||||||
# Mark the session itself as dead
|
# Mark the session itself as dead
|
||||||
self.alive = false
|
self.alive = false
|
||||||
|
|
||||||
# Indicate that the dispatcher should shut down too
|
|
||||||
@finish = true
|
|
||||||
|
|
||||||
# Reraise the error to the top-level caller
|
# Reraise the error to the top-level caller
|
||||||
raise err if err
|
raise err if err
|
||||||
end
|
end
|
||||||
|
@ -188,15 +191,16 @@ module PacketDispatcher
|
||||||
#
|
#
|
||||||
# Sends a packet and waits for a timeout for the given time interval.
|
# Sends a packet and waits for a timeout for the given time interval.
|
||||||
#
|
#
|
||||||
def send_request(packet, t = self.response_timeout)
|
# @param packet [Packet] request to send
|
||||||
if not t
|
# @param timeout [Fixnum,nil] seconds to wait for response, or nil to ignore the
|
||||||
send_packet(packet)
|
# 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
|
return nil
|
||||||
end
|
elsif response.nil?
|
||||||
|
|
||||||
response = send_packet_wait_response(packet, t)
|
|
||||||
|
|
||||||
if (response == nil)
|
|
||||||
raise TimeoutError.new("Send timed out")
|
raise TimeoutError.new("Send timed out")
|
||||||
elsif (response.result != 0)
|
elsif (response.result != 0)
|
||||||
einfo = lookup_error(response.result)
|
einfo = lookup_error(response.result)
|
||||||
|
@ -213,26 +217,58 @@ module PacketDispatcher
|
||||||
#
|
#
|
||||||
# Transmits a packet and waits for a response.
|
# Transmits a packet and waits for a response.
|
||||||
#
|
#
|
||||||
def send_packet_wait_response(packet, t)
|
# @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
|
# First, add the waiter association for the supplied packet
|
||||||
waiter = add_response_waiter(packet)
|
waiter = add_response_waiter(packet)
|
||||||
|
|
||||||
|
bytes_written = send_packet(packet)
|
||||||
|
|
||||||
# Transmit the packet
|
# Transmit the packet
|
||||||
if (send_packet(packet).to_i <= 0)
|
if (bytes_written.to_i <= 0)
|
||||||
# Remove the waiter if we failed to send the packet.
|
# Remove the waiter if we failed to send the packet.
|
||||||
remove_response_waiter(waiter)
|
remove_response_waiter(waiter)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not timeout
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
# Wait for the supplied time interval
|
# Wait for the supplied time interval
|
||||||
waiter.wait(t)
|
response = waiter.wait(timeout)
|
||||||
|
|
||||||
# Remove the waiter from the list of waiters in case it wasn't
|
# Remove the waiter from the list of waiters in case it wasn't
|
||||||
# removed
|
# removed. This happens if the waiter timed out above.
|
||||||
remove_response_waiter(waiter)
|
remove_response_waiter(waiter)
|
||||||
|
|
||||||
# Return the response packet, if any
|
# Return the response packet, if any
|
||||||
return waiter.response
|
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
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -254,58 +290,22 @@ module PacketDispatcher
|
||||||
self.waiters = []
|
self.waiters = []
|
||||||
|
|
||||||
@pqueue = ::Queue.new
|
@pqueue = ::Queue.new
|
||||||
@finish = false
|
|
||||||
@last_recvd = Time.now
|
|
||||||
@ping_sent = false
|
@ping_sent = false
|
||||||
|
|
||||||
self.alive = true
|
|
||||||
|
|
||||||
# Spawn a thread for receiving packets
|
# Spawn a thread for receiving packets
|
||||||
self.receiver_thread = Rex::ThreadFactory.spawn("MeterpreterReceiver", false) do
|
self.receiver_thread = Rex::ThreadFactory.spawn("MeterpreterReceiver", false) do
|
||||||
while (self.alive)
|
while (self.alive)
|
||||||
begin
|
begin
|
||||||
rv = Rex::ThreadSafe.select([ self.sock.fd ], nil, nil, 0.25)
|
rv = Rex::ThreadSafe.select([ self.sock.fd ], nil, nil, PING_TIME)
|
||||||
ping_time = 60
|
if rv
|
||||||
# If there's nothing to read, and it's been awhile since we
|
packet = receive_packet
|
||||||
# saw a packet, we need to send a ping. We wait
|
@pqueue << packet if packet
|
||||||
# ping_time*2 seconds before deciding a session is dead.
|
elsif self.send_keepalives && @pqueue.empty?
|
||||||
if (not rv and self.send_keepalives and Time.now - @last_recvd > ping_time)
|
keepalive
|
||||||
# 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
|
end
|
||||||
next if not rv
|
rescue ::Exception => e
|
||||||
packet = receive_packet
|
dlog("Exception caught in monitor_socket: #{e.class}: #{e}", 'meterpreter', LEV_1)
|
||||||
@pqueue << packet if packet
|
dlog("Call stack: #{e.backtrace.join("\n")}", 'meterpreter', LEV_2)
|
||||||
@last_recvd = Time.now
|
|
||||||
rescue ::Exception
|
|
||||||
dlog("Exception caught in monitor_socket: #{$!}", 'meterpreter', LEV_1)
|
|
||||||
@finish = true
|
|
||||||
self.alive = false
|
self.alive = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
@ -315,9 +315,7 @@ module PacketDispatcher
|
||||||
# Spawn a new thread that monitors the socket
|
# Spawn a new thread that monitors the socket
|
||||||
self.dispatcher_thread = Rex::ThreadFactory.spawn("MeterpreterDispatcher", false) do
|
self.dispatcher_thread = Rex::ThreadFactory.spawn("MeterpreterDispatcher", false) do
|
||||||
begin
|
begin
|
||||||
# Whether we're finished or not is determined by the receiver
|
while (self.alive)
|
||||||
# thread above.
|
|
||||||
while(not @finish)
|
|
||||||
incomplete = []
|
incomplete = []
|
||||||
backlog = []
|
backlog = []
|
||||||
|
|
||||||
|
@ -352,7 +350,6 @@ module PacketDispatcher
|
||||||
backlog.push(*tmp_channel)
|
backlog.push(*tmp_channel)
|
||||||
backlog.push(*tmp_close)
|
backlog.push(*tmp_close)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Process the message queue
|
# Process the message queue
|
||||||
#
|
#
|
||||||
|
@ -363,12 +360,12 @@ module PacketDispatcher
|
||||||
if ! dispatch_inbound_packet(pkt)
|
if ! dispatch_inbound_packet(pkt)
|
||||||
# Keep Packets in the receive queue until a handler is registered
|
# Keep Packets in the receive queue until a handler is registered
|
||||||
# for them. Packets will live in the receive queue for up to
|
# for them. Packets will live in the receive queue for up to
|
||||||
# PacketTimeout, after which they will be dropped.
|
# PACKET_TIMEOUT seconds, after which they will be dropped.
|
||||||
#
|
#
|
||||||
# A common reason why there would not immediately be a handler for
|
# A common reason why there would not immediately be a handler for
|
||||||
# a received Packet is in channels, where a connection may
|
# a received Packet is in channels, where a connection may
|
||||||
# open and receive data before anything has asked to read.
|
# open and receive data before anything has asked to read.
|
||||||
if (::Time.now.to_i - pkt.created_at.to_i < PacketTimeout)
|
if (::Time.now.to_i - pkt.created_at.to_i < PACKET_TIMEOUT)
|
||||||
incomplete << pkt
|
incomplete << pkt
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -493,21 +490,15 @@ module PacketDispatcher
|
||||||
# Otherwise, the packet is passed onto any registered dispatch
|
# Otherwise, the packet is passed onto any registered dispatch
|
||||||
# handlers until one returns success.
|
# handlers until one returns success.
|
||||||
#
|
#
|
||||||
def dispatch_inbound_packet(packet, client = nil)
|
def dispatch_inbound_packet(packet)
|
||||||
handled = false
|
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
|
|
||||||
|
|
||||||
# Update our last reply time
|
# Update our last reply time
|
||||||
client.last_checkin = Time.now
|
self.last_checkin = Time.now
|
||||||
|
|
||||||
# If the packet is a response, try to notify any potential
|
# If the packet is a response, try to notify any potential
|
||||||
# waiters
|
# waiters
|
||||||
if ((resp = packet.response?))
|
if packet.response?
|
||||||
if (notify_response_waiter(packet))
|
if (notify_response_waiter(packet))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -520,11 +511,11 @@ module PacketDispatcher
|
||||||
handled = nil
|
handled = nil
|
||||||
begin
|
begin
|
||||||
|
|
||||||
if ! resp
|
if packet.response?
|
||||||
handled = handler.request_handler(client, packet)
|
handled = handler.response_handler(self, packet)
|
||||||
else
|
else
|
||||||
handled = handler.response_handler(client, packet)
|
handled = handler.request_handler(self, packet)
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue ::Exception => e
|
rescue ::Exception => e
|
||||||
dlog("Exception caught in dispatch_inbound_packet: handler=#{handler} #{e.class} #{e} #{e.backtrace}", 'meterpreter', LEV_1)
|
dlog("Exception caught in dispatch_inbound_packet: handler=#{handler} #{e.class} #{e} #{e.backtrace}", 'meterpreter', LEV_1)
|
||||||
|
|
|
@ -15,6 +15,30 @@ module Meterpreter
|
||||||
###
|
###
|
||||||
class PacketResponseWaiter
|
class PacketResponseWaiter
|
||||||
|
|
||||||
|
# Arbitrary argument to {#completion_routine}
|
||||||
|
#
|
||||||
|
# @return [Object,nil]
|
||||||
|
attr_accessor :completion_param
|
||||||
|
|
||||||
|
# A callback to be called when this waiter is notified of a packet's
|
||||||
|
# arrival. If not nil, this will be called with the response packet as first
|
||||||
|
# parameter and {#completion_param} as the second.
|
||||||
|
#
|
||||||
|
# @return [Proc,nil]
|
||||||
|
attr_accessor :completion_routine
|
||||||
|
|
||||||
|
# @return [ConditionVariable]
|
||||||
|
attr_accessor :cond
|
||||||
|
|
||||||
|
# @return [Mutex]
|
||||||
|
attr_accessor :mutex
|
||||||
|
|
||||||
|
# @return [Packet]
|
||||||
|
attr_accessor :response
|
||||||
|
|
||||||
|
# @return [Fixnum] request ID to wait for
|
||||||
|
attr_accessor :rid
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initializes a response waiter instance for the supplied request
|
# Initializes a response waiter instance for the supplied request
|
||||||
# identifier.
|
# identifier.
|
||||||
|
@ -43,6 +67,8 @@ class PacketResponseWaiter
|
||||||
#
|
#
|
||||||
# Notifies the waiter that the supplied response packet has arrived.
|
# Notifies the waiter that the supplied response packet has arrived.
|
||||||
#
|
#
|
||||||
|
# @param response [Packet]
|
||||||
|
# @return [void]
|
||||||
def notify(response)
|
def notify(response)
|
||||||
if (self.completion_routine)
|
if (self.completion_routine)
|
||||||
self.response = response
|
self.response = response
|
||||||
|
@ -56,9 +82,12 @@ class PacketResponseWaiter
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Waits for a given time interval for the response packet to arrive.
|
# Wait for a given time interval for the response packet to arrive.
|
||||||
# If the interval is -1 we can wait forever.
|
|
||||||
#
|
#
|
||||||
|
# @param interval [Fixnum,nil] number of seconds to wait, or nil to wait
|
||||||
|
# forever
|
||||||
|
# @return [Packet,nil] the response, or nil if the interval elapsed before
|
||||||
|
# receiving one
|
||||||
def wait(interval)
|
def wait(interval)
|
||||||
interval = nil if interval and interval == -1
|
interval = nil if interval and interval == -1
|
||||||
self.mutex.synchronize do
|
self.mutex.synchronize do
|
||||||
|
@ -69,8 +98,6 @@ class PacketResponseWaiter
|
||||||
return self.response
|
return self.response
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :rid, :mutex, :cond, :response # :nodoc:
|
|
||||||
attr_accessor :completion_routine, :completion_param # :nodoc:
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end; end; end
|
end; end; end
|
||||||
|
|
Loading…
Reference in New Issue