263 lines
7.4 KiB
Ruby
263 lines
7.4 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'open3'
|
|
require 'json'
|
|
|
|
module Msf::Modules
|
|
class External
|
|
class Bridge
|
|
|
|
attr_reader :path, :running, :messages, :exit_status
|
|
|
|
def self.applies?(module_name)
|
|
File::executable? module_name
|
|
end
|
|
|
|
def exec(req)
|
|
unless self.running
|
|
self.running = true
|
|
send(req)
|
|
self.read_thread = threadme do
|
|
begin
|
|
while self.running && m = next_message
|
|
self.messages.push m
|
|
end
|
|
ensure
|
|
cleanup
|
|
end
|
|
end
|
|
|
|
self
|
|
end
|
|
end
|
|
|
|
def close
|
|
self.running = false
|
|
self.read_thread.join
|
|
|
|
self
|
|
end
|
|
|
|
def success?
|
|
self.exit_status && self.exit_status.success?
|
|
end
|
|
|
|
def initialize(module_path, framework: nil)
|
|
self.env = {}
|
|
self.running = false
|
|
self.path = module_path
|
|
self.cmd = [[self.path, self.path]]
|
|
self.messages = Queue.new
|
|
self.buf = ''
|
|
self.framework = framework
|
|
end
|
|
|
|
protected
|
|
|
|
attr_writer :path, :running, :messages, :exit_status
|
|
attr_accessor :cmd, :env, :ios, :buf, :read_thread, :wait_thread, :framework
|
|
|
|
# XXX TODO non-blocking writes, check write lengths
|
|
|
|
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
|
|
rescue => e
|
|
raise handle_exception(e)
|
|
end
|
|
|
|
def handle_exception(e)
|
|
e
|
|
end
|
|
|
|
def write_message(fd, json)
|
|
fd.write(json)
|
|
end
|
|
|
|
def next_message(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]
|
|
message << parts[0]
|
|
self.buf = ''
|
|
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)
|
|
if self.framework
|
|
elog "Unexpected output running #{self.path}:\n#{errbuf}"
|
|
else
|
|
$stderr.puts errbuf
|
|
end
|
|
end
|
|
if fds.include? out
|
|
self.buf << out.readpartial(4096)
|
|
end
|
|
end
|
|
end
|
|
|
|
Message.from_module(JSON.parse(message))
|
|
rescue JSON::ParserError
|
|
# Probably an incomplete response, but no way to really tell. Keep trying
|
|
# until EOF
|
|
retry
|
|
rescue EOFError => e
|
|
self.running = false
|
|
end
|
|
end
|
|
|
|
def harvest_process
|
|
if self.wait_thread.join(10)
|
|
self.exit_status = self.wait_thread.value
|
|
elsif Process.kill('TERM', self.wait_thread.pid) && self.wait_thread.join(10)
|
|
self.exit_status = self.wait_thread.value
|
|
else
|
|
Process.kill('KILL', self.wait_thread.pid)
|
|
self.exit_status = self.wait_thread.value
|
|
end
|
|
end
|
|
|
|
def cleanup
|
|
self.running = false
|
|
self.messages.close
|
|
harvest_process
|
|
self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
|
|
end
|
|
|
|
def threadme(&block)
|
|
if self.framework
|
|
# Leak as few connections as possible
|
|
self.framework.threads.spawn("External Module #{self.path}", false, &block)
|
|
else
|
|
::Thread.new &block
|
|
end
|
|
end
|
|
end
|
|
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, framework: nil)
|
|
super
|
|
pythonpath = ENV['PYTHONPATH'] || ''
|
|
self.env = self.env.merge({ 'PYTHONPATH' => File.expand_path('../python', __FILE__) + File::PATH_SEPARATOR + pythonpath})
|
|
end
|
|
|
|
def handle_exception(error)
|
|
case error
|
|
when Errno::ENOENT
|
|
LoadError.new('Failed to execute external Python module. Please ensure you have Python3 installed on your environment.')
|
|
else
|
|
super
|
|
end
|
|
end
|
|
end
|
|
|
|
class Msf::Modules::External::RbBridge < Msf::Modules::External::Bridge
|
|
def self.applies?(module_name)
|
|
module_name.match? /\.rb$/
|
|
end
|
|
|
|
def initialize(module_path, framework: nil)
|
|
super
|
|
ruby_path = File.expand_path('../ruby', __FILE__)
|
|
self.cmd = [[Gem.ruby, 'ruby'], "-I#{ruby_path}", self.path]
|
|
end
|
|
end
|
|
|
|
class Msf::Modules::External::GoBridge < Msf::Modules::External::Bridge
|
|
def self.applies?(module_name)
|
|
module_name.match? /\.go$/
|
|
end
|
|
|
|
def initialize(module_path, framework: nil)
|
|
super
|
|
default_go_path = ENV['GOPATH'] || ''
|
|
shared_module_lib_path = File.dirname(module_path) + "/shared"
|
|
go_path = File.expand_path('../go', __FILE__)
|
|
|
|
if File.exist?(default_go_path)
|
|
go_path = go_path + File::PATH_SEPARATOR + default_go_path
|
|
end
|
|
|
|
if File.exist?(shared_module_lib_path)
|
|
go_path = go_path + File::PATH_SEPARATOR + shared_module_lib_path
|
|
end
|
|
|
|
self.env = self.env.merge(
|
|
{
|
|
'GOPATH' => go_path,
|
|
'GO111MODULE' => 'auto'
|
|
}
|
|
)
|
|
self.cmd = ['go', 'run', self.path]
|
|
end
|
|
|
|
def handle_exception(error)
|
|
case error
|
|
when Errno::ENOENT
|
|
LoadError.new('Failed to execute external Go module. Please ensure you have Go installed on your environment.')
|
|
else
|
|
super
|
|
end
|
|
end
|
|
end
|
|
|
|
class Msf::Modules::External::Bridge
|
|
|
|
LOADERS = [
|
|
Msf::Modules::External::PyBridge,
|
|
Msf::Modules::External::RbBridge,
|
|
Msf::Modules::External::GoBridge,
|
|
Msf::Modules::External::Bridge
|
|
]
|
|
|
|
def self.open(module_path, framework: nil)
|
|
LOADERS.each do |klass|
|
|
return klass.new module_path, framework: framework if klass.applies? module_path
|
|
end
|
|
|
|
nil
|
|
end
|
|
end
|