Skip to content

Commit

Permalink
Add core session commands and test suite
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelch-r7 committed Nov 6, 2023
1 parent 3f3531d commit c7e0e09
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 214 deletions.
9 changes: 9 additions & 0 deletions lib/msf/base/sessions/meterpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ def initialize(rstream, opts={})
self.console = Rex::Post::Meterpreter::Ui::Console.new(self)
end

def exit
begin
self.core.shutdown
rescue StandardError
nil
end
self.shutdown_passive_dispatcher
self.console.stop
end
#
# Returns the session type as being 'meterpreter'.
#
Expand Down
235 changes: 235 additions & 0 deletions lib/msf/ui/console/command_dispatcher/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# frozen_string_literal: true

module Msf
module Ui
module Console
module CommandDispatcher
module Session
include Rex::Ui::Text::DispatcherShell::CommandDispatcher

@@irb_opts = Rex::Parser::Arguments.new(
%w[-h --help] => [false, 'Help menu.' ],
'-e' => [true, 'Expression to evaluate.']
)
def commands
{
'?' => 'Help menu',
'background' => 'Backgrounds the current session',
'bg' => 'Alias for background',
'exit' => 'Terminate the session',
'help' => 'Help menu',
'irb' => 'Open an interactive Ruby shell on the current session',
'pry' => 'Open the Pry debugger on the current session',
'quit' => 'Terminate the session',
'resource' => 'Run the commands stored in a file',
'uuid' => 'Get the UUID for the current session',
'sessions' => 'Quickly switch to another session'
}
end

def cmd_background_help
print_line('Usage: background')
print_line
print_line('Stop interacting with this session and return to the parent prompt')
print_line
end

def cmd_background(*args)
if args.include?('-h') || args.include?('--help')
cmd_background_help
return
end
print_status("Backgrounding session #{client.name}...")
client.interacting = false
end

alias cmd_bg cmd_background
alias cmd_bg_help cmd_background_help

#
# Terminates the session.
#
def cmd_exit(*args)
print_status("Shutting down session: #{client.sid}")
client.exit
end

alias cmd_quit cmd_exit

def cmd_irb_help
print_line('Usage: irb')
print_line
print_line('Open an interactive Ruby shell on the current session.')
print @@irb_opts.usage
end

def cmd_irb_tabs(str, words)
return [] if words.length > 1

@@irb_opts.option_keys
end

#
# Open an interactive Ruby shell on the current session
#
def cmd_irb(*args)
expressions = []

# Parse the command options
@@irb_opts.parse(args) do |opt, _idx, val|
case opt
when '-e'
expressions << val
when '-h', '--help'
return cmd_irb_help
end
end

session = client
framework = client.framework

if expressions.empty?
print_status('Starting IRB shell...')
print_status("You are in the \"client\" (session) object\n")
framework.history_manager.with_context(name: :irb) do
Rex::Ui::Text::IrbShell.new(client).run
end
else
# XXX: No vprint_status here
if framework.datastore['VERBOSE'].to_s == 'true'
print_status("You are executing expressions in #{binding.receiver}")
end

expressions.each { |expression| eval(expression, binding) }
end
end

def cmd_pry_help
print_line 'Usage: pry'
print_line
print_line 'Open the Pry debugger on the current session.'
print_line
end

#
# Open the Pry debugger on the current session
#
def cmd_pry(*args)
if args.include?('-h') || args.include?('--help')
cmd_pry_help
return
end

begin
require 'pry'
rescue LoadError
print_error('Failed to load Pry, try "gem install pry"')
return
end

print_status('Starting Pry shell...')
print_status("You are in the \"client\" (session) object\n")

Pry.config.history_load = false
client.framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
client.pry
end
end

def cmd_sessions_help
print_line('Usage: sessions <id>')
print_line
print_line('Interact with a different session Id.')
print_line('This works the same as calling this from the MSF shell: sessions -i <session id>')
print_line
end

def cmd_sessions(*args)
if args.empty? || args[0].to_i == 0
cmd_sessions_help
elsif args[0].to_s == client.name.to_s
print_status("Session #{client.name} is already interactive.")
else
print_status("Backgrounding session #{client.name}...")
# store the next session id so that it can be referenced as soon
# as this session is no longer interacting
client.next_session = args[0]
client.interacting = false
end
end

def cmd_resource_help
print_line 'Usage: resource path1 [path2 ...]'
print_line
print_line 'Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)'
print_line 'Resource files may also contain ERB or Ruby code between <ruby></ruby> tags.'
print_line
end

def cmd_resource(*args)
if args.empty? || args.include?('-h') || args.include?('--help')
cmd_resource_help
return false
end

args.each do |res|
good_res = nil
if res == '-'
good_res = res
elsif ::File.exist?(res)
good_res = res
elsif [
::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter'
].each do |dir|
res_path = dir + ::File::SEPARATOR + res
if ::File.exist?(res_path)
good_res = res_path
break
end
end
# let's check to see if it's in the scripts/resource dir (like when tab completed)
end
if good_res
client.console.load_resource(good_res)
else
print_error("#{res} is not a valid resource file")
next
end
end
end

def cmd_resource_tabs(str, words)
tabs = []
# return tabs if words.length > 1
if (str && str =~ (/^#{Regexp.escape(::File::SEPARATOR)}/))
# then you are probably specifying a full path so let's just use normal file completion
return tab_complete_filenames(str, words)
elsif (!(words[1]) || !words[1].match(%r{^/}))
# then let's start tab completion in the scripts/resource directories
begin
[
::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
'.'
].each do |dir|
next if !::File.exist? dir

tabs += ::Dir.new(dir).find_all do |e|
path = dir + ::File::SEPARATOR + e
::File.file?(path) and ::File.readable?(path)
end
end
rescue Exception
end
else
tabs += tab_complete_filenames(str, words)
end

return tabs
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/rex/post/meterpreter/ui/console/command_dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Ui
###
module Console::CommandDispatcher

include Rex::Ui::Text::DispatcherShell::CommandDispatcher
include Msf::Ui::Console::CommandDispatcher::Session

#
# The hash of file names to class names after a module has already been
Expand Down
Loading

0 comments on commit c7e0e09

Please sign in to comment.