Allow running a shell command separately to the currently interactive shell
This commit is contained in:
parent
91d0a6cc55
commit
9d8193150f
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'winrm'
|
||||
require 'shellwords'
|
||||
|
||||
module Msf::Sessions
|
||||
#
|
||||
|
@ -13,12 +14,13 @@ module Msf::Sessions
|
|||
class WinRMStreamAdapter
|
||||
# @param shell [Net::MsfWinRM::StdinShell] Shell for talking to the WinRM service
|
||||
# @param on_shell_ended [Method] Callback for when the background thread notices the shell has ended
|
||||
def initialize(shell, on_shell_ended)
|
||||
def initialize(shell, interactive_command_id, on_shell_ended)
|
||||
# To buffer input received while a session is backgrounded, we stick responses in a list
|
||||
@buffer_mutex = Mutex.new
|
||||
@buffer = []
|
||||
@check_stdin_event = Rex::Sync::Event.new(false, true)
|
||||
@received_stdout_event = Rex::Sync::Event.new(false, true)
|
||||
self.interactive_command_id = interactive_command_id
|
||||
self.shell = shell
|
||||
self.on_shell_ended = on_shell_ended
|
||||
end
|
||||
|
@ -37,7 +39,7 @@ module Msf::Sessions
|
|||
end
|
||||
|
||||
def write(buf)
|
||||
shell.send_stdin(buf)
|
||||
shell.send_stdin(buf, interactive_command_id)
|
||||
refresh_stdout
|
||||
end
|
||||
|
||||
|
@ -91,7 +93,7 @@ module Msf::Sessions
|
|||
loop do
|
||||
tmp_buffer = []
|
||||
output_seen = false
|
||||
shell.read_stdout do |stdout, stderr|
|
||||
shell.read_stdout(interactive_command_id) do |stdout, stderr|
|
||||
if stdout || stderr
|
||||
output_seen = true
|
||||
end
|
||||
|
@ -146,12 +148,12 @@ module Msf::Sessions
|
|||
# rubocop:disable Lint/SuppressedException
|
||||
def close
|
||||
stop_keep_alive_loop
|
||||
shell.cleanup_shell
|
||||
shell.cleanup_command(interactive_command_id)
|
||||
rescue WinRM::WinRMWSManFault
|
||||
end
|
||||
# rubocop:enable Lint/SuppressedException
|
||||
|
||||
attr_accessor :shell, :keep_alive_thread, :on_shell_ended
|
||||
attr_accessor :shell, :keep_alive_thread, :on_shell_ended, :interactive_command_id
|
||||
|
||||
end
|
||||
|
||||
|
@ -171,9 +173,10 @@ module Msf::Sessions
|
|||
#
|
||||
# @param shell [WinRM::Shells::Base] A WinRM shell object
|
||||
# @param opts [Hash] Optional parameters to pass to the session object.
|
||||
def initialize(shell, opts = {})
|
||||
def initialize(shell, interactive_command_id, opts = {})
|
||||
self.shell = shell
|
||||
self.adapter = WinRMStreamAdapter.new(self.shell, method(:shell_ended))
|
||||
self.interactive_command_id = interactive_command_id
|
||||
self.adapter = WinRMStreamAdapter.new(self.shell, interactive_command_id, method(:shell_ended))
|
||||
super(adapter, opts)
|
||||
end
|
||||
|
||||
|
@ -182,10 +185,16 @@ module Msf::Sessions
|
|||
end
|
||||
|
||||
def abort_foreground
|
||||
shell.send_ctrl_c
|
||||
shell.send_ctrl_c(interactive_command_id)
|
||||
adapter.refresh_stdout
|
||||
end
|
||||
|
||||
def shell_command(cmd, timeout = 5)
|
||||
args = Shellwords.shellwords(cmd)
|
||||
command = args.shift
|
||||
shell.shell_command_synchronous(command, args, timeout)
|
||||
end
|
||||
|
||||
# The characters used to terminate a command in this shell
|
||||
# (Breaks in 2012 without this)
|
||||
def command_termination
|
||||
|
@ -225,7 +234,7 @@ module Msf::Sessions
|
|||
|
||||
protected
|
||||
|
||||
attr_accessor :shell, :adapter
|
||||
attr_accessor :shell, :adapter, :interactive_command_id
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,25 +16,29 @@ module Net
|
|||
|
||||
def add_finalizer; end
|
||||
|
||||
def create_proc
|
||||
# We use cmd rather than powershell because powershell v3 on 2012 (and maybe earlier)
|
||||
# do not seem to pass us stdout/stderr.
|
||||
self.command_id = send_command('cmd.exe', [])
|
||||
def send_command(command, arguments = [])
|
||||
open unless shell_id
|
||||
super(command, arguments)
|
||||
end
|
||||
|
||||
def with_command_shell(input, _arguments = [])
|
||||
tries ||= 2
|
||||
send_stdin(input)
|
||||
yield shell_id, command_id
|
||||
rescue WinRM::WinRMWSManFault => e
|
||||
raise unless FAULTS_FOR_RESET.include?(e.fault_code) && (tries -= 1) > 0
|
||||
|
||||
reset_on_error(e)
|
||||
retry
|
||||
end
|
||||
|
||||
def cleanup_shell
|
||||
cleanup_command(command_id)
|
||||
# Runs a shell command synchronously, and returns the output
|
||||
def shell_command_synchronous(command, args, timeout)
|
||||
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
||||
command_id = send_command(command, args)
|
||||
buffer = []
|
||||
begin
|
||||
while (Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start_time) < (timeout * 1000)
|
||||
read_stdout(command_id) do |stdout, stderr|
|
||||
buffer << stdout if stdout
|
||||
buffer << stderr if stderr
|
||||
end
|
||||
end
|
||||
rescue EOFError
|
||||
# Shell terminated of its own accord
|
||||
ensure
|
||||
cleanup_command(command_id)
|
||||
end
|
||||
buffer.join('')
|
||||
end
|
||||
|
||||
# Runs the specified command with optional arguments
|
||||
|
@ -42,9 +46,8 @@ module Net
|
|||
# @yieldparam [string] standard out response text
|
||||
# @yieldparam [string] standard error response text
|
||||
# @yieldreturn [WinRM::Output] The command output
|
||||
def read_stdout(&block)
|
||||
def read_stdout(command_id, &block)
|
||||
open unless shell_id
|
||||
create_proc unless command_id
|
||||
begin
|
||||
response_reader.read_output(command_output_message(shell_id, command_id), &block)
|
||||
rescue WinRM::WinRMWSManFault => e
|
||||
|
@ -61,10 +64,7 @@ module Net
|
|||
end
|
||||
end
|
||||
|
||||
def send_ctrl_c
|
||||
open unless shell_id
|
||||
create_proc unless command_id
|
||||
|
||||
def send_ctrl_c(command_id)
|
||||
ctrl_c_msg = CtrlC.new(
|
||||
connection_opts,
|
||||
shell_uri: shell_uri,
|
||||
|
@ -74,9 +74,8 @@ module Net
|
|||
transport.send_request(ctrl_c_msg.build)
|
||||
end
|
||||
|
||||
def send_stdin(input)
|
||||
def send_stdin(input, command_id)
|
||||
open unless shell_id
|
||||
create_proc unless command_id
|
||||
|
||||
stdin_msg = WinRM::WSMV::WriteStdin.new(
|
||||
connection_opts,
|
||||
|
@ -101,15 +100,13 @@ module Net
|
|||
def open_shell
|
||||
msg = WinRM::WSMV::CreateShell.new(connection_opts, shell_opts)
|
||||
resp_doc = transport.send_request(msg.build)
|
||||
self.owner = REXML::XPath.first(resp_doc, '//rsp:Owner').text
|
||||
match = REXML::XPath.first(resp_doc, '//rsp:Owner')
|
||||
self.owner = match.text if match
|
||||
REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
||||
end
|
||||
|
||||
attr_accessor :owner
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :command_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,8 +98,6 @@ class MetasploitModule < Msf::Auxiliary
|
|||
}
|
||||
)
|
||||
shell = conn.shell(:stdin, {})
|
||||
# Trigger the shell to open
|
||||
shell.send_stdin('')
|
||||
session_setup(shell, rhost, rport, endpoint)
|
||||
end
|
||||
else
|
||||
|
@ -110,7 +108,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def session_setup(shell, _rhost, _rport, _endpoint)
|
||||
sess = Msf::Sessions::WinrmCommandShell.new(shell)
|
||||
# We use cmd rather than powershell because powershell v3 on 2012 (and maybe earlier)
|
||||
# do not seem to pass us stdout/stderr.
|
||||
interactive_process_id = shell.send_command('cmd.exe')
|
||||
sess = Msf::Sessions::WinrmCommandShell.new(shell, interactive_process_id)
|
||||
sess.platform = 'windows'
|
||||
username = datastore['USERNAME']
|
||||
password = datastore['PASSWORD']
|
||||
|
|
Loading…
Reference in New Issue