113 lines
3.9 KiB
Ruby
113 lines
3.9 KiB
Ruby
require 'winrm'
|
|
require 'winrm/wsmv/write_stdin'
|
|
require 'net/winrm/ctrl_c'
|
|
require 'net/winrm/receive_response_reader'
|
|
|
|
module Net
|
|
module MsfWinRM
|
|
# WinRM shell to use stdin, rather than sending isolated commands
|
|
class StdinShell < WinRM::Shells::Cmd
|
|
# We create our own empty finalizers because the built-in one triggers a
|
|
# request using the Rex HTTP client, which segfaults; possibly because it
|
|
# creates a thread, or something else that is not allowed in a finalizer.
|
|
# In this situation (observed only when the user quits MSF with active sessions),
|
|
# we'll just let the shell continue.
|
|
def remove_finalizer; end
|
|
|
|
def add_finalizer; end
|
|
|
|
def send_command(command, arguments = [])
|
|
open unless shell_id
|
|
super(command, arguments)
|
|
end
|
|
|
|
# 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
|
|
# @param block [&block] The optional callback for any realtime output
|
|
# @yieldparam [string] standard out response text
|
|
# @yieldparam [string] standard error response text
|
|
# @yieldreturn [WinRM::Output] The command output
|
|
def read_stdout(command_id, &block)
|
|
open unless shell_id
|
|
begin
|
|
response_reader.read_output(command_output_message(shell_id, command_id), &block)
|
|
rescue WinRM::WinRMWSManFault => e
|
|
# If no output is available before the wsman:OperationTimeout expires,
|
|
# the server MUST return a WSManFault with the Code attribute equal to
|
|
# 2150858793. When the client receives this fault, it SHOULD issue
|
|
# another Receive request.
|
|
# http://msdn.microsoft.com/en-us/library/cc251676.aspx
|
|
if e.fault_code == '2150858793'
|
|
yield nil, nil
|
|
else
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
def send_ctrl_c(command_id)
|
|
ctrl_c_msg = CtrlC.new(
|
|
connection_opts,
|
|
shell_uri: shell_uri,
|
|
shell_id: shell_id,
|
|
command_id: command_id
|
|
)
|
|
transport.send_request(ctrl_c_msg.build)
|
|
end
|
|
|
|
def send_stdin(input, command_id)
|
|
open unless shell_id
|
|
|
|
stdin_msg = WinRM::WSMV::WriteStdin.new(
|
|
connection_opts,
|
|
shell_uri: shell_uri,
|
|
shell_id: shell_id,
|
|
command_id: command_id,
|
|
stdin: input
|
|
)
|
|
result = transport.send_request(stdin_msg.build)
|
|
result
|
|
rescue WinRM::WinRMWSManFault => e
|
|
raise unless [ERROR_OPERATION_ABORTED, SHELL_NOT_FOUND].include?(e.fault_code)
|
|
rescue WinRM::WinRMHTTPTransportError => e
|
|
# dont let the cleanup raise so we dont lose any errors from the command
|
|
logger.info("[WinRM] #{e.status_code} returned in cleanup with error: #{e.message}")
|
|
end
|
|
|
|
def response_reader
|
|
@response_reader ||= ReceiveResponseReader.new(transport, logger)
|
|
end
|
|
|
|
def open_shell
|
|
msg = WinRM::WSMV::CreateShell.new(connection_opts, shell_opts)
|
|
resp_doc = transport.send_request(msg.build)
|
|
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
|
|
|
|
end
|
|
end
|
|
end
|