Skip to content

Commit 076848e

Browse files
committed
Land rapid7#7993, Keep sessions in progress alive
2 parents f9e4fd5 + e5d0370 commit 076848e

File tree

4 files changed

+69
-28
lines changed

4 files changed

+69
-28
lines changed

lib/rex/post/meterpreter/packet_dispatcher.rb

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ def keepalive
284284
# Reception
285285
#
286286
##
287+
288+
#
289+
# Simple class to track packets and if they are in-progress or complete.
290+
#
291+
class QueuedPacket
292+
attr_reader :packet
293+
attr_reader :in_progress
294+
295+
def initialize(packet, in_progress)
296+
@packet = packet
297+
@in_progress = in_progress
298+
end
299+
end
300+
287301
#
288302
# Monitors the PacketDispatcher's sock for data in its own
289303
# thread context and parsers all inbound packets.
@@ -306,8 +320,8 @@ def monitor_socket
306320
begin
307321
rv = Rex::ThreadSafe.select([ self.sock.fd ], nil, nil, PING_TIME)
308322
if rv
309-
packet = receive_packet
310-
@pqueue << packet if packet
323+
packet, in_progress = receive_packet
324+
@pqueue << QueuedPacket.new(packet, in_progress)
311325
elsif self.send_keepalives && @pqueue.empty?
312326
keepalive
313327
end
@@ -342,11 +356,11 @@ def monitor_socket
342356
tmp_channel = []
343357
tmp_close = []
344358
backlog.each do |pkt|
345-
if(pkt.response?)
359+
if(pkt.packet.response?)
346360
tmp_command << pkt
347361
next
348362
end
349-
if(pkt.method == "core_channel_close")
363+
if(pkt.packet.method == "core_channel_close")
350364
tmp_close << pkt
351365
next
352366
end
@@ -365,21 +379,23 @@ def monitor_socket
365379
backlog.each do |pkt|
366380

367381
begin
368-
if ! dispatch_inbound_packet(pkt)
382+
if ! dispatch_inbound_packet(pkt.packet, pkt.in_progress)
369383
# Keep Packets in the receive queue until a handler is registered
370384
# for them. Packets will live in the receive queue for up to
371385
# PACKET_TIMEOUT seconds, after which they will be dropped.
372386
#
373387
# A common reason why there would not immediately be a handler for
374388
# a received Packet is in channels, where a connection may
375389
# open and receive data before anything has asked to read.
376-
if (::Time.now.to_i - pkt.created_at.to_i < PACKET_TIMEOUT)
390+
#
391+
# Also, don't bother saving incomplete packets if we have no handler.
392+
if (!pkt.in_progress and ::Time.now.to_i - pkt.packet.created_at.to_i < PACKET_TIMEOUT)
377393
incomplete << pkt
378394
end
379395
end
380396

381397
rescue ::Exception => e
382-
dlog("Dispatching exception with packet #{pkt}: #{e} #{e.backtrace}", 'meterpreter', LEV_1)
398+
dlog("Dispatching exception with packet #{pkt.packet}: #{e} #{e.backtrace}", 'meterpreter', LEV_1)
383399
end
384400
end
385401

