Skip to content

Commit

Permalink
Add support for long arguments to Rex Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanusz-r7 committed Dec 14, 2021
1 parent 950e976 commit 063c393
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 105 deletions.
74 changes: 37 additions & 37 deletions lib/msf/ui/console/command_dispatcher/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ class Core

# Session command options
@@sessions_opts = Rex::Parser::Arguments.new(
"-c" => [ true, "Run a command on the session given with -i, or all" ],
"-C" => [ true, "Run a Meterpreter Command on the session given with -i, or all" ],
"-h" => [ false, "Help banner" ],
"-i" => [ true, "Interact with the supplied session ID" ],
"-l" => [ false, "List all active sessions" ],
"-v" => [ false, "List all active sessions in verbose mode" ],
"-d" => [ false, "List all inactive sessions" ],
"-q" => [ false, "Quiet mode" ],
"-k" => [ true, "Terminate sessions by session ID and/or range" ],
"-K" => [ false, "Terminate all sessions" ],
"-s" => [ true, "Run a script or module on the session given with -i, or all" ],
"-u" => [ true, "Upgrade a shell to a meterpreter session on many platforms" ],
"-t" => [ true, "Set a response timeout (default: 15)" ],
"-S" => [ true, "Row search filter." ],
"-x" => [ false, "Show extended information in the session table" ],
"-n" => [ true, "Name or rename a session by ID" ])
["-c", "--command"] => [ true, "Run a command on the session given with -i, or all" ],
["-C", "--meterpreter-command"] => [ true, "Run a Meterpreter Command on the session given with -i, or all" ],
["-h", "--help"] => [ false, "Help banner" ],
["-i", "--interact"] => [ true, "Interact with the supplied session ID" ],
["-l", "--list-active"] => [ false, "List all active sessions" ],
["-v", "--list-verbose"] => [ false, "List all active sessions in verbose mode" ],
["-d", "--list-inactive"] => [ false, "List all inactive sessions" ],
["-q", "--quiet"] => [ false, "Quiet mode" ],
["-k", "--kill"] => [ true, "Terminate sessions by session ID and/or range" ],
["-K", "--kill-all"] => [ false, "Terminate all sessions" ],
["-s", "--script"] => [ true, "Run a script or module on the session given with -i, or all" ],
["-u", "--upgrade"] => [ true, "Upgrade a shell to a meterpreter session on many platforms" ],
["-t", "--timeout"] => [ true, "Set a response timeout (default: 15)" ],
["-S", "--search"] => [ true, "Row search filter." ],
["-x", "--list-extended"] => [ false, "Show extended information in the session table" ],
["-n", "--name"] => [ true, "Name or rename a session by ID" ])


