Skip to content

Commit 7deae90

Browse files
committed
Land rapid7#6499 : Fix reverse_tcp handling of disconnects
Fixes rapid7#6497
2 parents 6187354 + a587975 commit 7deae90

File tree

1 file changed

+61
-63
lines changed

1 file changed

+61
-63
lines changed

lib/msf/core/handler/reverse_tcp.rb

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
module Msf
66
module Handler
7-
87
###
98
#
109
# This module implements the reverse TCP handler. This means
@@ -16,7 +15,6 @@ module Handler
1615
#
1716
###
1817
module ReverseTcp
19-
2018
include Msf::Handler
2119
include Msf::Handler::Reverse
2220
include Msf::Handler::Reverse::Comm
@@ -26,7 +24,7 @@ module ReverseTcp
2624
# 'reverse_tcp'.
2725
#
2826
def self.handler_type
29-
return "reverse_tcp"
27+
"reverse_tcp"
3028
end
3129

3230
#
@@ -55,7 +53,6 @@ def initialize(info = {})
5553
self.conn_threads = []
5654
end
5755

58-
5956
#
6057
# Closes the listener socket if one was created.
6158
#
@@ -64,9 +61,13 @@ def cleanup_handler
6461

6562
# Kill any remaining handle_connection threads that might
6663
# be hanging around
67-
conn_threads.each { |thr|
68-
thr.kill rescue nil
69-
}
64+
conn_threads.each do |thr|
65+
begin
66+
thr.kill
67+
rescue
68+
nil
69+
end
70+
end
7071
end
7172

7273
# A string suitable for displaying to the user
@@ -84,138 +85,135 @@ def start_handler
8485

8586
local_port = bind_port
8687

87-
self.listener_thread = framework.threads.spawn("ReverseTcpHandlerListener-#{local_port}", false, queue) { |lqueue|
88+
handler_name = "ReverseTcpHandlerListener-#{local_port}"
89+
self.listener_thread = framework.threads.spawn(handler_name, false, queue) { |lqueue|
8890
loop do
8991
# Accept a client connection
9092
begin
91-
client = self.listener_sock.accept
92-
if ! client
93-
wlog("ReverseTcpHandlerListener-#{local_port}: No client received in call to accept, exiting...")
94-
break
93+
client = listener_sock.accept
94+
if client
95+
self.pending_connections += 1
96+
lqueue.push(client)
9597
end
96-
97-
self.pending_connections += 1
98-
lqueue.push(client)
99-
rescue ::Exception
100-
wlog("ReverseTcpHandlerListener-#{local_port}: Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}")
101-
break
98+
rescue Errno::ENOTCONN
99+
nil
100+
rescue StandardError => e
101+
wlog [
102+
"#{handler_name}: Exception raised during listener accept: #{e.class}",
103+
"#{$ERROR_INFO}",
104+
"#{$ERROR_POSITION.join("\n")}"
105+
].join("\n")
102106
end
103107
end
104108
}
105109

106-
self.handler_thread = framework.threads.spawn("ReverseTcpHandlerWorker-#{local_port}", false, queue) { |cqueue|
110+
worker_name = "ReverseTcpHandlerWorker-#{local_port}"
111+
self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue|
107112
loop do
108113
begin
109114
client = cqueue.pop
110115

111-
if ! client
112-
elog("ReverseTcpHandlerWorker-#{local_port}: Queue returned an empty result, exiting...")
113-
break
116+
unless client
117+
elog("#{worker_name}: Queue returned an empty result, exiting...")
114118
end
115119

116120
# Timeout and datastore options need to be passed through to the client
117121
opts = {
118-
:datastore => datastore,
119-
:expiration => datastore['SessionExpirationTimeout'].to_i,
120-
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
121-
:retry_total => datastore['SessionRetryTotal'].to_i,
122-
:retry_wait => datastore['SessionRetryWait'].to_i
122+
datastore: datastore,
123+
expiration: datastore['SessionExpirationTimeout'].to_i,
124+
comm_timeout: datastore['SessionCommunicationTimeout'].to_i,
125+
retry_total: datastore['SessionRetryTotal'].to_i,
126+
retry_wait: datastore['SessionRetryWait'].to_i
123127
}
124128

125129
if datastore['ReverseListenerThreaded']
126-
self.conn_threads << framework.threads.spawn("ReverseTcpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { |client_copy|
130+
thread_name = "#{worker_name}-#{client.peerhost}"
131+
conn_threads << framework.threads.spawn(thread_name, false, client) do |client_copy|
127132
handle_connection(wrap_aes_socket(client_copy), opts)
128-
}
133+
end
129134
else
130135
handle_connection(wrap_aes_socket(client), opts)
131136
end
132-
rescue ::Exception
133-
elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}")
137+
rescue StandardError
138+
elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}")
134139
end
135140
end
136141
}
137-
138142
end
139143

