From 2a6a8245cf5f797298cad3e495e11d394e40a9d9 Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Thu, 23 Oct 2014 05:56:26 -0500 Subject: [PATCH 01/11] Allow killing multiple specific sessions --- lib/msf/ui/console/command_dispatcher/core.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 47b9b239c1..9071f796ce 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -41,7 +41,7 @@ class Core "-v" => [ false, "List verbose fields" ], "-q" => [ false, "Quiet mode" ], "-d" => [ true, "Detach an interactive session" ], - "-k" => [ true, "Terminate session" ], + "-k" => [ true, "Terminate sessions by session ID and/or range" ], "-K" => [ false, "Terminate all sessions" ], "-s" => [ true, "Run a script on the session given with -i, or all"], "-r" => [ false, "Reset the ring buffer for the session given with -i, or all"], @@ -1708,11 +1708,15 @@ class Core end when 'kill' - if ((session = framework.sessions.get(sid))) - print_status("Killing session #{sid}") - session.kill - else - print_error("Invalid session identifier: #{sid}") + session_list = build_sessions_array(sid) + print_status("Killing the following session(s): #{session_list}") + session_list.each do |sess| + if ((session = framework.sessions.get(sess))) + print_status("Killing session #{sess}") + session.kill + else + print_error("Invalid session identifier: #{sess}") + end end when 'killall' From 13b6f1cf48d4f7dfa883e0ea1cf09ec4c7e77dfb Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Sat, 25 Oct 2014 09:39:15 -0500 Subject: [PATCH 02/11] Syntax changes --- lib/msf/ui/console/command_dispatcher/core.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 9071f796ce..9da4b2fce4 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1711,7 +1711,8 @@ class Core session_list = build_sessions_array(sid) print_status("Killing the following session(s): #{session_list}") session_list.each do |sess| - if ((session = framework.sessions.get(sess))) + session = framework.sessions.get(sess) + if session print_status("Killing session #{sess}") session.kill else From 36c85b7150636b5a599ccd68471d4aaf5f38c32a Mon Sep 17 00:00:00 2001 From: William Vu Date: Tue, 28 Oct 2014 03:01:53 -0500 Subject: [PATCH 03/11] Add support for jobs -k ranges --- lib/msf/ui/console/command_dispatcher/core.rb | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 9da4b2fce4..9fcb469f04 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -49,7 +49,7 @@ class Core @@jobs_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ], - "-k" => [ true, "Terminate the specified job name." ], + "-k" => [ true, "Terminate jobs by job ID and/or range." ], "-K" => [ false, "Terminate all running jobs." ], "-i" => [ true, "Lists detailed information about a running job."], "-l" => [ false, "List all running jobs." ], @@ -795,13 +795,17 @@ class Core when "-l" dump_list = true - # Terminate the supplied job name + # Terminate the supplied job ID(s) when "-k" - if (not framework.jobs.has_key?(val)) - print_error("No such job") - else - print_line("Stopping job: #{val}...") - framework.jobs.stop_job(val) + job_list = build_jobs_array(val) + print_status("Killing the following job(s): #{job_list.join(', ')}") + job_list.map(&:to_s).each do |job| + if framework.jobs.has_key?(job) + print_status("Killing job #{job}") + framework.jobs.stop_job(job) + else + print_error("Invalid job identifier: #{job}") + end end when "-K" print_line("Stopping all jobs...") @@ -1709,7 +1713,7 @@ class Core when 'kill' session_list = build_sessions_array(sid) - print_status("Killing the following session(s): #{session_list}") + print_status("Killing the following session(s): #{session_list.join(', ')}") session_list.each do |sess| session = framework.sessions.get(sess) if session @@ -3380,6 +3384,26 @@ class Core return session_list.uniq.sort end + # Generate an array of job IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10' + def build_jobs_array(jid_list) + job_list = Array.new + temp_list = jid_list.split(",") + + temp_list.each do |ele| + if ele.include? '-' + temp_array = (ele.split("-").inject {|s,e| s.to_i..e.to_i}).to_a + job_list.concat(temp_array) + elsif ele.include? '..' + temp_array = (ele.split("..").inject {|s,e| s.to_i..e.to_i}).to_a + job_list.concat(temp_array) + else + job_list.push(ele.to_i) + end + end + + return job_list.uniq.sort + end + end From 5547890002a8cbce20b433949dfa96267e5801a4 Mon Sep 17 00:00:00 2001 From: William Vu Date: Tue, 28 Oct 2014 03:07:46 -0500 Subject: [PATCH 04/11] Add support for sessions -d ranges --- lib/msf/ui/console/command_dispatcher/core.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 9fcb469f04..32d349f2f8 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1733,13 +1733,16 @@ class Core end when 'detach' - if ((session = framework.sessions.get(sid))) - print_status("Detaching session #{sid}") - if (session.interactive?) - session.detach() + session_list = build_sessions_array(sid) + print_status("Detaching the following session(s): #{session_list.join(', ')}") + session_list.each do |sess| + session = framework.sessions.get(sess) + if session + print_status("Detaching session #{sess}") + session.detach + else + print_error("Invalid session identifier: #{sess}") end - else - print_error("Invalid session identifier: #{sid}") end when 'interact' From 4251ad199ef887bc8e85b4a0d4a01b904abc5999 Mon Sep 17 00:00:00 2001 From: William Vu Date: Tue, 28 Oct 2014 05:49:30 -0500 Subject: [PATCH 05/11] Change killing back to stopping Got a little excited with the copypasta, I guess. --- lib/msf/ui/console/command_dispatcher/core.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 32d349f2f8..ed185d3f0c 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -798,10 +798,10 @@ class Core # Terminate the supplied job ID(s) when "-k" job_list = build_jobs_array(val) - print_status("Killing the following job(s): #{job_list.join(', ')}") + print_status("Stopping the following job(s): #{job_list.join(', ')}") job_list.map(&:to_s).each do |job| if framework.jobs.has_key?(job) - print_status("Killing job #{job}") + print_status("Stopping job #{job}") framework.jobs.stop_job(job) else print_error("Invalid job identifier: #{job}") From 0b8b0499f333f8f589cf84d5d0e4868fbc50df72 Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Fri, 31 Oct 2014 15:02:17 -0500 Subject: [PATCH 06/11] - Added range support to sessions -c and sessions -s - Added check for un-detach-able sessions - Added back the check for session.interactive? when detaching sessions - Collapse build_jobs_array and build_sessions_array to build_range_array - Added check for empty or invalid parameters to detach and kill [session | job] - Reworked session id sanity check around line 1660 - RuboCop/Style guide change: Array.new -> [] - Misc RuboCop/Style guide spacing changes --- lib/msf/ui/console/command_dispatcher/core.rb | 113 ++++++++---------- 1 file changed, 51 insertions(+), 62 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index ed185d3f0c..24bc982796 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -797,7 +797,11 @@ class Core # Terminate the supplied job ID(s) when "-k" - job_list = build_jobs_array(val) + job_list = build_range_array(val) + if job_list.blank? + print_error("Please specify valid job identifier(s)") + return false + end print_status("Stopping the following job(s): #{job_list.join(', ')}") job_list.map(&:to_s).each do |job| if framework.jobs.has_key?(job) @@ -1615,10 +1619,6 @@ class Core when "-k" method = 'kill' sid = val if val - if not sid - print_error("Specify a session to kill") - return false - end when "-K" method = 'killall' @@ -1653,15 +1653,18 @@ class Core end } - if sid and not framework.sessions.get(sid) - print_error("Invalid session id") - return false - end - if method.nil? and sid method = 'interact' end + unless sid.blank? || method == 'interact' + session_list = build_range_array(sid) + if session_list.blank? + print_error("Please specify valid session identifier(s)") + return false + end + end + # Now, perform the actual method case method @@ -1672,7 +1675,7 @@ class Core end cmds.each do |cmd| if sid - sessions = [ sid ] + sessions = session_list else sessions = framework.sessions.keys.sort end @@ -1712,7 +1715,6 @@ class Core end when 'kill' - session_list = build_sessions_array(sid) print_status("Killing the following session(s): #{session_list.join(', ')}") session_list.each do |sess| session = framework.sessions.get(sess) @@ -1727,27 +1729,30 @@ class Core when 'killall' print_status("Killing all sessions...") framework.sessions.each_sorted do |s| - if ((session = framework.sessions.get(s))) - session.kill - end + session = framework.sessions.get(s) + session.kill if session end when 'detach' - session_list = build_sessions_array(sid) print_status("Detaching the following session(s): #{session_list.join(', ')}") session_list.each do |sess| session = framework.sessions.get(sess) - if session + if session && session.interactive? print_status("Detaching session #{sess}") - session.detach + begin + session.detach + rescue NoMethodError + print_error "#{sess} is not detachable" + end else print_error("Invalid session identifier: #{sess}") end end when 'interact' - if ((session = framework.sessions.get(sid))) - if (session.interactive?) + session = framework.sessions.get(sid) + if session + if session.interactive? print_status("Starting interaction with #{session.name}...\n") if (quiet == false) self.active_session = session @@ -1756,7 +1761,7 @@ class Core self.active_session = nil - if (driver.input.supports_readline) + if driver.input.supports_readline driver.input.reset_tab_completion end @@ -1768,7 +1773,7 @@ class Core end when 'scriptall' - if (script.nil?) + if script.nil? print_error("No script specified!") return false end @@ -1778,17 +1783,16 @@ class Core script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script) if sid - print_status("Running script #{script} on session #{sid}...") - sessions = [ sid ] + sessions = session_list else - print_status("Running script #{script} on all sessions...") sessions = framework.sessions.keys.sort end - sessions.each do |s| - if ((session = framework.sessions.get(s))) - if (script_paths[session.type]) + session = framework.sessions.get(s) + if session + if script_paths[session.type] print_status("Session #{s} (#{session.session_host}):") + print_status("Running script #{script} on #{session.type} session #{s} (#{session.session_host})") begin session.execute_file(script_paths[session.type], extra) rescue ::Exception => e @@ -1799,12 +1803,12 @@ class Core end when 'upexec' - session_list = build_sessions_array(sid) print_status("Executing 'post/multi/manage/shell_to_meterpreter' on session(s): #{session_list}") session_list.each do |sess| - if ((session = framework.sessions.get(sess))) - if (session.interactive?) - if (session.type == "shell") + session = framework.sessions.get(sess) + if session + if session.interactive? + if session.type == "shell" session.init_ui(driver.input, driver.output) session.execute_script('post/multi/manage/shell_to_meterpreter') session.reset_ui @@ -3367,44 +3371,29 @@ class Core return all_lines.slice(start..finish) end - # Generate an array of session IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10' - def build_sessions_array(sid_list) - session_list = Array.new - temp_list = sid_list.split(",") + # Generate an array of job or session IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10' + def build_range_array(id_list) + return if id_list.blank? + item_list = [] + temp_list = id_list.split(",") temp_list.each do |ele| + return if ele.count('-') > 1 + return if ele[0] == '-' || ele[-1] == '-' + return if ele[0] == '.' || ele[-1] == '.' + if ele.include? '-' - temp_array = (ele.split("-").inject {|s,e| s.to_i..e.to_i}).to_a - session_list.concat(temp_array) + temp_array = (ele.split("-").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) elsif ele.include? '..' - temp_array = (ele.split("..").inject {|s,e| s.to_i..e.to_i}).to_a - session_list.concat(temp_array) + temp_array = (ele.split("..").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) else - session_list.push(ele.to_i) + item_list.push(ele.to_i) end end - return session_list.uniq.sort - end - - # Generate an array of job IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10' - def build_jobs_array(jid_list) - job_list = Array.new - temp_list = jid_list.split(",") - - temp_list.each do |ele| - if ele.include? '-' - temp_array = (ele.split("-").inject {|s,e| s.to_i..e.to_i}).to_a - job_list.concat(temp_array) - elsif ele.include? '..' - temp_array = (ele.split("..").inject {|s,e| s.to_i..e.to_i}).to_a - job_list.concat(temp_array) - else - job_list.push(ele.to_i) - end - end - - return job_list.uniq.sort + item_list.uniq.sort end end From 78a4ee686b09c273ea136ae4707ba0ab905af37b Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 4 Nov 2014 23:33:31 -0600 Subject: [PATCH 07/11] modernizes & DRYs session/job ranges --- lib/msf/ui/console/command_dispatcher/core.rb | 501 +++++++++--------- 1 file changed, 244 insertions(+), 257 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 24bc982796..968206493c 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -778,9 +778,7 @@ class Core def cmd_jobs(*args) # Make the default behavior listing all jobs if there were no options # or the only option is the verbose flag - if (args.length == 0 or args == ["-v"]) - args.unshift("-l") - end + args.unshift("-l") if args.length == 0 || args == ["-v"] verbose = false dump_list = false @@ -788,13 +786,12 @@ class Core job_id = nil # Parse the command options - @@jobs_opts.parse(args) { |opt, idx, val| + @@jobs_opts.parse(args) do |opt, idx, val| case opt when "-v" verbose = true when "-l" dump_list = true - # Terminate the supplied job ID(s) when "-k" job_list = build_range_array(val) @@ -825,28 +822,28 @@ class Core cmd_jobs_help return false end - } - - if (dump_list) - print("\n" + Serializer::ReadableText.dump_jobs(framework, verbose) + "\n") end - if (dump_info) - if (job_id and framework.jobs[job_id.to_s]) + + if dump_list + print("\n#{Serializer::ReadableText.dump_jobs(framework, verbose)}\n") + end + if dump_info + if job_id && framework.jobs[job_id.to_s] job = framework.jobs[job_id.to_s] mod = job.ctx[0] - output = "\n" + output = '\n' output += "Name: #{mod.name}" output += ", started at #{job.start_time}" if job.start_time print_line(output) - if (mod.options.has_options?) - show_options(mod) - end + show_options(mod) if mod.options.has_options? - if (verbose) + if verbose mod_opt = Serializer::ReadableText.dump_advanced_options(mod,' ') - print_line("\nModule advanced options:\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0) + if mod_opt && mod_opt.length > 0 + print_line("\nModule advanced options:\n\n#{mod_opt}\n") + end end else print_line("Invalid Job ID") @@ -1571,7 +1568,7 @@ class Core print_line "Usage: sessions [options]" print_line print_line "Active session manipulation and interaction." - print(@@sessions_opts.usage()) + print(@@sessions_opts.usage) end # @@ -1592,68 +1589,55 @@ class Core extra = [] # Parse the command options - @@sessions_opts.parse(args) { |opt, idx, val| + @@sessions_opts.parse(args) do |opt, idx, val| case opt - when "-q" - quiet = true - - # Run a command on all sessions, or the session given with -i - when "-c" - method = 'cmd' - if (val) - cmds << val - end - - when "-v" - verbose = true - - # Do something with the supplied session identifier instead of - # all sessions. - when "-i" - sid = val - - # Display the list of active sessions - when "-l" - method = 'list' - - when "-k" - method = 'kill' - sid = val if val - - when "-K" - method = 'killall' - - when "-d" - method = 'detach' - sid = val - - # Run a script on all meterpreter sessions - when "-s" - if not script - method = 'scriptall' - script = val - end - - # Upload and exec to the specific command session - when "-u" - method = 'upexec' - sid = val - - # Reset the ring buffer read pointer - when "-r" - reset_ring = true - method = 'reset_ring' - - # Display help banner - when "-h" - cmd_sessions_help - return false - else - extra << val + when "-q" + quiet = true + # Run a command on all sessions, or the session given with -i + when "-c" + method = 'cmd' + cmds << val if val + when "-v" + verbose = true + # Do something with the supplied session identifier instead of + # all sessions. + when "-i" + sid = val + # Display the list of active sessions + when "-l" + method = 'list' + when "-k" + method = 'kill' + sid = val if val + when "-K" + method = 'killall' + when "-d" + method = 'detach' + sid = val + # Run a script on all meterpreter sessions + when "-s" + unless script + method = 'scriptall' + script = val + end + # Upload and exec to the specific command session + when "-u" + method = 'upexec' + sid = val + # Reset the ring buffer read pointer + when "-r" + reset_ring = true + method = 'reset_ring' + # Display help banner + when "-h" + cmd_sessions_help + return false + else + extra << val end - } + end - if method.nil? and sid + if !method && sid method = 'interact' end @@ -1667,121 +1651,12 @@ class Core # Now, perform the actual method case method - - when 'cmd' - if (cmds.length < 1) - print_error("No command specified!") - return false - end - cmds.each do |cmd| - if sid - sessions = session_list - else - sessions = framework.sessions.keys.sort - end - sessions.each do |s| - session = framework.sessions.get(s) - print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})") - - if (session.type == "meterpreter") - # If session.sys is nil, dont even try.. - if not (session.sys) - print_error("Session #{s} does not have stdapi loaded, skipping...") - next - end - c, c_args = cmd.split(' ', 2) - begin - process = session.sys.process.execute(c, c_args, - { - 'Channelized' => true, - 'Hidden' => true - }) - rescue ::Rex::Post::Meterpreter::RequestError - print_error("Failed: #{$!.class} #{$!}") - end - if process and process.channel and (data = process.channel.read) - print_line(data) - end - elsif session.type == "shell" - if (output = session.shell_command(cmd)) - print_line(output) - end - end - # If the session isn't a meterpreter or shell type, it - # could be a VNC session (which can't run commands) or - # something custom (which we don't know how to run - # commands on), so don't bother. - end - end - - when 'kill' - print_status("Killing the following session(s): #{session_list.join(', ')}") - session_list.each do |sess| - session = framework.sessions.get(sess) - if session - print_status("Killing session #{sess}") - session.kill - else - print_error("Invalid session identifier: #{sess}") - end - end - - when 'killall' - print_status("Killing all sessions...") - framework.sessions.each_sorted do |s| - session = framework.sessions.get(s) - session.kill if session - end - - when 'detach' - print_status("Detaching the following session(s): #{session_list.join(', ')}") - session_list.each do |sess| - session = framework.sessions.get(sess) - if session && session.interactive? - print_status("Detaching session #{sess}") - begin - session.detach - rescue NoMethodError - print_error "#{sess} is not detachable" - end - else - print_error("Invalid session identifier: #{sess}") - end - end - - when 'interact' - session = framework.sessions.get(sid) - if session - if session.interactive? - print_status("Starting interaction with #{session.name}...\n") if (quiet == false) - - self.active_session = session - - session.interact(driver.input.dup, driver.output) - - self.active_session = nil - - if driver.input.supports_readline - driver.input.reset_tab_completion - end - - else - print_error("Session #{sid} is non-interactive.") - end - else - print_error("Invalid session identifier: #{sid}") - end - - when 'scriptall' - if script.nil? - print_error("No script specified!") - return false - end - - script_paths = {} - script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script) - script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script) - + when 'cmd' + if cmds.length < 1 + print_error("No command specified!") + return false + end + cmds.each do |cmd| if sid sessions = session_list else @@ -1789,61 +1664,141 @@ class Core end sessions.each do |s| session = framework.sessions.get(s) - if session - if script_paths[session.type] - print_status("Session #{s} (#{session.session_host}):") - print_status("Running script #{script} on #{session.type} session #{s} (#{session.session_host})") - begin - session.execute_file(script_paths[session.type], extra) - rescue ::Exception => e - log_error("Error executing script: #{e.class} #{e}") - end - end - end - end + print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})") - when 'upexec' - print_status("Executing 'post/multi/manage/shell_to_meterpreter' on session(s): #{session_list}") - session_list.each do |sess| - session = framework.sessions.get(sess) - if session - if session.interactive? - if session.type == "shell" - session.init_ui(driver.input, driver.output) - session.execute_script('post/multi/manage/shell_to_meterpreter') - session.reset_ui - else - print_error("Session #{sess} is not a command shell session, skipping...") - next - end - else - print_error("Session #{sess} is non-interactive, skipping...") + if session.type == 'meterpreter' + # If session.sys is nil, dont even try.. + print_good "Checking if session.sys" + unless session.sys + print_error("Session #{s} does not have stdapi loaded, skipping...") next end + c, c_args = cmd.split(' ', 2) + begin + process = session.sys.process.execute(c, c_args, + { + 'Channelized' => true, + 'Hidden' => true + }) + rescue ::Rex::Post::Meterpreter::RequestError + print_error("Failed: #{$!.class} #{$!}") + end + if process && process.channel + data = process.channel.read + print_line(data) if data + end + elsif session.type == 'shell' + output = session.shell_command(cmd) + print_line(output) if output + end + # If the session isn't a meterpreter or shell type, it + # could be a VNC session (which can't run commands) or + # something custom (which we don't know how to run + # commands on), so don't bother. + end + end + when 'kill' + session_list.each do |sess_id| + print_status("Killing the following session(s): #{session_list.join(', ')}") + session = framework.sessions.get(sess_id) + if session + print_status("Killing session #{sess_id}") + session.kill + else + print_error("Invalid session identifier: #{sess_id}") + end + end + when 'killall' + print_status("Killing all sessions...") + framework.sessions.each_sorted do |s| + session = framework.sessions.get(s) + session.kill if session + end + when 'detach' + print_status("Detaching the following session(s): #{session_list.join(', ')}") + session_list.each do |sess_id| + session = verify_session(sess_id) + # if session is interactive, it's detachable + if session + print_status("Detaching session #{sess_id}") + session.detach + end + end + when 'interact' + session = verify_session(sid) + if session + print_status("Starting interaction with #{session.name}...\n") unless quiet + self.active_session = session + session.interact(driver.input.dup, driver.output) + self.active_session = nil + driver.input.reset_tab_completion if driver.input.supports_readline + end + when 'scriptall' + unless script + print_error("No script specified!") + return false + end + script_paths = {} + script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script) + script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script) + + sessions = sid ? session_list : framework.sessions.keys.sort + + sessions.each do |sess_id| + session = verify_session(sess_id, true) + # @TODO: Not interactive sessions can or cannot have scripts run on them? + if session == false # specifically looking for false + # if verify_session returned false, sess_id is valid, but not interactive + session = framework.sessions.get(sess_id) + end + if session + if script_paths[session.type] + print_status("Session #{sess_id} (#{session.session_host}):") + print_status("Running script #{script} on #{session.type} session" + + " #{sess_id} (#{session.session_host})") + begin + session.execute_file(script_paths[session.type], extra) + rescue ::Exception => e + log_error("Error executing script: #{e.class} #{e}") + end + end + else + print_error("Invalid session identifier: #{sess_id}") + end + end + when 'upexec' + print_status("Executing 'post/multi/manage/shell_to_meterpreter' on " + + "session(s): #{session_list}") + session_list.each do |sess_id| + session = verify_session(sess_id) + if session + if session.type == 'shell' + session.init_ui(driver.input, driver.output) + session.execute_script('post/multi/manage/shell_to_meterpreter') + session.reset_ui else - print_error("Invalid session identifier: #{sess}") + print_error("Session #{sess_id} is not a command shell session, skipping...") next end - - if session_list.count > 1 - print_status("Sleeping 5 seconds to allow the previous handler to finish..") - sleep(5) - end end - when 'reset_ring' - sessions = sid ? [ sid ] : framework.sessions.keys - sessions.each do |sidx| - s = framework.sessions[sidx] - next if not (s and s.respond_to?(:ring_seq)) - s.reset_ring_sequence - print_status("Reset the ring buffer pointer for Session #{sidx}") + if session_list.count > 1 + print_status("Sleeping 5 seconds to allow the previous handler to finish..") + sleep(5) end - - when 'list',nil - print_line - print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose)) - print_line + end + when 'reset_ring' + sessions = sid ? [sid] : framework.sessions.keys + sessions.each do |sidx| + s = framework.sessions[sidx] + next unless (s && s.respond_to?(:ring_seq)) + s.reset_ring_sequence + print_status("Reset the ring buffer pointer for Session #{sidx}") + end + when 'list',nil + print_line + print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose)) + print_line end rescue IOError, EOFError, Rex::StreamClosedError @@ -1857,7 +1812,7 @@ class Core # Reset the active session self.active_session = nil - return true + true end # @@ -3017,6 +2972,34 @@ class Core protected + # + # verifies that a given session_id is valid and that the session is interactive + # interactive. The various return values allow the caller to make better + # decisions on what action can & should be taken depending on the capabilities + # of the session and the caller's objective while making it simple to use in + # the nominal case where the caller needs session_id to match an interactive + # session + # + # @param session_id [String] A session id, which is an integer as a string + # @param quiet [Boolean] True means the method will produce no error messages + # @return [session] if the given session_id is valid and session is interactive + # @return [false] if the given session_id is valid, but not interactive + # @return [nil] if the given session_id is not valid at all + def verify_session(session_id, quiet = false) + session = framework.sessions.get(session_id) + if session + if session.interactive? + session + else + print_error("Session #{session_id} is non-interactive.") unless quiet + false + end + else + print_error("Invalid session identifier: #{session_id}") unless quiet + nil + end + end + # # Go_pro methods -- these are used to start and connect to # Metasploit Community / Pro. @@ -3368,28 +3351,33 @@ class Core start = line_num - before start = 0 if start < 0 finish = line_num + after - return all_lines.slice(start..finish) + all_lines.slice(start..finish) end - # Generate an array of job or session IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10' + # + # Generate an array of job or session IDs from a given range String. + # Always returns an Array. + # + # @param id_list [String] Range or list description such as 1-5 or 1,3,5 etc + # @return [Array] Representing the range def build_range_array(id_list) - return if id_list.blank? item_list = [] - temp_list = id_list.split(",") + unless id_list.blank? + temp_list = id_list.split(',') + temp_list.each do |ele| + return if ele.count('-') > 1 + return if ele.first == '-' || ele[-1] == '-' + return if ele.first == '.' || ele[-1] == '.' - temp_list.each do |ele| - return if ele.count('-') > 1 - return if ele[0] == '-' || ele[-1] == '-' - return if ele[0] == '.' || ele[-1] == '.' - - if ele.include? '-' - temp_array = (ele.split("-").inject { |s, e| s.to_i..e.to_i }).to_a - item_list.concat(temp_array) - elsif ele.include? '..' - temp_array = (ele.split("..").inject { |s, e| s.to_i..e.to_i }).to_a - item_list.concat(temp_array) - else - item_list.push(ele.to_i) + if ele.include? '-' + temp_array = (ele.split("-").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) + elsif ele.include? '..' + temp_array = (ele.split("..").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) + else + item_list.push(ele.to_i) + end end end @@ -3398,5 +3386,4 @@ class Core end - end end end end From 8aa6fca76029718253273384888f1500ee8daa54 Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Wed, 5 Nov 2014 06:46:55 -0600 Subject: [PATCH 08/11] Minor fixes and status update Minor tweaks after the PR from @kernelsmith Remaining items: 1. Handle empty session IDs correctly, for example 'sessions -d' or 'sessions -k' 2. Find a method of explaining the range options in the help text 3. Retest all changed code areas 4. Edit PR Summary to reflect changes to the scope --- lib/msf/ui/console/command_dispatcher/core.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 968206493c..0396842b15 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1613,7 +1613,7 @@ class Core method = 'killall' when "-d" method = 'detach' - sid = val + sid = val || nil # Run a script on all meterpreter sessions when "-s" unless script @@ -1698,8 +1698,8 @@ class Core end end when 'kill' + print_status("Killing the following session(s): #{session_list.join(', ')}") session_list.each do |sess_id| - print_status("Killing the following session(s): #{session_list.join(', ')}") session = framework.sessions.get(sess_id) if session print_status("Killing session #{sess_id}") @@ -2973,12 +2973,11 @@ class Core protected # - # verifies that a given session_id is valid and that the session is interactive - # interactive. The various return values allow the caller to make better - # decisions on what action can & should be taken depending on the capabilities - # of the session and the caller's objective while making it simple to use in - # the nominal case where the caller needs session_id to match an interactive - # session + # verifies that a given session_id is valid and that the session is interactive. + # The various return values allow the caller to make better decisions on what + # action can & should be taken depending on the capabilities of the session + # and the caller's objective while making it simple to use in the nominal case + # where the caller needs session_id to match an interactive session # # @param session_id [String] A session id, which is an integer as a string # @param quiet [Boolean] True means the method will produce no error messages From 2bec646393067f6b216224fbbf3b1bd646c13c86 Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Wed, 5 Nov 2014 06:49:06 -0600 Subject: [PATCH 09/11] rolling back a change --- lib/msf/ui/console/command_dispatcher/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 0396842b15..ba807f4bf1 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1613,7 +1613,7 @@ class Core method = 'killall' when "-d" method = 'detach' - sid = val || nil + sid = val # Run a script on all meterpreter sessions when "-s" unless script From 8bf6a34d6cfcef5ec44d8b769bfedcf7f4833cee Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Thu, 6 Nov 2014 07:18:55 -0600 Subject: [PATCH 10/11] Fix empty session ID and cleanup - Fixed handling of empty session IDs for those commands that required them - Added help text for ranges with examples --- lib/msf/ui/console/command_dispatcher/core.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index ba807f4bf1..73106b6966 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1569,6 +1569,10 @@ class Core print_line print_line "Active session manipulation and interaction." print(@@sessions_opts.usage) + print_line + print_line "Many options allow specifying session ranges using commas and dashes." + print_line "For example: sessions -s checkvm -i 1,3-5 or sessions -k 1-2,5,6" + print_line end # @@ -1608,12 +1612,12 @@ class Core method = 'list' when "-k" method = 'kill' - sid = val if val + sid = val || false when "-K" method = 'killall' when "-d" method = 'detach' - sid = val + sid = val || false # Run a script on all meterpreter sessions when "-s" unless script @@ -1623,7 +1627,7 @@ class Core # Upload and exec to the specific command session when "-u" method = 'upexec' - sid = val + sid = val || false # Reset the ring buffer read pointer when "-r" reset_ring = true @@ -1641,7 +1645,7 @@ class Core method = 'interact' end - unless sid.blank? || method == 'interact' + unless sid.nil? || method == 'interact' session_list = build_range_array(sid) if session_list.blank? print_error("Please specify valid session identifier(s)") @@ -1662,8 +1666,13 @@ class Core else sessions = framework.sessions.keys.sort end + if sessions.blank? + print_error("Please specify valid session identifier(s) using -i") + return false + end sessions.each do |s| - session = framework.sessions.get(s) + session = verify_session(s) + next unless session print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})") if session.type == 'meterpreter' From 9295d9077e89e239dfe01932adf53450da9ad97e Mon Sep 17 00:00:00 2001 From: Tom Sellers Date: Thu, 6 Nov 2014 09:27:44 -0600 Subject: [PATCH 11/11] Remove debugging output --- lib/msf/ui/console/command_dispatcher/core.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 73106b6966..f3e782965b 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1677,7 +1677,6 @@ class Core if session.type == 'meterpreter' # If session.sys is nil, dont even try.. - print_good "Checking if session.sys" unless session.sys print_error("Session #{s} does not have stdapi loaded, skipping...") next