@@ -459,12 +475,16 @@ def add_response_waiter(request, completion_routine = nil, completion_param = ni
459475
# Notifies a whomever is waiting for a the supplied response,
460476
# if anyone.
461477
#
462-
def notify_response_waiter(response)
478+
# For not-yet-complete responses, we might not be able to determine
479+
# the response ID, in that case just let all waiters know that some
480+
# responses are trickling in.
481+
#
482+
def notify_response_waiter(response, in_progress=false)
463483
handled = false
464484
self.waiters.each() { |waiter|
465-
if (waiter.waiting_for?(response))
466-
waiter.notify(response)
467-
remove_response_waiter(waiter)
485+
if (in_progress || waiter.waiting_for?(response))
486+
waiter.notify(response, in_progress)
487+
remove_response_waiter(waiter) unless in_progress
468488
handled = true
469489
break
470490
end
@@ -498,7 +518,7 @@ def initialize_inbound_handlers
498518
# Otherwise, the packet is passed onto any registered dispatch
499519
# handlers until one returns success.
500520
#
501-
def dispatch_inbound_packet(packet)
521+
def dispatch_inbound_packet(packet, in_progress=false)
502522
handled = false
503523

504524
# Update our last reply time
@@ -507,7 +527,7 @@ def dispatch_inbound_packet(packet)
507527
# If the packet is a response, try to notify any potential
508528
# waiters
509529
if packet.response?
510-
if (notify_response_waiter(packet))
530+
if (notify_response_waiter(packet, in_progress))
511531
return true
512532
end
513533
end

lib/rex/post/meterpreter/packet_parser.rb

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,27 @@ def recv(sock)
7575
end
7676
end
7777

78+
in_progress = true
79+
80+
# TODO: cipher decryption
81+
if (cipher)
82+
end
83+
84+
# Deserialize the packet from the raw buffer
85+
packet.from_r(self.raw)
86+
7887
# If we've finished reading the entire packet
7988
if ((self.hdr_length_left == 0) &&
8089
(self.payload_length_left == 0))
8190

82-
# TODO: cipher decryption
83-
if (cipher)
84-
end
85-
86-
# Deserialize the packet from the raw buffer
87-
packet.from_r(self.raw)
88-
8991
# Reset our state
9092
reset
9193

92-
return packet
94+
# packet is complete!
95+
in_progress = false
9396
end
97+
98+
return packet, in_progress
9499
end
95100

96101
protected

lib/rex/post/meterpreter/packet_response_waiter.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,17 @@ class PacketResponseWaiter
3939
# @return [Integer] request ID to wait for
4040
attr_accessor :rid
4141

42+
# @return [Boolean] indicates if part of the response has been received
43+
attr_accessor :in_progress
44+
4245
#
4346
# Initializes a response waiter instance for the supplied request
4447
# identifier.
4548
#
4649
def initialize(rid, completion_routine = nil, completion_param = nil)
4750
self.rid = rid.dup
4851
self.response = nil
52+
self.in_progress = false
4953

5054
if (completion_routine)
5155
self.completion_routine = completion_routine
@@ -69,14 +73,21 @@ def waiting_for?(packet)
6973
#
7074
# @param response [Packet]
7175
# @return [void]
72-
def notify(response)
76+
def notify(response, in_progress = false)
7377
if (self.completion_routine)
74-
self.response = response
75-
self.completion_routine.call(response, self.completion_param)
78+
self.in_progress = in_progress
79+
unless in_progress
80+
self.response = response
81+
self.completion_routine.call(response, self.completion_param)
82+
end
7683
else
7784
self.mutex.synchronize do
78-
self.response = response
79-
self.cond.signal
85+
self.in_progress = in_progress
86+
unless in_progress
87+
# complete packet, ready for processing...
88+
self.response = response
89+
self.cond.signal
90+
end
8091
end
8192
end
8293
end
@@ -92,7 +103,11 @@ def wait(interval)
92103
interval = nil if interval and interval == -1
93104
self.mutex.synchronize do
94105
if self.response.nil?
95-
self.cond.wait(self.mutex, interval)
106+
loop do
107+
self.cond.wait(self.mutex, interval)
108+
break unless self.in_progress
109+
self.in_progress = false
110+
end
96111
end
97112
end
98113
return self.response

spec/lib/rex/post/meterpreter/packet_parser_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626

2727
it "should parse valid raw data into a packet object" do
2828
while @raw.length >0
29-
parsed_packet = parser.recv(@sock)
29+
parsed_packet, in_progress = parser.recv(@sock)
3030
end
3131
expect(parsed_packet).to be_a Rex::Post::Meterpreter::Packet
3232
expect(parsed_packet.type).to eq Rex::Post::Meterpreter::PACKET_TYPE_REQUEST
3333
expect(parsed_packet.method?("test_method")).to eq true
34+
expect(in_progress).to eq false
3435
end
3536

3637
end

0 commit comments

Comments
 (0)