@@threads_opts = Rex::Parser::Arguments.new(
Expand Down Expand Up @@ -730,7 +730,7 @@ def cmd_history_help

def cmd_history_tabs(str, words)
return [] if words.length > 1
@@history_opts.fmt.keys
@@history_opts.option_keys
end

def cmd_sleep_help
Expand Down Expand Up @@ -867,10 +867,10 @@ def cmd_threads(*args)

def cmd_threads_tabs(str, words)
if words.length == 1
return @@threads_opts.fmt.keys
return @@threads_opts.option_keys
end

if words.length == 2 and (@@threads_opts.fmt[words[1]] || [false])[0]
if words.length == 2 && @@threads_opts.include?(words[1]) && @@threads_opts.arg_required?(words[1])
return framework.threads.each_index.map{ |idx| idx.to_s }
end

Expand Down Expand Up @@ -1363,54 +1363,54 @@ def cmd_sessions(*args)
# Parse the command options
@@sessions_opts.parse(args) do |opt, idx, val|
case opt
when "-q"
when "-q", "--quiet"
quiet = true
# Run a command on all sessions, or the session given with -i
when "-c"
when "-c", "--command"
method = 'cmd'
cmds << val if val
when "-C"
when "-C", "--meterpreter-command"
method = 'meterp-cmd'
cmds << val if val
# Display the list of inactive sessions
when "-d"
when "-d", "--list-inactive"
show_inactive = true
method = 'list_inactive'
when "-x"
when "-x", "--list-extended"
show_extended = true
when "-v"
when "-v", "--list-verbose"
verbose = true
# Do something with the supplied session identifier instead of
# all sessions.
when "-i"
when "-i", "--interact"
sid = val
# Display the list of active sessions
when "-l"
when "-l", "--list-active"
show_active = true
method = 'list'
when "-k"
when "-k", "--kill"
method = 'kill'
sid = val || false
when "-K"
when "-K", "--kill-all"
method = 'killall'
# Run a script or module on specified sessions
when "-s"
when "-s", "--script"
unless script
method = 'script'
script = val
end
# Upload and exec to the specific command session
when "-u"
when "-u", "--upgrade"
method = 'upexec'
sid = val || false
# Search for specific session
when "-S", "--search"
search_term = val
# Display help banner
when "-h"
when "-h", "--help"
cmd_sessions_help
return false
when "-t"
when "-t", "--timeout"
if val.to_s =~ /^\d+$/
response_timeout = val.to_i
end
Expand Down Expand Up @@ -1713,17 +1713,17 @@ def cmd_sessions(*args)

def cmd_sessions_tabs(str, words)
if words.length == 1
return @@sessions_opts.fmt.keys.select { |opt| opt.start_with?(str) }
return @@sessions_opts.option_keys.select { |opt| opt.start_with?(str) }
end

case words[-1]
when "-i", "-k", "-u"
when "-i", "--interact", "-k", "--kill", "-u", "--upgrade"
return framework.sessions.keys.map { |k| k.to_s }

when "-c"
when "-c", "--command"
# Can't really complete commands hehe

when "-s"
when "-s", "--search"
# XXX: Complete scripts

end
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/ui/console/command_dispatcher/developer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def cmd_irb(*args)
def cmd_irb_tabs(_str, words)
return [] if words.length > 1

@@irb_opts.fmt.keys
@@irb_opts.option_keys
end

def cmd_pry_help
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/ui/console/command_dispatcher/jobs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,9 @@ def add_persist_job(job_id)
# at least 1 when tab completion has reached this stage since the command itself has been completed

def cmd_jobs_tabs(_str, words)
return @@jobs_opts.fmt.keys if words.length == 1
return @@jobs_opts.option_keys if words.length == 1

if words.length == 2 && (@@jobs_opts.fmt[words[1]] || [false])[0]
if words.length == 2 && @@jobs_opts.include?(words[1]) && @@jobs_opts.arg_required?(words[1])
return framework.jobs.keys
end

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/ui/console/command_dispatcher/modules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ def cmd_search(*args)

def cmd_search_tabs(str, words)
if words.length == 1
return @@search_opts.fmt.keys
return @@search_opts.option_keys
end

[]
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/ui/console/module_action_commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def cmd_action_help(action)
# at least 1 when tab completion has reached this stage since the command itself has been completed
#
def cmd_run_tabs(str, words)
flags = @@module_opts_with_action_support.fmt.keys
flags = @@module_opts_with_action_support.option_keys
options = tab_complete_option(active_module, str, words)
flags + options
end
Expand Down
8 changes: 4 additions & 4 deletions lib/msf/ui/console/module_argument_parsing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ module ModuleArgumentParsing
'-q' => [ false, 'Run the module in quiet mode with no output' ]
)

@@module_opts_with_action_support = Rex::Parser::Arguments.new(@@module_opts.fmt.merge(
@@module_opts_with_action_support = @@module_opts.merge(
'-a' => [ true, 'The action to use. If none is specified, ACTION is used.']
))
)

@@exploit_opts = Rex::Parser::Arguments.new(@@module_opts.fmt.merge(
@@exploit_opts = @@module_opts.merge(
'-e' => [ true, 'The payload encoder to use. If none is specified, ENCODER is used.' ],
'-f' => [ false, 'Force the exploit to run regardless of the value of MinimumRank.' ],
'-n' => [ true, 'The NOP generator to use. If none is specified, NOP is used.' ],
'-p' => [ true, 'The payload to use. If none is specified, PAYLOAD is used.' ],
'-t' => [ true, 'The target index to use. If none is specified, TARGET is used.' ],
'-z' => [ false, 'Do not interact with the session after successful exploitation.' ]
))
)