140144
def wrap_aes_socket(sock)
141-
if datastore["PAYLOAD"] !~ /java\// or (datastore["AESPassword"] || "") == ""
145+
if datastore["PAYLOAD"] !~ %r{java/} || (datastore["AESPassword"] || "") == ""
142146
return sock
143147
end
144148

145-
socks = Rex::Socket::tcp_socket_pair()
149+
socks = Rex::Socket.tcp_socket_pair
146150
socks[0].extend(Rex::Socket::Tcp)
147151
socks[1].extend(Rex::Socket::Tcp)
148152

149153
m = OpenSSL::Digest.new('md5')
150154
m.reset
151155
key = m.digest(datastore["AESPassword"] || "")
152156

153-
Rex::ThreadFactory.spawn('Session-AESEncrypt', false) {
157+
Rex::ThreadFactory.spawn('Session-AESEncrypt', false) do
154158
c1 = OpenSSL::Cipher.new('aes-128-cfb8')
155159
c1.encrypt
156-
c1.key=key
160+
c1.key = key
157161
sock.put([0].pack('N'))
158-
sock.put(c1.iv=c1.random_iv)
162+
sock.put((c1.iv = c1.random_iv))
159163
buf1 = socks[0].read(4096)
160-
while buf1 and buf1 != ""
164+
while buf1 && buf1 != ""
161165
sock.put(c1.update(buf1))
162166
buf1 = socks[0].read(4096)
163167
end
164-
sock.close()
165-
}
166-
Rex::ThreadFactory.spawn('Session-AESDecrypt', false) {
168+
sock.close
169+
end
170+
171+
Rex::ThreadFactory.spawn('Session-AESDecrypt', false) do
167172
c2 = OpenSSL::Cipher.new('aes-128-cfb8')
168173
c2.decrypt
169-
c2.key=key
170-
iv=""
171-
while iv.length < 16
172-
iv << sock.read(16-iv.length)
173-
end
174+
c2.key = key
175+
176+
iv = ""
177+
iv << sock.read(16 - iv.length) while iv.length < 16
178+
174179
c2.iv = iv
175180
buf2 = sock.read(4096)
176-
while buf2 and buf2 != ""
181+
while buf2 && buf2 != ""
177182
socks[0].put(c2.update(buf2))
178183
buf2 = sock.read(4096)
179184
end
180-
socks[0].close()
181-
}
182-
return socks[1]
185+
socks[0].close
186+
end
187+
188+
socks[1]
183189
end
184190

185191
#
186192
# Stops monitoring for an inbound connection.
187193
#
188194
def stop_handler
189195
# Terminate the listener thread
190-
if (self.listener_thread and self.listener_thread.alive? == true)
191-
self.listener_thread.kill
192-
self.listener_thread = nil
193-
end
196+
listener_thread.kill if listener_thread && listener_thread.alive? == true
194197

195198
# Terminate the handler thread
196-
if (self.handler_thread and self.handler_thread.alive? == true)
197-
self.handler_thread.kill
198-
self.handler_thread = nil
199-
end
199+
handler_thread.kill if handler_thread && handler_thread.alive? == true
200200

201-
if (self.listener_sock)
201+
if listener_sock
202202
begin
203-
self.listener_sock.close
203+
listener_sock.close
204204
rescue IOError
205205
# Ignore if it's listening on a dead session
206206
dlog("IOError closing listener sock; listening on dead session?", LEV_1)
207207
end
208-
self.listener_sock = nil
209208
end
210209
end
211210

212-
protected
211+
protected
213212

214213
attr_accessor :listener_sock # :nodoc:
215214
attr_accessor :listener_thread # :nodoc:
216215
attr_accessor :handler_thread # :nodoc:
217216
attr_accessor :conn_threads # :nodoc:
218217
end
219-
220218
end
221219
end

0 commit comments

Comments
 (0)