diff --git a/documentation/TODO b/documentation/TODO index 4d01751a6e..6b40657f5d 100644 --- a/documentation/TODO +++ b/documentation/TODO @@ -48,3 +48,5 @@ service.shutdown - findsock handler - meterpreter - more ui wrapping + - fix route addition/removal in stdapi server dll (mib structure issue) + - fix interactive stream pool channels diff --git a/lib/msf/core/session/basic.rb b/lib/msf/core/session/basic.rb index a19dcd12be..41dcda3879 100644 --- a/lib/msf/core/session/basic.rb +++ b/lib/msf/core/session/basic.rb @@ -36,25 +36,7 @@ protected # overriden by derived classes if they wish to do this another way. # def _interact - while self.interacting - # Select input and rstream - sd = Rex::ThreadSafe.select([ user_input.fd, rstream.fd ]) - - # Cycle through the items that have data - # From the rstream? Write to user_output. - sd[0].each { |s| - if (s == rstream.fd) - data = rstream.get - - user_output.print(data) - # From user_input? Write to rstream. - elsif (s == user_input.fd) - data = user_input.gets - - rstream.put(data) - end - } if (sd) - end + interact_stream(rstream) end end diff --git a/lib/msf/core/session/interactive.rb b/lib/msf/core/session/interactive.rb index 204dd64f21..14602f46d6 100644 --- a/lib/msf/core/session/interactive.rb +++ b/lib/msf/core/session/interactive.rb @@ -16,7 +16,7 @@ module Interactive # Interactive sessions by default may interact with the local user input # and output. # - include Rex::Ui::Subscriber + include Rex::Ui::Interactive # # Initialize's the session @@ -55,75 +55,43 @@ module Interactive rstream = nil end - # - # Starts interacting with the session at the most raw level, simply - # forwarding input from user_input to rstream and forwarding input from - # rstream to user_output. - # - def interact - self.interacting = true - - eof = false - - # Handle suspend notifications - handle_suspend - - callcc { |ctx| - # As long as we're interacting... - while (self.interacting == true) - begin - _interact - # If we get an interrupt exception, ask the user if they want to - # abort the interaction. If they do, then we return out of - # the interact function and call it a day. - rescue Interrupt - if (user_want_abort? == true) - eof = true - ctx.call - end - # If we reach EOF or the connection is reset... - rescue EOFError, Errno::ECONNRESET - dlog("Session #{name} got EOF, closing.", 'core', LEV_1) - eof = true - ctx.call - end - end - } - - # Restore the suspend handler - restore_suspend - - # If we hit end-of-file, then that means we should finish off this - # session and call it a day. - framework.sessions.deregister(self) if (eof == true) - - # Return whether or not EOF was reached - return eof - end - # # The remote stream handle. Must inherit from Rex::IO::Stream. # attr_accessor :rstream - # - # Whether or not the session is currently being interacted with - # - attr_reader :interacting protected - attr_writer :interacting - # - # The original suspend proc. - # - attr_accessor :orig_suspend - # # Stub method that is meant to handler interaction # def _interact end + # + # Check to see if the user wants to abort + # + def _interrupt + user_want_abort? + end + + # + # Check to see if we should suspnd + # + def _suspend + # Ask the user if they would like to background the session + if (prompt_yesno("Background session #{name}?") == true) + self.interacting = false + end + end + + # + # If the session reaches EOF, deregister it. + # + def _interact_complete + framework.sessions.deregister(self) + end + # # Checks to see if the user wants to abort # @@ -131,49 +99,6 @@ protected prompt_yesno("Abort session #{name}?") end - # - # Installs a signal handler to monitor suspend signal notifications. - # - def handle_suspend - if (orig_suspend == nil) - self.orig_suspend = Signal.trap("TSTP") { - # Ask the user if they would like to background the session - if (prompt_yesno("Background session #{name}?") == true) - self.interacting = false - end - } - end - end - - # - # Restores the previously installed signal handler for suspend - # notifications. - # - def restore_suspend - if (orig_suspend) - Signal.trap("TSTP", orig_suspend) - - self.orig_suspend = nil - end - end - - # - # Prompt the user for input if possible. - # - def prompt(query) - if (user_output and user_input) - user_output.print("\n" + query) - user_input.gets - end - end - - # - # Check the return value of a yes/no prompt - # - def prompt_yesno(query) - (prompt(query + " [y/N] ") =~ /^y/i) ? true : false - end - end end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index f1f1a7051b..5ed35114f9 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -96,37 +96,7 @@ class Core # Displays the command help banner # def cmd_help(*args) - driver.dispatcher_stack.reverse.each { |dispatcher| - begin - commands = dispatcher.commands - rescue - commands = nil - next - end - - # Display the commands - tbl = Table.new( - Table::Style::Default, - 'Header' => "#{dispatcher.name} Commands", - 'Columns' => - [ - 'Command', - 'Description' - ], - 'ColProps' => - { - 'Command' => - { - 'MaxWidth' => 12 - } - }) - - dispatcher.commands.sort.each { |c| - tbl << c - } - - print(tbl.to_s) - } + print(driver.help_to_s) end # diff --git a/lib/rex/exceptions.rb b/lib/rex/exceptions.rb index c326d1fbe5..90c5e80bea 100644 --- a/lib/rex/exceptions.rb +++ b/lib/rex/exceptions.rb @@ -11,9 +11,6 @@ module Rex # ### module Exception - def to_s - "An unknown exception occurred." - end end class TimeoutError < Interrupt @@ -35,9 +32,9 @@ end class RuntimeError < ::RuntimeError include Exception - def to_s - "A runtime error occurred." - end +# def to_s +# "A runtime error occurred." +# end end class ArgumentError < ::ArgumentError diff --git a/lib/rex/io/stream.rb b/lib/rex/io/stream.rb index 8bf8a66e6f..1d4c8b95cb 100644 --- a/lib/rex/io/stream.rb +++ b/lib/rex/io/stream.rb @@ -33,24 +33,30 @@ module Stream # Writes data to the stream. # def write(buf, opts = {}) + fd.syswrite(buf) end # # Reads data from the stream. # def read(length = nil, opts = {}) + length = 16384 unless length + + fd.sysread(length) end # # Shuts down the stream for reading, writing, or both. # def shutdown(how = SW_BOTH) + fd.shutdown(how) end # # Closes the stream and allows for resource cleanup # def close + fd.close end # @@ -58,6 +64,7 @@ module Stream # true if data is available for reading, otherwise false is returned. # def has_read_data?(timeout = nil) + Rex::ThreadSafe.select([ fd ], nil, nil, timeout) end # diff --git a/lib/rex/io/stream_abstraction.rb b/lib/rex/io/stream_abstraction.rb index 9618ee27c6..eb014adb4b 100644 --- a/lib/rex/io/stream_abstraction.rb +++ b/lib/rex/io/stream_abstraction.rb @@ -16,7 +16,9 @@ module IO ### module StreamAbstraction + # # Creates a streaming socket pair + # def initialize_abstraction self.lsock, self.rsock = ::Socket.pair(::Socket::AF_UNIX, ::Socket::SOCK_STREAM, 0) diff --git a/lib/rex/post/meterpreter/channel.rb b/lib/rex/post/meterpreter/channel.rb index ac6b86be01..fe167a1963 100644 --- a/lib/rex/post/meterpreter/channel.rb +++ b/lib/rex/post/meterpreter/channel.rb @@ -49,7 +49,6 @@ class Channel # Valid channel context? if (channel == nil) - puts "nil wtf" return false end @@ -243,6 +242,26 @@ class Channel return true end + # + # Enables or disables interactive mode + # + def interactive(tf = true, addends = nil) + if (self.cid == nil) + raise IOError, "Channel has been closed.", caller + end + + request = Packet.create_request('core_channel_interact') + + # Populate the request + request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid) + request.add_tlv(TLV_TYPE_BOOL, tf) + request.add_tlvs(addends) + + self.client.send_request(request) + + return true + end + ## # # Direct I/O diff --git a/lib/rex/post/meterpreter/channels/pools/stream_pool.rb b/lib/rex/post/meterpreter/channels/pools/stream_pool.rb index b111612eed..8a58f61f68 100644 --- a/lib/rex/post/meterpreter/channels/pools/stream_pool.rb +++ b/lib/rex/post/meterpreter/channels/pools/stream_pool.rb @@ -24,6 +24,8 @@ module Pools ### class StreamPool < Rex::Post::Meterpreter::Channels::Pool + include Rex::IO::StreamAbstraction + ## # # Constructor @@ -33,6 +35,8 @@ class StreamPool < Rex::Post::Meterpreter::Channels::Pool # Initializes the file channel instance def initialize(client, cid, type, flags) super(client, cid, type, flags) + + initialize_abstraction end # @@ -51,6 +55,19 @@ class StreamPool < Rex::Post::Meterpreter::Channels::Pool return false end + def dio_write_handler(packet, data) + rsock.write(data) + + return true; + end + + def dio_close_handler(packet) + rsock.close + + return super(packet) + end + + end end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb index adc5418295..61704ec61d 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb @@ -63,6 +63,8 @@ class Config return ifaces end + alias interfaces get_interfaces + ## # # Routing @@ -91,6 +93,8 @@ class Config return routes end + + alias routes get_routes # Adds a route to the target machine def add_route(subnet, netmask, gateway) diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index a59988def4..519b95a6d1 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -6,6 +6,24 @@ module Rex module Post module Meterpreter +### +# +# RequestError +# ------------ +# +# Exception thrown when a request fails. +# +### +class RequestError < ArgumentError + def initialize(method, result) + @method = method + @result = result + end + def to_s + "#{@method}: Operation failed: #{@result}" + end +end + ### # # PacketDispatcher @@ -44,9 +62,13 @@ module PacketDispatcher response = send_packet_wait_response(packet, t) if (response == nil) - raise RuntimeError, packet.method + ": No response was received.", caller + raise TimeoutError elsif (response.result != 0) - raise RuntimeError, packet.method + ": Operation failed: #{response.result}", caller + e = RequestError.new(packet.method, response.result) + + e.set_backtrace(caller) + + raise e end return response diff --git a/lib/rex/post/meterpreter/ui/console.rb b/lib/rex/post/meterpreter/ui/console.rb index 1494247e34..494c29ff95 100644 --- a/lib/rex/post/meterpreter/ui/console.rb +++ b/lib/rex/post/meterpreter/ui/console.rb @@ -19,6 +19,7 @@ class Console include Rex::Ui::Text::DispatcherShell # Dispatchers + require 'rex/post/meterpreter/ui/console/interactive_channel' require 'rex/post/meterpreter/ui/console/command_dispatcher' require 'rex/post/meterpreter/ui/console/command_dispatcher/core' @@ -55,6 +56,31 @@ class Console } end + # + # Interacts with the supplied channel + # + def interact_with_channel(channel) + channel.extend(InteractiveChannel) unless (channel.kind_of?(InteractiveChannel) == true) + + channel.init_ui(input, output) + channel.interact + channel.reset_ui + end + + # + # Runs the specified command wrapper in something to catch meterpreter + # exceptions. + # + def run_command(dispatcher, method, arguments) + begin + super + rescue TimeoutError + output.print_line("Operation timed out.") + rescue RequestError => info + output.print_line(info.to_s) + end + end + attr_reader :client protected diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 55ce5ebec8..c04b5315a2 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -60,6 +60,7 @@ class Console::CommandDispatcher::Core # Displays the help menu # def cmd_help(*args) + print(shell.help_to_s) end # @@ -90,11 +91,11 @@ class Console::CommandDispatcher::Core md = m.downcase if (extensions.include?(md)) - print_error("The '#{m}' extension has already been loaded.") + print_error("The '#{md}' extension has already been loaded.") next end - print("Loading extension #{m}...") + print("Loading extension #{md}...") begin # Use the remote side, then load the client-side diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb index 4badb785e7..becc8ffd52 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb @@ -15,16 +15,27 @@ module Ui ### class Console::CommandDispatcher::Stdapi + require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs' + require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net' + require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys' + Klass = Console::CommandDispatcher::Stdapi - include Console::CommandDispatcher + Dispatchers = + [ + Klass::Fs, + Klass::Net, + Klass::Sys, + ] - require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs' + include Console::CommandDispatcher def initialize(shell) super - shell.enstack_dispatcher(Klass::Fs) + Dispatchers.each { |d| + shell.enstack_dispatcher(d) + } end # diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb index df7c43f823..73b4b00246 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb @@ -7,10 +7,10 @@ module Ui ### # -# Stdapi -# ------ +# Fs +# -- # -# Standard API extension. +# The file system portion of the standard API extension. # ### class Console::CommandDispatcher::Stdapi::Fs @@ -207,7 +207,7 @@ class Console::CommandDispatcher::Stdapi::Fs # Uploads a file or directory to the remote machine from the local # machine. # - def cmd_download(*args) + def cmd_upload(*args) if (args.length < 2) print( "Usage: upload [options] src1 src2 src3 ... destination\n\n" + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb new file mode 100644 index 0000000000..a10a0ae16f --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb @@ -0,0 +1,126 @@ +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Net +# --- +# +# The networking portion of the standard API extension. +# +### +class Console::CommandDispatcher::Stdapi::Net + + Klass = Console::CommandDispatcher::Stdapi::Net + + include Console::CommandDispatcher + + # + # Options for the generate command + # + @@route_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner." ]) + + # + # List of supported commands + # + def commands + { + "ipconfig" => "Display interfaces", + "route" => "View and modify the routing table", + } + end + + # + # Name for this dispatcher + # + def name + "Stdapi: Networking" + end + + # + # Displays interfaces on the remote machine. + # + def cmd_ipconfig(*args) + ifaces = client.net.config.interfaces + + if (ifaces.length == 0) + print_line("No interfaces were found.") + else + client.net.config.each_interface { |iface| + print("\n" + iface.pretty + "\n") + } + end + end + + # + # Displays or modifies the routing table on the remote machine. + # + def cmd_route(*args) + # Default to list + if (args.length == 0) + args.unshift("list") + end + + # Check to see if they specified -h + @@route_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print( + "Usage: route [-h] command [args]\n\n" + + "Display or modify the routing table on the remote machine.\n\n" + + "Supported commands:\n\n" + + " add [subnet] [netmask] [gateway]\n" + + " delete [subnet] [netmask] [gateway]\n" + + " list\n\n") + return true + end + } + + # Process the commands + case args.shift + when "list" + routes = client.net.config.routes + + if (routes.length == 0) + print_line("No routes were found.") + else + tbl = Rex::Ui::Text::Table.new( + 'Header' => "Network routes", + 'Indent' => 4, + 'Columns' => + [ + "Subnet", + "Netmask", + "Gateway" + ]) + + routes.each { |route| + tbl << [ route.subnet, route.netmask, route.gateway ] + } + + print("\n" + tbl.to_s + "\n") + end + when "add" + print_line("Creating route #{args[0]}/#{args[1]} -> #{args[2]}") + + client.net.config.add_route(*args) + when "delete" + print_line("Deleting route #{args[0]}/#{args[1]} -> #{args[2]}") + + client.net.config.add_route(*args) + else + print_error("Unsupported command: #{args[0]}") + end + end + +end + +end +end +end +end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb new file mode 100644 index 0000000000..764343bbef --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb @@ -0,0 +1,128 @@ +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Sys +# --- +# +# The system level portion of the standard API extension. +# +### +class Console::CommandDispatcher::Stdapi::Sys + + Klass = Console::CommandDispatcher::Stdapi::Sys + + include Console::CommandDispatcher + + @@execute_opts = Rex::Parser::Arguments.new( + "-a" => [ true, "The arguments to pass to the command." ], + "-c" => [ false, "Channelized I/O (required for interaction)." ], + "-f" => [ true, "The executable command to run." ], + "-h" => [ false, "Help menu." ], + "-H" => [ false, "Create the process hidden from view." ], + "-i" => [ false, "Interact with the process after creating it." ]) + + # + # List of supported commands + # + def commands + { + "ps" => "List running processes", + "execute" => "Execute a command", + "kill" => "Terminate a process", + "getpid" => "Get the current process identifier", + } + end + + # + # Name for this dispatcher + # + def name + "Stdapi: System" + end + + # + # Executes a command with some options. + # + def cmd_execute(*args) + if (args.length == 0) + args.unshift("-h") + end + + interact = false + channelized = nil + hidden = nil + cmd_args = nil + cmd_exec = nil + + @@execute_opts.parse(args) { |opt, idx, val| + case opt + when "-a" + cmd_args = val + when "-c" + channelized = true + when "-f" + cmd_exec = val + when "-H" + hidden = true + when "-h" + print( + "Usage: execute -f file [options]\n\n" + + "Executes a command on the remote machine.\n" + + @@execute_opts.usage) + return true + when "-i" + channelized = true + interact = true + end + } + + # Did we at least get an executable? + if (cmd_exec == nil) + print_error("You must specify an executable file with -f") + return true + end + + # Execute it + p = client.sys.process.execute(cmd_exec, cmd_args, + 'Channelized' => channelized, + 'Hidden' => hidden) + + print_line("Process #{p.pid} created.") + print_line("Channel #{p.channel.cid} created.") if (p.channel) + + if (interact and p.channel) + shell.interact_with_channel(p.channel) + end + end + + # + # Gets the process identifier that meterpreter is running in on the remote + # machine. + # + def cmd_getpid(*args) + end + + # + # Kills one or more processes. + # + def cmd_kill(*args) + end + + # + # Lists running processes + # + def cmd_ps(*args) + end + +end + +end +end +end +end diff --git a/lib/rex/post/meterpreter/ui/console/interactive_channel.rb b/lib/rex/post/meterpreter/ui/console/interactive_channel.rb new file mode 100644 index 0000000000..9a78b3a7e6 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/interactive_channel.rb @@ -0,0 +1,94 @@ +module Rex +module Post +module Meterpreter +module Ui + +### +# +# InteractiveChannel +# ------------------ +# +# Mixin that is meant to extend the base channel class from meterpreter in a +# manner that adds interactive capabilities. +# +### +module Console::InteractiveChannel + + include Rex::Ui::Interactive + + # + # Interacts with self. + # + def _interact + # If the channel has a left-side socket, then we can interact with it. + if (self.lsock) + self.interactive(true) + + begin + interact_stream(self) + rescue + end + + self.interactive(false) + else + print_error("Channel #{self.cid} does not support interaction.") + + self.interacting = false + end + end + + # + # Called when an interrupt is sent + # + def _interrupt + prompt_yesno("Terminate channel #{self.cid}?") + end + + # + # + # + def _suspend + # Ask the user if they would like to background the session + if (prompt_yesno("Background channel #{self.cid}?") == true) + self.interacting = false + end + end + + # + # Closes the channel like it aint no thang. + # + def _interact_complete + self.close + end + + # + # Reads data from local input and writes it remotely. + # + def _stream_read_local_write_remote(channel) + data = user_input.gets + + self.write(data) + end + + # + # Reads from the channel and writes locally. + # + def _stream_read_remote_write_local(channel) + data = channel.read + + user_output.print(data) + end + + # + # Returns the remote file descriptor to select on + # + def _remote_fd + self.lsock + end + +end + +end +end +end +end diff --git a/lib/rex/ui.rb b/lib/rex/ui.rb index bd759e8455..32a8e2aa19 100644 --- a/lib/rex/ui.rb +++ b/lib/rex/ui.rb @@ -17,3 +17,4 @@ require 'rex/ui/text/table' # Ui subscriber require 'rex/ui/subscriber' +require 'rex/ui/interactive' diff --git a/lib/rex/ui/interactive.rb b/lib/rex/ui/interactive.rb new file mode 100644 index 0000000000..837b737537 --- /dev/null +++ b/lib/rex/ui/interactive.rb @@ -0,0 +1,197 @@ +module Rex +module Ui + +### +# +# Interactive +# ----------- +# +# This class implements the stubs that are needed to provide an interactive +# user interface that is backed against something arbitrary. +# +### +module Interactive + + # + # Interactive sessions by default may interact with the local user input + # and output. + # + include Rex::Ui::Subscriber + + # + # Starts interacting with the session at the most raw level, simply + # forwarding input from user_input to rstream and forwarding input from + # rstream to user_output. + # + def interact + self.interacting = true + + eof = false + + # Handle suspend notifications + handle_suspend + + callcc { |ctx| + # As long as we're interacting... + while (self.interacting == true) + begin + _interact + # If we get an interrupt exception, ask the user if they want to + # abort the interaction. If they do, then we return out of + # the interact function and call it a day. + rescue Interrupt + if (_interrupt) + eof = true + ctx.call + end + # If we reach EOF or the connection is reset... + rescue EOFError, Errno::ECONNRESET + eof = true + ctx.call + end + end + } + + # Restore the suspend handler + restore_suspend + + # If we've hit eof, call the interact complete handler + _interact_complete if (eof == true) + + # Return whether or not EOF was reached + return eof + end + + # + # Whether or not the session is currently being interacted with + # + attr_reader :interacting + +protected + + attr_writer :interacting + # + # The original suspend proc. + # + attr_accessor :orig_suspend + + # + # Stub method that is meant to handler interaction + # + def _interact + end + + # + # Called when an interrupt is sent. + # + def _interrupt + true + end + + # + # Called when a suspend is sent. + # + def _suspend + false + end + + # + # Called when interaction has completed and one of the sides has closed. + # + def _interact_complete + true + end + + # + # Read from remote and write to local. + # + def _stream_read_remote_write_local(stream) + data = stream.get + + user_output.print(data) + end + + # + # Read from local and write to remote. + # + def _stream_read_local_write_remote(stream) + data = user_input.gets + + stream.put(data) + end + + def _local_fd + user_input.fd + end + + def _remote_fd(stream) + stream.fd + end + + # + # Interacts with two streaming connections, reading data from one and + # writing it to the other. Both are expected to implement Rex::IO::Stream. + # + def interact_stream(stream) + while self.interacting + # Select input and rstream + sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ]) + + # Cycle through the items that have data + # From the stream? Write to user_output. + sd[0].each { |s| + if (s == _remote_fd(stream)) + _stream_read_remote_write_local(stream) + # From user_input? Write to stream. + elsif (s == _local_fd) + _stream_read_local_write_remote(stream) + end + } if (sd) + end + + end + + # + # Installs a signal handler to monitor suspend signal notifications. + # + def handle_suspend + if (orig_suspend == nil) + self.orig_suspend = Signal.trap("TSTP") { + _suspend + } + end + end + + # + # Restores the previously installed signal handler for suspend + # notifications. + # + def restore_suspend + if (orig_suspend) + Signal.trap("TSTP", orig_suspend) + + self.orig_suspend = nil + end + end + + # + # Prompt the user for input if possible. + # + def prompt(query) + if (user_output and user_input) + user_output.print("\n" + query) + user_input.gets + end + end + + # + # Check the return value of a yes/no prompt + # + def prompt_yesno(query) + (prompt(query + " [y/N] ") =~ /^y/i) ? true : false + end + +end + +end +end diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index 151ca6fd39..c97f219cf1 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -101,7 +101,9 @@ module DispatcherShell } end + # # Run a single command line + # def run_single(line) arguments = parse_line(line) method = arguments.shift @@ -115,10 +117,9 @@ module DispatcherShell dispatcher_stack.each { |dispatcher| begin if (dispatcher.respond_to?('cmd_' + method)) + run_command(dispatcher, method, arguments) + found = true - eval(" - dispatcher.#{'cmd_' + method}(*arguments) - ") end rescue output.print_error( @@ -139,6 +140,13 @@ module DispatcherShell return found end + # + # Runs the supplied command on the given dispatcher. + # + def run_command(dispatcher, method, arguments) + eval("dispatcher.#{'cmd_' + method}(*arguments)") + end + # # If the command is unknown... # @@ -146,16 +154,58 @@ module DispatcherShell output.print_error("Unknown command: #{method}.") end + # # Push a dispatcher to the front of the stack + # def enstack_dispatcher(dispatcher) self.dispatcher_stack.unshift(dispatcher.new(self)) end + # # Pop a dispatcher from the front of the stacker + # def destack_dispatcher self.dispatcher_stack.shift end + # + # Return a readable version of a help banner for all of the enstacked + # dispatchers. + # + def help_to_s(opts = {}) + str = '' + + dispatcher_stack.reverse.each { |dispatcher| + # No commands? Suckage. + next if ((dispatcher.respond_to?('commands') == false) or + (dispatcher.commands.length == 0)) + + # Display the commands + tbl = Table.new( + 'Header' => "#{dispatcher.name} Commands", + 'Indent' => opts['Indent'] || 4, + 'Columns' => + [ + 'Command', + 'Description' + ], + 'ColProps' => + { + 'Command' => + { + 'MaxWidth' => 12 + } + }) + + dispatcher.commands.sort.each { |c| + tbl << c + } + + str += "\n" + tbl.to_s + "\n" + } + + return str + end attr_accessor :dispatcher_stack