Add standalone runner for external modules
parent
64c38ec6b8
commit
0dd89bf428
|
@ -5,6 +5,7 @@ class Msf::Modules::External
|
|||
|
||||
autoload :Bridge, 'msf/core/modules/external/bridge'
|
||||
autoload :Message, 'msf/core/modules/external/message'
|
||||
autoload :CLI, 'msf/core/modules/external/cli'
|
||||
|
||||
attr_reader :path
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: binary -*-
|
||||
# CLI for interaction with modules outside of msfconsole
|
||||
|
||||
require 'optparse'
|
||||
|
||||
module Msf::Modules::External::CLI
|
||||
def self.parse_options(mod)
|
||||
action = 'run'
|
||||
actions = ['run'] + mod.meta['capabilities']
|
||||
args = mod.meta['options'].reduce({}) do |defaults, (n, opt)|
|
||||
if opt['default'].nil?
|
||||
if opt['required']
|
||||
defaults
|
||||
else
|
||||
defaults[n] = nil
|
||||
defaults
|
||||
end
|
||||
else
|
||||
defaults[n] = opt['default']
|
||||
defaults
|
||||
end
|
||||
end
|
||||
|
||||
op = OptionParser.new do |opts|
|
||||
if $0 != mod.path
|
||||
opts.banner = "Usage: #{$0} #{mod.path} [OPTIONS] [ACTION]"
|
||||
end
|
||||
opts.separator ""
|
||||
|
||||
opts.separator mod.meta['description']
|
||||
opts.separator ""
|
||||
|
||||
opts.separator "Postitional arguments:"
|
||||
opts.separator " ACTION: The action to take (#{actions.inspect})"
|
||||
opts.separator ""
|
||||
|
||||
opts.separator "Required arguments:"
|
||||
make_options opts, args, mod.meta['options'].select {|n, o| o['required'] && o['default'].nil?}
|
||||
opts.separator ""
|
||||
|
||||
opts.separator "Optional arguments:"
|
||||
make_options opts, args, mod.meta['options'].select {|n, o| !o['required'] || !o['default'].nil?}
|
||||
|
||||
opts.on '-h', '--help', 'Prints this help' do
|
||||
$stderr.puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
extra = op.permute *ARGV
|
||||
# If no extra args are given we use the default action
|
||||
if extra.length == 1
|
||||
action = extra.shift
|
||||
elsif extra.length > 1
|
||||
action = extra.shift
|
||||
$stderr.puts "WARNING: unrecognized arguments #{extra.inspect}"
|
||||
end
|
||||
rescue OptionParser::InvalidArgument => e
|
||||
$stderr.puts e.message
|
||||
abort
|
||||
rescue OptionParser::MissingArgument => e
|
||||
$stderr.puts e.message
|
||||
abort
|
||||
end
|
||||
|
||||
required = mod.meta['options'].select {|_, o| o['required']}.map {|n, _| n}.sort
|
||||
|
||||
# Were we run with any non-module options if we need them?
|
||||
if args.empty? && !required.empty?
|
||||
$stderr.puts op
|
||||
exit
|
||||
# Did someone forget to add some options we need?
|
||||
elsif (args.keys & required).sort != required
|
||||
missing = required - (args.keys & required)
|
||||
abort "Missing required option(s): #{missing.map {|o| '--' + o}.join ', '}"
|
||||
end
|
||||
|
||||
unless action == 'run' || mod.meta['capabilities'].include?(action)
|
||||
$stderr.puts "Invalid ACTION choice #{action.inspect} (choose from #{actions.inspect})"
|
||||
abort
|
||||
end
|
||||
|
||||
action =
|
||||
case action
|
||||
when 'run'; :run
|
||||
when 'soft_check'; :soft_check
|
||||
when 'hard_check'; :hard_check
|
||||
end
|
||||
[args, action]
|
||||
end
|
||||
|
||||
def self.choose_type(t)
|
||||
if t == 'int' or t == 'port'
|
||||
Integer
|
||||
elsif t == 'float'
|
||||
Float
|
||||
elsif t.match /range$/
|
||||
Array
|
||||
else # XXX TODO add validation for addresses and other MSF option types
|
||||
String
|
||||
end
|
||||
end
|
||||
|
||||
def self.make_options(parser, out, args)
|
||||
args.each do |n, opt|
|
||||
name = n.gsub '_', '-'
|
||||
desc = if opt['default']
|
||||
"#{opt['description']}, (default: #{opt['default']})"
|
||||
else
|
||||
opt['description']
|
||||
end
|
||||
parser.on "--#{name} #{n.upcase}", choose_type(opt['type']), desc do |arg|
|
||||
out[n] = arg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Msf
|
||||
module Modules
|
||||
end
|
||||
end
|
||||
|
||||
msfbase = __FILE__
|
||||
while File.symlink?(msfbase)
|
||||
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
|
||||
end
|
||||
|
||||
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib')))
|
||||
require 'msf/core/modules/external'
|
||||
|
||||
require 'json'
|
||||
|
||||
module_path = ARGV.shift
|
||||
|
||||
# Usage when we don't have a module name
|
||||
def usage(mod='MODULE_FILE', name='Run a module outside of Metasploit Framework')
|
||||
$stderr.puts "Usage: solo.rb #{mod} [OPTIONS] [ACTION]"
|
||||
$stderr.puts name
|
||||
end
|
||||
|
||||
def log_output(m)
|
||||
message = m.params['message']
|
||||
|
||||
sigil = case m.params['level']
|
||||
when 'error', 'warning'
|
||||
'!'
|
||||
when 'good'
|
||||
'+'
|
||||
else
|
||||
'*'
|
||||
end
|
||||
|
||||
$stderr.puts "[#{sigil}] #{message}"
|
||||
end
|
||||
|
||||
def process_report(m)
|
||||
puts "[+] Found #{m.params['type']}: #{JSON.generate m.params['data']}"
|
||||
end
|
||||
|
||||
if !module_path || module_path[0] == '-'
|
||||
usage
|
||||
else
|
||||
mod = Msf::Modules::External.new module_path
|
||||
args, method = Msf::Modules::External::CLI.parse_options mod
|
||||
|
||||
success = mod.exec(method: method, args: args) do |m|
|
||||
begin
|
||||
case m.method
|
||||
when :message
|
||||
log_output(m)
|
||||
when :report
|
||||
process_report(m)
|
||||
when :reply
|
||||
puts m.params['return']
|
||||
end
|
||||
rescue Interrupt => e
|
||||
abort 'Exiting...'
|
||||
rescue Exception => e
|
||||
abort "Encountered an error: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
abort 'Module exited abnormally' if !success
|
||||
end
|
Loading…
Reference in New Issue