2021-08-31 11:00:53 +10:00
|
|
|
# -*- coding: binary -*-
|
|
|
|
|
|
|
|
|
|
require 'winrm'
|
2021-09-21 16:20:51 +10:00
|
|
|
require 'shellwords'
|
2021-08-31 11:00:53 +10:00
|
|
|
|
|
|
|
|
module Msf::Sessions
|
|
|
|
|
#
|
|
|
|
|
# This class provides a session for WinRM client connections, where Metasploit
|
|
|
|
|
# has authenticated to a remote WinRM instance.
|
|
|
|
|
#
|
2021-09-09 22:40:43 +10:00
|
|
|
class WinrmCommandShell < Msf::Sessions::CommandShell
|
2021-09-10 18:15:43 +10:00
|
|
|
|
|
|
|
|
# Abstract WinRM to look like a stream so CommandShell can be happy
|
2021-09-10 17:45:08 +10:00
|
|
|
class WinRMStreamAdapter
|
2021-09-10 18:15:43 +10:00
|
|
|
# @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
|
2021-09-21 16:20:51 +10:00
|
|
|
def initialize(shell, interactive_command_id, on_shell_ended)
|
2021-09-10 17:45:08 +10:00
|
|
|
# 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)
|
2021-09-10 22:19:38 +10:00
|
|
|
@received_stdout_event = Rex::Sync::Event.new(false, true)
|
2021-09-21 16:20:51 +10:00
|
|
|
self.interactive_command_id = interactive_command_id
|
2021-09-10 17:45:08 +10:00
|
|
|
self.shell = shell
|
|
|
|
|
self.on_shell_ended = on_shell_ended
|
|
|
|
|
end
|
2021-09-10 18:15:43 +10:00
|
|
|
|
|
|
|
|
def peerinfo
|
|
|
|
|
shell.transport.peerinfo
|
|
|
|
|
end
|
2021-09-02 13:23:26 +10:00
|
|
|
|
2021-09-10 18:15:43 +10:00
|
|
|
def localinfo
|
|
|
|
|
shell.transport.localinfo
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# Trigger the background thread to go get more stdout
|
2021-09-10 22:19:38 +10:00
|
|
|
def refresh_stdout
|
|
|
|
|
@check_stdin_event.set
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def write(buf)
|
2021-09-21 16:20:51 +10:00
|
|
|
shell.send_stdin(buf, interactive_command_id)
|
2021-09-10 22:19:38 +10:00
|
|
|
refresh_stdout
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-10 18:15:43 +10:00
|
|
|
##
|
|
|
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
|
|
|
#
|
|
|
|
|
# Read from the command shell.
|
|
|
|
|
#
|
2021-09-13 11:40:03 +10:00
|
|
|
def get_once(length = -1, timeout = 1)
|
2021-09-10 22:19:38 +10:00
|
|
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
2021-09-13 11:40:03 +10:00
|
|
|
result = ''
|
2021-09-10 22:19:38 +10:00
|
|
|
loop do
|
|
|
|
|
result = _get_once(length)
|
|
|
|
|
time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
|
|
|
elapsed = time - start_time
|
2021-09-13 11:40:03 +10:00
|
|
|
time_remaining = timeout - elapsed
|
2021-09-10 22:19:38 +10:00
|
|
|
break if (result != '' || time_remaining <= 0)
|
2021-09-13 11:40:03 +10:00
|
|
|
|
|
|
|
|
# rubocop:disable Lint/SuppressedException
|
2021-09-10 22:19:38 +10:00
|
|
|
begin
|
2021-09-12 19:38:16 +10:00
|
|
|
# We didn't receive anything - let's wait for some more
|
2021-09-10 22:19:38 +10:00
|
|
|
@received_stdout_event.wait(time_remaining)
|
2022-04-23 02:40:02 +01:00
|
|
|
rescue ::Timeout::Error
|
2021-09-10 22:19:38 +10:00
|
|
|
end
|
2021-09-13 11:40:03 +10:00
|
|
|
# rubocop:enable Lint/SuppressedException
|
2021-09-10 22:19:38 +10:00
|
|
|
# If we didn't get anything, let's hurry the background thread along
|
|
|
|
|
refresh_stdout unless result
|
|
|
|
|
end
|
|
|
|
|
result
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def _get_once(length)
|
2021-09-10 18:15:43 +10:00
|
|
|
result = ''
|
|
|
|
|
@buffer_mutex.synchronize do
|
|
|
|
|
result = @buffer.join('')
|
|
|
|
|
@buffer = []
|
|
|
|
|
if (length > -1) && (result.length > length)
|
|
|
|
|
# Return up to length, and keep the rest in the buffer
|
|
|
|
|
extra = result[length..-1]
|
|
|
|
|
result = result[0, length]
|
|
|
|
|
@buffer << extra
|
|
|
|
|
end
|
2021-09-10 15:13:30 +10:00
|
|
|
end
|
2021-09-10 18:15:43 +10:00
|
|
|
result
|
2021-09-08 08:19:43 +10:00
|
|
|
end
|
|
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# Start a background thread for regularly checking for stdout
|
2021-09-10 18:15:43 +10:00
|
|
|
def start_keep_alive_loop(framework)
|
|
|
|
|
self.keep_alive_thread = framework.threads.spawn('WinRM-shell-keepalive', false, shell) do |_thr_shell|
|
|
|
|
|
loop_delay = 0.5
|
|
|
|
|
loop do
|
|
|
|
|
tmp_buffer = []
|
|
|
|
|
output_seen = false
|
2021-09-21 16:20:51 +10:00
|
|
|
shell.read_stdout(interactive_command_id) do |stdout, stderr|
|
2021-09-10 18:15:43 +10:00
|
|
|
if stdout || stderr
|
|
|
|
|
output_seen = true
|
|
|
|
|
end
|
|
|
|
|
tmp_buffer << stdout if stdout
|
|
|
|
|
tmp_buffer << stderr if stderr
|
|
|
|
|
end
|
|
|
|
|
@buffer_mutex.synchronize do
|
|
|
|
|
@buffer.concat(tmp_buffer)
|
2021-09-07 14:37:19 +10:00
|
|
|
end
|
|
|
|
|
|
2021-09-10 18:15:43 +10:00
|
|
|
# If our last request received stdout, let's be ready for some more
|
|
|
|
|
if output_seen
|
2021-09-10 22:19:38 +10:00
|
|
|
@received_stdout_event.set
|
2021-09-10 18:15:43 +10:00
|
|
|
loop_delay = 0.5
|
|
|
|
|
else
|
|
|
|
|
# Gradual backoff
|
|
|
|
|
loop_delay *= 4
|
|
|
|
|
loop_delay = [loop_delay, 30].min
|
|
|
|
|
end
|
2021-09-07 14:37:19 +10:00
|
|
|
|
2021-09-10 18:15:43 +10:00
|
|
|
# Wait loop_delay seconds, or until an interactive thread wakes us up
|
|
|
|
|
begin
|
|
|
|
|
@check_stdin_event.wait(loop_delay)
|
|
|
|
|
# rubocop:disable Lint/SuppressedException
|
2022-04-23 02:40:02 +01:00
|
|
|
rescue ::Timeout::Error
|
2021-09-10 18:15:43 +10:00
|
|
|
end
|
|
|
|
|
# rubocop:enable Lint/SuppressedException
|
|
|
|
|
Thread.pass
|
|
|
|
|
rescue WinRM::WinRMWSManFault => e
|
|
|
|
|
print_error(e.fault_description)
|
|
|
|
|
on_shell_ended.call
|
|
|
|
|
rescue EOFError
|
|
|
|
|
# Shell has been terminated
|
|
|
|
|
on_shell_ended.call
|
|
|
|
|
rescue Rex::HostUnreachable => e
|
|
|
|
|
on_shell_ended.call(e.message)
|
|
|
|
|
rescue StandardError => e
|
|
|
|
|
on_shell_ended.call(e.message)
|
2021-09-08 07:36:59 +10:00
|
|
|
end
|
2021-09-02 13:23:26 +10:00
|
|
|
end
|
|
|
|
|
end
|
2021-08-31 21:36:25 +10:00
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# Stop the background thread
|
2021-09-10 18:15:43 +10:00
|
|
|
def stop_keep_alive_loop
|
|
|
|
|
keep_alive_thread.kill
|
|
|
|
|
end
|
2021-09-06 09:33:44 +10:00
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# Close the shell; cleanly terminating it on the server if possible
|
2021-09-13 11:40:03 +10:00
|
|
|
#
|
|
|
|
|
# The shell may already be dead, or unreachable at this point, so do a best
|
|
|
|
|
# effort, and capture exceptions
|
|
|
|
|
# rubocop:disable Lint/SuppressedException
|
2021-09-10 18:15:43 +10:00
|
|
|
def close
|
|
|
|
|
stop_keep_alive_loop
|
2021-09-21 16:20:51 +10:00
|
|
|
shell.cleanup_command(interactive_command_id)
|
2021-09-10 18:15:43 +10:00
|
|
|
rescue WinRM::WinRMWSManFault
|
|
|
|
|
end
|
2021-09-13 11:40:03 +10:00
|
|
|
# rubocop:enable Lint/SuppressedException
|
2021-09-08 07:36:59 +10:00
|
|
|
|
2021-09-21 16:20:51 +10:00
|
|
|
attr_accessor :shell, :keep_alive_thread, :on_shell_ended, :interactive_command_id
|
2021-09-10 17:45:08 +10:00
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def commands
|
|
|
|
|
{
|
|
|
|
|
'help' => 'Help menu',
|
|
|
|
|
'background' => 'Backgrounds the current shell session',
|
|
|
|
|
'sessions' => 'Quickly switch to another session',
|
|
|
|
|
'resource' => 'Run a meta commands script stored in a local file',
|
|
|
|
|
'irb' => 'Open an interactive Ruby shell on the current session',
|
|
|
|
|
'pry' => 'Open the Pry debugger on the current session'
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Create an MSF command shell from a WinRM shell object
|
|
|
|
|
#
|
|
|
|
|
# @param shell [WinRM::Shells::Base] A WinRM shell object
|
|
|
|
|
# @param opts [Hash] Optional parameters to pass to the session object.
|
2021-09-21 16:20:51 +10:00
|
|
|
def initialize(shell, interactive_command_id, opts = {})
|
2021-09-10 17:45:08 +10:00
|
|
|
self.shell = shell
|
2021-09-21 16:20:51 +10:00
|
|
|
self.interactive_command_id = interactive_command_id
|
|
|
|
|
self.adapter = WinRMStreamAdapter.new(self.shell, interactive_command_id, method(:shell_ended))
|
2021-09-10 18:15:43 +10:00
|
|
|
super(adapter, opts)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def abort_foreground_supported
|
|
|
|
|
true
|
2021-09-10 17:45:08 +10:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def abort_foreground
|
2021-09-21 16:20:51 +10:00
|
|
|
shell.send_ctrl_c(interactive_command_id)
|
2021-09-10 18:15:43 +10:00
|
|
|
adapter.refresh_stdout
|
2021-09-10 17:45:08 +10:00
|
|
|
end
|
|
|
|
|
|
2021-09-21 16:20:51 +10:00
|
|
|
def shell_command(cmd, timeout = 5)
|
|
|
|
|
args = Shellwords.shellwords(cmd)
|
|
|
|
|
command = args.shift
|
|
|
|
|
shell.shell_command_synchronous(command, args, timeout)
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# The characters used to terminate a command in this shell
|
|
|
|
|
# (Breaks in 2012 without this)
|
2021-09-10 22:19:38 +10:00
|
|
|
def command_termination
|
|
|
|
|
"\r\n"
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-10 17:45:08 +10:00
|
|
|
##
|
|
|
|
|
# :category: Msf::Session::Interactive implementors
|
|
|
|
|
#
|
|
|
|
|
def _interact_stream
|
|
|
|
|
fds = [user_input.fd]
|
|
|
|
|
while interacting
|
|
|
|
|
sd = Rex::ThreadSafe.select(fds, nil, fds, 0.5)
|
|
|
|
|
begin
|
2021-09-10 22:19:38 +10:00
|
|
|
user_output.print(shell_read(-1, 0))
|
2021-09-10 17:45:08 +10:00
|
|
|
if sd
|
2021-09-10 22:19:38 +10:00
|
|
|
run_single((user_input.gets || '').chomp("\n"))
|
2021-09-10 17:45:08 +10:00
|
|
|
end
|
|
|
|
|
rescue WinRM::WinRMWSManFault => e
|
|
|
|
|
print_error(e.fault_description)
|
|
|
|
|
shell_close
|
|
|
|
|
end
|
|
|
|
|
Thread.pass
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def on_registered
|
|
|
|
|
adapter.start_keep_alive_loop(framework)
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-12 19:38:16 +10:00
|
|
|
# Callback used by the background thread to let us know the thread is done
|
2021-09-10 17:45:08 +10:00
|
|
|
def shell_ended(reason = '')
|
|
|
|
|
self.interacting = false
|
|
|
|
|
framework.events.on_session_interact_completed
|
|
|
|
|
framework.sessions.deregister(self, reason)
|
2021-09-02 16:58:07 +10:00
|
|
|
end
|
|
|
|
|
|
2021-09-10 15:13:30 +10:00
|
|
|
protected
|
|
|
|
|
|
2021-09-21 16:20:51 +10:00
|
|
|
attr_accessor :shell, :adapter, :interactive_command_id
|
2021-08-31 21:36:25 +10:00
|
|
|
|
2021-08-31 11:00:53 +10:00
|
|
|
end
|
|
|
|
|
end
|