Files
metasploit-gs/lib/msf/core/modules/external/bridge.rb
T
2018-01-24 17:20:03 -06:00

197 lines
5.1 KiB
Ruby

# -*- coding: binary -*-
require 'msf/core/modules/external'
require 'msf/core/modules/external/message'
require 'open3'
require 'json'
class Msf::Modules::External::Bridge
attr_reader :path, :running
def self.applies?(module_name)
File::executable? module_name
end
def meta
@meta ||= describe
end
def run(datastore)
unless self.running
m = Msf::Modules::External::Message.new(:run)
m.params = datastore.dup
send(m)
self.running = true
end
end
def get_status
if self.running || !self.messages.empty?
m = receive_notification
if m.nil?
close_ios
self.messages.close
self.running = false
end
return m
end
end
def initialize(module_path)
self.env = {}
self.running = false
self.path = module_path
self.cmd = [self.path, self.path]
self.messages = Queue.new
self.buf = ''
end
protected
attr_writer :path, :running
attr_accessor :cmd, :env, :ios, :buf, :messages, :wait_thread
def describe
resp = send_receive(Msf::Modules::External::Message.new(:describe))
close_ios
resp.params
end
# XXX TODO non-blocking writes, check write lengths
def send_receive(message)
send(message)
recv(message.id)
end
def send(message)
input, output, err, status = ::Open3.popen3(self.env, self.cmd)
self.ios = [input, output, err]
self.wait_thread = status
# We would call Rex::Threadsafe directly, but that would require rex for standalone use
case select(nil, [input], nil, 0.1)
when nil
raise "Cannot run module #{self.path}"
when [[], [input], []]
m = message.to_json
write_message(input, m)
else
raise "Error running module #{self.path}"
end
end
def receive_notification
if self.messages.empty?
recv
else
self.messages.pop
end
end
def write_message(fd, json)
fd.write(json)
end
def recv(filter_id=nil, timeout=600)
_, out, err = self.ios
message = ''
# 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
# 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
# whole lot of `select` syscalls and keep a buffer ourselves :(
begin
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
res = select([out, err], nil, nil, timeout)
if res == nil
# This is what we would have gotten without Rex and what `readpartial` can also raise
raise EOFError.new
else
fds = res[0]
# Preferentially drain and log stderr, EOF counts as activity, but
# stdout might have some buffered data left, so carry on
if fds.include?(err) && !err.eof?
errbuf = err.readpartial(4096)
elog "Unexpected output running #{self.path}:\n#{errbuf}"
end
if fds.include? out
self.buf << out.readpartial(4096)
end
end
end
m = Msf::Modules::External::Message.from_module(JSON.parse(message))
if filter_id && m.id != filter_id
# We are filtering for a response to a particular message, but we got
# something else, store the message and try again
self.messages.push m
read_json(filter_id, timeout)
else
# Either we weren't filtering, or we got what we were looking for
m
end
rescue JSON::ParserError
# Probably an incomplete response, but no way to really tell. Keep trying
# until EOF
retry
rescue EOFError => e
nil
end
end
def close_ios
self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
end
end
class Msf::Modules::External::PyBridge < Msf::Modules::External::Bridge
def self.applies?(module_name)
module_name.match? /\.py$/
end
def initialize(module_path)
super
pythonpath = ENV['PYTHONPATH'] || ''
self.env = self.env.merge({ 'PYTHONPATH' => pythonpath + File::PATH_SEPARATOR + File.expand_path('../python', __FILE__) })
end
end
class Msf::Modules::External::Bridge
LOADERS = [
Msf::Modules::External::PyBridge,
Msf::Modules::External::Bridge
]
def self.open(module_path)
LOADERS.each do |klass|
return klass.new module_path if klass.applies? module_path
end
nil
end
end