Class: Msf::SessionManager

Inherits:
Hash
  • Object
show all
Includes:
Framework::Offspring
Defined in:
lib/msf/core/session_manager.rb

Overview

The purpose of the session manager is to keep track of sessions that are created during the course of a framework instance's lifetime. When exploits succeed, the payloads they use will create a session object, where applicable, there will implement zero or more of the core supplied interfaces for interacting with that session. For instance, if the payload supports reading and writing from an executed process, the session would implement SimpleCommandShell in a method that is applicable to the way that the command interpreter is communicated with.

Constant Summary collapse

LAST_SEEN_INTERVAL =
60 * 2.5
SCHEDULER_THREAD_COUNT =
5

Instance Attribute Summary collapse

Attributes included from Framework::Offspring

#framework

Instance Method Summary collapse

Constructor Details

#initialize(framework) ⇒ SessionManager

Returns a new instance of SessionManager.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/msf/core/session_manager.rb', line 26

def initialize(framework)
  self.framework = framework
  self.sid_pool  = 0
  self.mutex = Mutex.new
  self.scheduler_queue = ::Queue.new
  self.initialize_scheduler_threads

  self.monitor_thread = framework.threads.spawn("SessionManager", true) do
    last_seen_timer = Time.now.utc

    respawn_max = 30
    respawn_cnt = 0

    begin
    while true

      #
      # Process incoming data from all stream-based sessions and queue the
      # data into the associated ring buffers.
      #
      rings = values.select{|s| s.respond_to?(:ring) and s.ring and s.rstream }
      ready = ::IO.select(rings.map{|s| s.rstream}, nil, nil, 0.5) || [[],[],[]]

      ready[0].each do |fd|
        s = rings.select{|s| s.rstream == fd}.first
        next if not s

        begin
          buff = fd.get_once(-1)
          if buff
            # Store the data in the associated ring
            s.ring.store_data(buff)

            # Store the session event into the database.
            # Rescue anything the event handlers raise so they
            # don't break our session.
            framework.events.on_session_output(s, buff) rescue nil
          end
        rescue ::Exception => e
          wlog("Exception reading from Session #{s.sid}: #{e.class} #{e}")
          unless e.kind_of? EOFError
            # Don't bother with a call stack if it's just a
            # normal EOF
            dlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
          end

          # Flush any ring data in the queue
          s.ring.clear_data rescue nil

          # Shut down the socket itself
          s.rstream.close rescue nil

          # Deregister the session
          deregister(s, "Died from #{e.class}")
        end
      end


      #
      # TODO: Call the dispatch entry point of each Meterpreter thread instead of
      #       dedicating specific processing threads to each session
      #


      #
      # Check for closed / dead / terminated sessions
      #
      values.each do |s|
        if not s.alive?
          deregister(s, "Died")
          wlog("Session #{s.sid} has died")
          next
        end
      end

      #
      # Skip the database cleanup code below if there is no database
      #
      next unless framework.db && framework.db.active && framework.db.is_local?

      #
      # Mark all open session as alive every LAST_SEEN_INTERVAL
      #
      if (Time.now.utc - last_seen_timer) >= LAST_SEEN_INTERVAL

        # Update this timer BEFORE processing the session list, this will prevent
        # processing time for large session lists from skewing our update interval.

        last_seen_timer = Time.now.utc

        ::ApplicationRecord.connection_pool.with_connection do
          values.each do |s|
            # Update the database entry on a regular basis, marking alive threads
            # as recently seen.  This notifies other framework instances that this
            # session is being maintained.
            if s.db_record
              s.db_record = framework.db.update_session(id: s.db_record.id, last_seen: Time.now.utc)
            end
          end
        end
      end

      #
      # Clean out any stale sessions that have been orphaned by a dead
      # framework instance.
      #
      framework.db.remove_stale_sessions(LAST_SEEN_INTERVAL)

    end

    #
    # All session management falls apart when any exception is raised to this point. Log it.
    #
    rescue ::Exception => e
      respawn_cnt += 1
      elog("Exception #{respawn_cnt}/#{respawn_max} in monitor thread", error: e)
      if respawn_cnt < respawn_max
        ::IO.select(nil, nil, nil, 10.0)
        retry
      end
    end
  end
end

Instance Attribute Details

#monitor_threadObject (protected)

:nodoc:



289
290
291
# File 'lib/msf/core/session_manager.rb', line 289

def monitor_thread
  @monitor_thread
end

#mutexObject (protected)