def parse_check_opts(args)
help_cmd = proc do |_result|
Expand Down
83 changes: 63 additions & 20 deletions lib/rex/parser/arguments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ class Arguments
# Initializes the format list with an array of formats like:
#
# Arguments.new(
# '-b' => [ false, "some text" ]
# '-b' => [ false, "some text" ],
# ['-b'] => [ false, "some text" ],
# '--sample' => [ false, "sample long arg" ],
# '--also-a-sample' => [ false, "sample longer arg" ],
# ['-x', '--execute'] => [ true, "mixing long and short args" ]
# )
#
def initialize(fmt)
self.fmt = fmt
self.longest = fmt.keys.max_by(&:length)
normalised_fmt = fmt.map { |key, metadata| [Array(key), metadata] }.to_h
self.fmt = normalised_fmt
self.longest = normalised_fmt.keys.map { |key| key.flatten.join(', ') }.max_by(&:length)
end

#
Expand All @@ -35,6 +40,11 @@ def self.from_s(str)
# Parses the supplied arguments into a set of options.
#
def parse(args, &_block)
# e.g. '-x' or '-xyz'
short_flag = /^-[a-zA-Z]+$/
# e.g. '--verbose', '--very-verbose' or '--very-very-verbose'
long_flag = /^--([a-zA-Z]+)((-[a-zA-Z]+)*)$/

skip_next = false

args.each_with_index do |arg, idx|
Expand All @@ -43,22 +53,27 @@ def parse(args, &_block)
next
end

if arg.length > 1 && arg[0] == '-' && arg[1] != '-'
arg.split('').each do |flag|
fmt.each_pair do |fmtspec, val|
next if fmtspec != "-#{flag}"

param = nil

if val[0]
param = args[idx + 1]
skip_next = true
end
param = nil
if arg =~ short_flag
arg.split('')[1..-1].each do |letter|
next unless include?("-#{letter}")

yield fmtspec, idx, param
if arg_required?("-#{letter}")
param = args[idx + 1]
skip_next = true
end

yield "-#{letter}", idx, param
end
elsif arg =~ long_flag && include?(arg)
if arg_required?(arg)
param = args[idx + 1]
skip_next = true
end

yield arg, idx, param
else
# else treat the passed in flag as argument
yield nil, idx, arg
end
end
Expand All @@ -70,26 +85,54 @@ def parse(args, &_block)
def usage
txt = ["\nOPTIONS:\n"]

fmt.sort.each do |entry|
fmtspec, val = entry
fmt.sort_by { |key, _metadata| key.to_s.downcase }.each do |key, val|
opt = val[0] ? " <opt> " : " "
txt << " #{fmtspec.ljust(longest.length)}#{opt}#{val[1]}"

# Get all arguments for a command
output = key.join(', ')
output += opt

# Left align the fmt options and <opt> string
txt << " #{(output).ljust((longest + opt).length)}#{val[1]}"
end

txt << ""
txt.join("\n")
end

def include?(search)
fmt.include?(search)
fmt.keys.flatten.include?(search)
end

def arg_required?(opt)
fmt[opt][0] if fmt[opt]
value = select_value_from_fmt_option(opt)
return false if value.nil?

value.first
end

def option_keys
fmt.keys.flatten
end

# Return new Parser object featuring options from the base object and including the options hash that was passed in
def merge(to_merge)
return fmt unless to_merge.is_a?(Hash)

Rex::Parser::Arguments.new(fmt.clone.merge(to_merge))
end

private

attr_accessor :fmt # :nodoc:
attr_accessor :longest # :nodoc:

def select_value_from_fmt_option(option)
fmt_option = fmt.find { |key, value| value if key.include?(option) }
return if fmt_option.nil?

fmt_option[1]
end
end
end
end
Loading

0 comments on commit 063c393

Please sign in to comment.