Class: Msf::Sessions::WinrmCommandShell::WinRMStreamAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/base/sessions/winrm_command_shell.rb

Overview

Abstract WinRM to look like a stream so CommandShell can be happy

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(shell, interactive_command_id, on_shell_ended) ⇒ WinRMStreamAdapter

Returns a new instance of WinRMStreamAdapter.

Parameters:

  • shell (Net::MsfWinRM::StdinShell)

    Shell for talking to the WinRM service

  • on_shell_ended (Method)

    Callback for when the background thread notices the shell has ended



17
18
19
20
21
22
23
24
25
26
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 17

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

Instance Attribute Details

#interactive_command_idObject

rubocop:enable Lint/SuppressedException



156
157
158
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 156

def interactive_command_id
  @interactive_command_id
end

#keep_alive_threadObject

rubocop:enable Lint/SuppressedException



156
157
158
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 156

def keep_alive_thread
  @keep_alive_thread
end

#on_shell_endedObject

rubocop:enable Lint/SuppressedException



156
157
158
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 156

def on_shell_ended
  @on_shell_ended
end

#shellObject

rubocop:enable Lint/SuppressedException



156
157
158
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 156

def shell
  @shell
end

Instance Method Details

#_get_once(length) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 74

def _get_once(length)
  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
  end
  result
end

#closeObject

Close the shell; cleanly terminating it on the server if possible

The shell may already be dead, or unreachable at this point, so do a best effort, and capture exceptions rubocop:disable Lint/SuppressedException



149
150
151
152
153
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 149

def close
  stop_keep_alive_loop
  shell.cleanup_command(interactive_command_id)
rescue WinRM::WinRMWSManFault
end

#get_once(length = -1,, timeout = 1) ⇒ Object

:category: Msf::Session::Provider::SingleCommandShell implementors

Read from the command shell.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 51

def get_once(length = -1, timeout = 1)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
  result = ''
  loop do
    result = _get_once(length)
    time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
    elapsed = time - start_time
    time_remaining = timeout - elapsed
    break if (result != '' || time_remaining <= 0)

    # rubocop:disable Lint/SuppressedException
    begin
      # We didn't receive anything - let's wait for some more
      @received_stdout_event.wait(time_remaining)
    rescue ::Timeout::Error
    end
    # rubocop:enable Lint/SuppressedException
    # If we didn't get anything, let's hurry the background thread along
    refresh_stdout unless result
  end
  result
end

#localinfoObject



32
33
34
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 32

def localinfo
  shell.transport.localinfo
end

#peerinfoObject



28
29
30
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 28

def peerinfo
  shell.transport.peerinfo
end

#refresh_stdoutObject

Trigger the background thread to go get more stdout



37
38
39
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 37

def refresh_stdout
  @check_stdin_event.set
end

#start_keep_alive_loop(framework) ⇒ Object

Start a background thread for regularly checking for stdout



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 90

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
      shell.read_stdout(interactive_command_id) do |stdout, stderr|
        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)
      end

      # If our last request received stdout, let's be ready for some more
      if output_seen
        @received_stdout_event.set
        loop_delay = 0.5
      else
        # Gradual backoff
        loop_delay *= 4
        loop_delay = [loop_delay, 30].min
      end

      # Wait loop_delay seconds, or until an interactive thread wakes us up
      begin
        @check_stdin_event.wait(loop_delay)
        # rubocop:disable Lint/SuppressedException
      rescue ::Timeout::Error
      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)
    end
  end
end

#stop_keep_alive_loopObject

Stop the background thread



140
141
142
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 140

def stop_keep_alive_loop
  keep_alive_thread.kill
end

#write(buf) ⇒ Object



41
42
43
44
# File 'lib/msf/base/sessions/winrm_command_shell.rb', line 41

def write(buf)
  shell.send_stdin(buf, interactive_command_id)
  refresh_stdout
end