From 80c4bcd5ab33da2452f849723c4a5201cbb4d5aa Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 18 Feb 2007 04:25:46 +0000 Subject: [PATCH] Session detach support, closer to clean hand-off between session -d / session -i. Make autovnc look for both vncviewer and vncviewer.exe git-svn-id: file:///home/svn/framework3/trunk@4424 4d416f70-5f16-0410-b530-b9f4589650da --- .../app/controllers/console_controller.rb | 20 +++++-- data/msfweb/app/views/console/index.rhtml | 2 +- data/msfweb/public/javascripts/console.js | 2 +- data/msfweb/public/stylesheets/msfconsole.css | 1 + lib/msf/base/sessions/vncinject.rb | 6 +- lib/msf/ui/console/command_dispatcher/core.rb | 26 ++++++--- lib/msf/ui/web/console.rb | 5 +- lib/msf/ui/web/driver.rb | 7 ++- lib/rex/ui/interactive.rb | 55 +++++++++++++++---- lib/rex/ui/text/dispatcher_shell.rb | 3 + 10 files changed, 99 insertions(+), 28 deletions(-) diff --git a/data/msfweb/app/controllers/console_controller.rb b/data/msfweb/app/controllers/console_controller.rb index 84db8a367d..6a4b0be556 100644 --- a/data/msfweb/app/controllers/console_controller.rb +++ b/data/msfweb/app/controllers/console_controller.rb @@ -33,6 +33,10 @@ class ConsoleController < ApplicationController out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join pro = console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join + if (console.busy) + pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join + end + script = "// Metasploit Web Console Data\n" script += "var con_prompt = unescape('#{pro}');\n" script += "var con_update = unescape('#{out}');\n" @@ -66,7 +70,11 @@ class ConsoleController < ApplicationController out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join pro = @console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join - + + if (@console.busy) + pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join + end + script = "// Metasploit Web Console Data\n" script += "var con_prompt = unescape('#{pro}');\n" script += "var con_update = unescape('#{out}');\n" @@ -79,7 +87,7 @@ class ConsoleController < ApplicationController cmdl = params[:tab] out = "" - if (params[:tab].strip.length > 0) + if (not @console.busy and params[:tab].strip.length > 0) opts = @console.tab_complete(params[:tab]) || [] end @@ -108,14 +116,18 @@ class ConsoleController < ApplicationController cmdl = cmd_top[0, depth] end - out = "\n" + opts.map{ |c| " >> " + c }.join("\n") + out = "\n" + opts.map{ |c| ">> " + c }.join("\n") end end out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join pro = @console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join tln = cmdl.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join - + + if (@console.busy) + pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join + end + script = "// Metasploit Web Console Data\n" script += "var con_prompt = unescape('#{pro}');\n" script += "var con_update = unescape('#{out}');\n" diff --git a/data/msfweb/app/views/console/index.rhtml b/data/msfweb/app/views/console/index.rhtml index fcaca7a838..edcd1b394c 100644 --- a/data/msfweb/app/views/console/index.rhtml +++ b/data/msfweb/app/views/console/index.rhtml @@ -6,7 +6,7 @@ - msfweb v.3 - console demo + Metasploit Console <% ["prototype","effects","controls", "window", "application", "console"].each do |js| %> <%= javascript_include_tag js %><% end %> <%= stylesheet_link_tag "msfconsole" %> diff --git a/data/msfweb/public/javascripts/console.js b/data/msfweb/public/javascripts/console.js index 955f2b467d..3548c4d039 100644 --- a/data/msfweb/public/javascripts/console.js +++ b/data/msfweb/public/javascripts/console.js @@ -127,7 +127,7 @@ function console_keypress(e) { console_hindex = console_history.length - 1; } - console_printline("\n" + con_prompt + ' ' + console_input.value, 'output_line') + console_printline("\n>> " + console_input.value + "\n\n", 'output_line') if(cmd_internal[console_input.value]) { cmd_internal[console_input.value](); diff --git a/data/msfweb/public/stylesheets/msfconsole.css b/data/msfweb/public/stylesheets/msfconsole.css index 7357354d09..b2646edcd3 100644 --- a/data/msfweb/public/stylesheets/msfconsole.css +++ b/data/msfweb/public/stylesheets/msfconsole.css @@ -67,6 +67,7 @@ html,body { #console_command_bar { background: #000000; + margin-top: 1em; } #console_status { diff --git a/lib/msf/base/sessions/vncinject.rb b/lib/msf/base/sessions/vncinject.rb index 1f05435e50..bc0156c018 100644 --- a/lib/msf/base/sessions/vncinject.rb +++ b/lib/msf/base/sessions/vncinject.rb @@ -126,7 +126,11 @@ class VncInject # Launches VNC viewer against the local relay for this VNC server session. # def autovnc - if (Rex::FileUtils::find_full_path('vncviewer')) + vnc = + Rex::FileUtils::find_full_path('vncviewer') || + Rex::FileUtils::find_full_path('vncviewer.exe') + + if (vnc) Thread.new { system("vncviewer #{vlhost}::#{vlport}") } diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 39e8ca65da..6700efdef3 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -26,6 +26,7 @@ class Core "-l" => [ false, "List all active sessions." ], "-v" => [ false, "List verbose fields." ], "-q" => [ false, "Quiet mode." ], + "-d" => [ true, "Detach an interactive session" ], "-k" => [ true, "Terminate session." ]) @@jobs_opts = Rex::Parser::Arguments.new( @@ -623,6 +624,10 @@ class Core when "-k" method = 'kill' sid = val + + when "-d" + method = 'detach' + sid = val # Display help banner when "-h" @@ -639,9 +644,18 @@ class Core when 'kill' if ((session = framework.sessions.get(sid))) + print_status("Killing session #{sid}") session.kill end - + + when 'detach' + if ((session = framework.sessions.get(sid))) + print_status("Detaching session #{sid}") + if (session.interactive?) + session.detach() + end + end + when 'interact' if ((session = framework.sessions.get(sid))) if (session.interactive?) @@ -654,13 +668,9 @@ class Core # Interact session.interact() - - # Once interact returns, swap the output handle with a - # none output - # - # TODO: change this to use buffered output so we can call - # flush later on - session.reset_ui + + self.active_session = nil + else print_error("Session #{sid} is non-interactive.") end diff --git a/lib/msf/ui/web/console.rb b/lib/msf/ui/web/console.rb index 2e615f88ec..9592b1a1f0 100644 --- a/lib/msf/ui/web/console.rb +++ b/lib/msf/ui/web/console.rb @@ -77,7 +77,6 @@ class WebConsole def read update_access - self.pipe.read_subscriber('msfweb') end @@ -102,6 +101,10 @@ class WebConsole self.pipe.close self.thread.kill end + + def busy + self.console.busy + end end diff --git a/lib/msf/ui/web/driver.rb b/lib/msf/ui/web/driver.rb index 6278fe13f7..c83f32afba 100644 --- a/lib/msf/ui/web/driver.rb +++ b/lib/msf/ui/web/driver.rb @@ -100,17 +100,20 @@ class Driver < Msf::Ui::Driver # Ignore invalid sessions ses = self.framework.sessions[id] return if not ses - + # Has this session already been detached? return if ses.user_output.has_subscriber?('session_reader') + # Detach session if necessary + ses.detach() + # Create a new pipe spipe = WebConsole::WebConsolePipe.new spipe.input = spipe.pipe_input # Create a read subscriber spipe.create_subscriber('session_reader') - + # Replace the input/output handles ses.user_input = spipe.input ses.user_output = spipe diff --git a/lib/rex/ui/interactive.rb b/lib/rex/ui/interactive.rb index a01025a9af..36fa8ab272 100644 --- a/lib/rex/ui/interactive.rb +++ b/lib/rex/ui/interactive.rb @@ -21,7 +21,14 @@ module Interactive # rstream to user_output. # def interact + + # Prevent two thread from interacting at once + if(self.interacting) + raise RuntimeError, "This session is already interacting with another console" + end + self.interacting = true + self.completed = false eof = false @@ -53,25 +60,52 @@ module Interactive break if eof end + begin - # Restore the suspend handler - restore_suspend - - # If we've hit eof, call the interact complete handler - _interact_complete if (eof == true) + # Restore the suspend handler + restore_suspend - # Shutdown the readline thread - # XXX disabled - # user_input.readline_stop() if user_input.supports_readline + # If we've hit eof, call the interact complete handler + _interact_complete if (eof == true) + + # Shutdown the readline thread + # XXX disabled + # user_input.readline_stop() if user_input.supports_readline + + # Detach from the input/output handles + reset_ui() + + ensure + # Mark this as completed + self.completed = true + end # Return whether or not EOF was reached return eof end + # + # Stops the current interaction + # + def detach + if (self.interacting) + self.interacting = false + stime = Time.now.to_f + while(Time.now.to_f > stime + 5 and not self.completed) + select(nil, nil, nil, 0.10) + end + end + end + # # Whether or not the session is currently being interacted with # attr_accessor :interacting + + # + # Whether or not the session has completed interaction + # + attr_accessor :completed protected @@ -144,9 +178,10 @@ protected # writing it to the other. Both are expected to implement Rex::IO::Stream. # def interact_stream(stream) - while self.interacting + while self.interacting + # Select input and rstream - sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ]) + sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ], nil, nil, 0.25) # Cycle through the items that have data # From the stream? Write to user_output. diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index 0da21825b5..baa9b45247 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -226,7 +226,9 @@ module DispatcherShell # Runs the supplied command on the given dispatcher. # def run_command(dispatcher, method, arguments) + self.busy = true dispatcher.send('cmd_' + method, *arguments) + self.busy = false end # @@ -321,6 +323,7 @@ module DispatcherShell attr_accessor :dispatcher_stack # :nodoc: attr_accessor :tab_words # :nodoc: + attr_accessor :busy # :nodoc: end