:nodoc:



292
293
294
# File 'lib/msf/core/session_manager.rb', line 292

def mutex
  @mutex
end

#scheduler_queueObject (protected)

:nodoc:



291
292
293
# File 'lib/msf/core/session_manager.rb', line 291

def scheduler_queue
  @scheduler_queue
end

#scheduler_threadsObject (protected)

:nodoc:



290
291
292
# File 'lib/msf/core/session_manager.rb', line 290

def scheduler_threads
  @scheduler_threads
end

#sessionsObject (protected)

:nodoc:



288
289
290
# File 'lib/msf/core/session_manager.rb', line 288

def sessions
  @sessions
end

#sid_poolObject (protected)

:nodoc:



288
289
290
# File 'lib/msf/core/session_manager.rb', line 288

def sid_pool
  @sid_pool
end

Instance Method Details

#allocate_sidObject

Allocates the next Session ID



280
281
282
283
284
# File 'lib/msf/core/session_manager.rb', line 280

def allocate_sid
  self.mutex.synchronize do
    self.sid_pool += 1
  end
end

#deregister(session, reason = '') ⇒ Object

Deregisters the supplied session object with the framework.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/msf/core/session_manager.rb', line 235

def deregister(session, reason='')
  return if not session.register?

  if (session.dead? and not self[session.sid.to_i])
    return
  end

  # Tell the framework that we have a parting session
  framework.events.on_session_close(session, reason) rescue nil

  # If this session implements the comm interface, remove any routes
  # that have been created for it.
  if (session.kind_of?(Msf::Session::Comm))
    Rex::Socket::SwitchBoard.remove_by_comm(session)
  end

  # Remove it from the hash
  self.delete(session.sid.to_i)

  # Mark the session as dead
  session.alive = false

  # Close it down
  session.cleanup
end

#each(&block) ⇒ Object

Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+

"can't add a new key into hash during iteration"

This allows us to register new sessions while other threads are enumerating the session list.



191
192
193
194
195
196
197
# File 'lib/msf/core/session_manager.rb', line 191

def each(&block)
  list = []
  self.keys.sort.each do |sidx|
    list << [sidx, self[sidx]]
  end
  list.each(&block)
end

#each_sorted(&block) ⇒ Object

Enumerates the sorted list of keys.



181
182
183
# File 'lib/msf/core/session_manager.rb', line 181

def each_sorted(&block)
  self.keys.sort.each(&block)
end

#get(sid) ⇒ Object

Returns the session associated with the supplied sid, if any.



264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/msf/core/session_manager.rb', line 264

def get(sid)
  session = nil
  sid = sid.to_i

  if sid < 0
    session = self[self.keys.sort[sid]]
  elsif sid > 0
    session = self[sid]
  end

  session
end

#initialize_scheduler_threadsObject

Dedicated worker threads for pulling data out of new sessions



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/msf/core/session_manager.rb', line 153

def initialize_scheduler_threads
  self.scheduler_threads = []
  1.upto(SCHEDULER_THREAD_COUNT) do |i|
    self.scheduler_threads << framework.threads.spawn("SessionScheduler-#{i}", true) do
      while true
        item = self.scheduler_queue.pop
        begin
          item.call()
        rescue ::Exception => e
          wlog("Exception in scheduler thread #{e.class} #{e}")
          wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
        end
      end
    end
  end
end

#register(session) ⇒ Object

Registers the supplied session object with the framework and returns a unique session identifier to the caller.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/msf/core/session_manager.rb', line 203

def register(session)
  if (session.sid)
    wlog("registered session passed to register again (sid #{session.sid}).")
    return nil
  end

  next_sid = allocate_sid

  # Initialize the session's sid and framework instance pointer
  session.sid       = next_sid
  session.framework = framework

  # Only register if the session allows for it
  if session.register?
    # Insert the session into the session hash table
    self[next_sid.to_i] = session

    if session.respond_to?("console")
      session.console.on_command_proc = Proc.new { |command, error| framework.events.on_session_command(session, command) }
      session.console.on_print_proc = Proc.new { |output| framework.events.on_session_output(session, output) }
    end
    if session.respond_to?("on_registered")
      session.on_registered
    end
  end

  return next_sid
end

#schedule(task) ⇒ Object

Add a new task to the loader thread queue. Task is assumed to be a Proc or another object that responds to call()



174
175
176
# File 'lib/msf/core/session_manager.rb', line 174

def schedule(task)
  self.scheduler_queue.push(task)
end