Make external module read loop more robust
Changes from a "hope we get at most one message at a time" model to something beginning to resemble a state machine. Also logs error output and fails the MSF module when the external module fails.MS-2855/keylogger-mettle-extension
parent
39f06a3995
commit
dd57138423
|
@ -2,6 +2,7 @@ include Msf::Auxiliary::Report
|
||||||
|
|
||||||
module Msf::Module::External
|
module Msf::Module::External
|
||||||
def wait_status(mod)
|
def wait_status(mod)
|
||||||
|
begin
|
||||||
while mod.running
|
while mod.running
|
||||||
m = mod.get_status
|
m = mod.get_status
|
||||||
if m
|
if m
|
||||||
|
@ -16,6 +17,10 @@ module Msf::Module::External
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
rescue Exception => e #Msf::Modules::External::Bridge::Error => e
|
||||||
|
elog e.backtrace.join("\n")
|
||||||
|
fail_with Failure::UNKNOWN, e.message
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_output(m)
|
def log_output(m)
|
||||||
|
|
|
@ -43,12 +43,13 @@ class Msf::Modules::External::Bridge
|
||||||
self.path = module_path
|
self.path = module_path
|
||||||
self.cmd = [self.path, self.path]
|
self.cmd = [self.path, self.path]
|
||||||
self.messages = Queue.new
|
self.messages = Queue.new
|
||||||
|
self.buf = ''
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :path, :running
|
attr_writer :path, :running
|
||||||
attr_accessor :cmd, :env, :ios, :messages
|
attr_accessor :cmd, :env, :ios, :buf, :messages, :wait_thread
|
||||||
|
|
||||||
def describe
|
def describe
|
||||||
resp = send_receive(Msf::Modules::External::Message.new(:describe))
|
resp = send_receive(Msf::Modules::External::Message.new(:describe))
|
||||||
|
@ -64,8 +65,9 @@ class Msf::Modules::External::Bridge
|
||||||
end
|
end
|
||||||
|
|
||||||
def send(message)
|
def send(message)
|
||||||
input, output, status = ::Open3.popen3(self.env, self.cmd)
|
input, output, err, status = ::Open3.popen3(self.env, self.cmd)
|
||||||
self.ios = [input, output, status]
|
self.ios = [input, output, err]
|
||||||
|
self.wait_thread = status
|
||||||
# We would call Rex::Threadsafe directly, but that would require rex for standalone use
|
# We would call Rex::Threadsafe directly, but that would require rex for standalone use
|
||||||
case select(nil, [input], nil, 0.1)
|
case select(nil, [input], nil, 0.1)
|
||||||
when nil
|
when nil
|
||||||
|
@ -91,33 +93,55 @@ class Msf::Modules::External::Bridge
|
||||||
end
|
end
|
||||||
|
|
||||||
def recv(filter_id=nil, timeout=600)
|
def recv(filter_id=nil, timeout=600)
|
||||||
_, fd, _ = self.ios
|
_, out, err = self.ios
|
||||||
|
message = ''
|
||||||
|
|
||||||
# Multiple messages can come over the wire all at once, and since yajl
|
# Multiple messages can come over the wire all at once, and since yajl
|
||||||
# doesn't play nice with windows, we have to emulate a state machine to
|
# doesn't play nice with windows, we have to emulate a state machine to
|
||||||
# read just enough off the wire to get one request at a time. Since
|
# read just enough off the wire to get one request at a time. Since
|
||||||
# Windows cannot do a nonblocking read on a pipe, we are forced to do a
|
# Windows cannot do a nonblocking read on a pipe, we are forced to do a
|
||||||
# whole lot of `select` syscalls :(
|
# whole lot of `select` syscalls and keep a buffer ourselves :(
|
||||||
buf = ""
|
|
||||||
begin
|
begin
|
||||||
loop do
|
loop do
|
||||||
|
# This is so we don't end up calling JSON.parse on every char and
|
||||||
|
# catch an exception. Windows can't do nonblock on pipes, so we
|
||||||
|
# still have to do the select if we are not at the end of object
|
||||||
|
# and don't have any buffer left
|
||||||
|
parts = self.buf.split '}', 2
|
||||||
|
if parts.length == 2 # [part, rest]
|
||||||
|
message << parts[0] << '}'
|
||||||
|
self.buf = parts[1]
|
||||||
|
break
|
||||||
|
elsif parts.length == 1 # [part]
|
||||||
|
if self.buf[-1] == '}'
|
||||||
|
message << parts[0] << '}'
|
||||||
|
self.buf = ''
|
||||||
|
break
|
||||||
|
else
|
||||||
|
message << parts[0]
|
||||||
|
self.buf = ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# We would call Rex::Threadsafe directly, but that would require Rex for standalone use
|
# We would call Rex::Threadsafe directly, but that would require Rex for standalone use
|
||||||
case select([fd], nil, nil, timeout)
|
res = select([out, err], nil, nil, timeout)
|
||||||
when nil
|
if res == nil
|
||||||
# This is what we would have gotten without Rex and what `readpartial` can also raise
|
# This is what we would have gotten without Rex and what `readpartial` can also raise
|
||||||
raise EOFError.new
|
raise EOFError.new
|
||||||
when [[fd], [], []]
|
else
|
||||||
c = fd.readpartial(1)
|
fds = res[0]
|
||||||
buf << c
|
# Preferentially drain and log stderr
|
||||||
|
if fds.include? err
|
||||||
# This is so we don't end up calling JSON.parse on every char and
|
errbuf = err.readpartial(4096)
|
||||||
# having to catch an exception. Windows can't do nonblock on pipes,
|
elog "Unexpected output running #{self.path}:\n#{errbuf}"
|
||||||
# so we still have to do the select each time.
|
end
|
||||||
break if c == '}'
|
if fds.include? out
|
||||||
|
self.buf << out.readpartial(4096)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
m = Msf::Modules::External::Message.from_module(JSON.parse(buf))
|
m = Msf::Modules::External::Message.from_module(JSON.parse(message))
|
||||||
if filter_id && m.id != filter_id
|
if filter_id && m.id != filter_id
|
||||||
# We are filtering for a response to a particular message, but we got
|
# We are filtering for a response to a particular message, but we got
|
||||||
# something else, store the message and try again
|
# something else, store the message and try again
|
||||||
|
@ -128,16 +152,16 @@ class Msf::Modules::External::Bridge
|
||||||
m
|
m
|
||||||
end
|
end
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
# Probably an incomplete response, but no way to really tell
|
# Probably an incomplete response, but no way to really tell. Keep trying
|
||||||
|
# until EOF
|
||||||
retry
|
retry
|
||||||
rescue EOFError => e
|
rescue EOFError => e
|
||||||
{}
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def close_ios
|
def close_ios
|
||||||
input, output, status = self.ios
|
self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
|
||||||
[input, output].each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ def send_mail(to, mailserver, cmd, mfrom, port):
|
||||||
except smtplib.SMTPDataError as err:
|
except smtplib.SMTPDataError as err:
|
||||||
if err[0] == 450:
|
if err[0] == 450:
|
||||||
module.log("Triggered bug in target server (%s)"%err[1], 'good')
|
module.log("Triggered bug in target server (%s)"%err[1], 'good')
|
||||||
|
s.close()
|
||||||
return(True)
|
return(True)
|
||||||
module.log("Bug not triggered in target server", 'error')
|
module.log("Bug not triggered in target server", 'error')
|
||||||
module.log("it may not be vulnerable or have the attachment plugin activated", 'error')
|
module.log("it may not be vulnerable or have the attachment plugin activated", 'error')
|
||||||
|
|
Loading…
Reference in New Issue