Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

session_manager.rb 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. # -*- coding: binary -*-
  2. require 'thread'
  3. module Msf
  4. ###
  5. #
  6. # The purpose of the session manager is to keep track of sessions that are
  7. # created during the course of a framework instance's lifetime. When
  8. # exploits succeed, the payloads they use will create a session object,
  9. # where applicable, there will implement zero or more of the core
  10. # supplied interfaces for interacting with that session. For instance,
  11. # if the payload supports reading and writing from an executed process,
  12. # the session would implement SimpleCommandShell in a method that is
  13. # applicable to the way that the command interpreter is communicated
  14. # with.
  15. #
  16. ###
  17. class SessionManager < Hash
  18. include Framework::Offspring
  19. LAST_SEEN_INTERVAL = 60 * 2.5
  20. SCHEDULER_THREAD_COUNT = 5
  21. def initialize(framework)
  22. self.framework = framework
  23. self.sid_pool = 0
  24. self.mutex = Mutex.new
  25. self.scheduler_queue = ::Queue.new
  26. self.initialize_scheduler_threads
  27. self.monitor_thread = framework.threads.spawn("SessionManager", true) do
  28. last_seen_timer = Time.now.utc
  29. respawn_max = 30
  30. respawn_cnt = 0
  31. begin
  32. while true
  33. #
  34. # Process incoming data from all stream-based sessions and queue the
  35. # data into the associated ring buffers.
  36. #
  37. rings = values.select{|s| s.respond_to?(:ring) and s.ring and s.rstream }
  38. ready = ::IO.select(rings.map{|s| s.rstream}, nil, nil, 0.5) || [[],[],[]]
  39. ready[0].each do |fd|
  40. s = rings.select{|s| s.rstream == fd}.first
  41. next if not s
  42. begin
  43. buff = fd.get_once(-1)
  44. if buff
  45. # Store the data in the associated ring
  46. s.ring.store_data(buff)
  47. # Store the session event into the database.
  48. # Rescue anything the event handlers raise so they
  49. # don't break our session.
  50. framework.events.on_session_output(s, buff) rescue nil
  51. end
  52. rescue ::Exception => e
  53. wlog("Exception reading from Session #{s.sid}: #{e.class} #{e}")
  54. unless e.kind_of? EOFError
  55. # Don't bother with a call stack if it's just a
  56. # normal EOF
  57. dlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
  58. end
  59. # Flush any ring data in the queue
  60. s.ring.clear_data rescue nil
  61. # Shut down the socket itself
  62. s.rstream.close rescue nil
  63. # Deregister the session
  64. deregister(s, "Died from #{e.class}")
  65. end
  66. end
  67. #
  68. # TODO: Call the dispatch entry point of each Meterpreter thread instead of
  69. # dedicating specific processing threads to each session
  70. #
  71. #
  72. # Check for closed / dead / terminated sessions
  73. #
  74. values.each do |s|
  75. if not s.alive?
  76. deregister(s, "Died")
  77. wlog("Session #{s.sid} has died")
  78. next
  79. end
  80. end
  81. #
  82. # Mark all open session as alive every LAST_SEEN_INTERVAL
  83. #
  84. if (Time.now.utc - last_seen_timer) >= LAST_SEEN_INTERVAL
  85. # Update this timer BEFORE processing the session list, this will prevent
  86. # processing time for large session lists from skewing our update interval.
  87. last_seen_timer = Time.now.utc
  88. if framework.db.active
  89. ::ActiveRecord::Base.connection_pool.with_connection do
  90. values.each do |s|
  91. # Update the database entry on a regular basis, marking alive threads
  92. # as recently seen. This notifies other framework instances that this
  93. # session is being maintained.
  94. if s.db_record
  95. s.db_record.last_seen = Time.now.utc
  96. s.db_record.save
  97. end
  98. end
  99. end
  100. end
  101. end
  102. #
  103. # Skip the database cleanup code below if there is no database
  104. #
  105. next if not (framework.db and framework.db.active)
  106. #
  107. # Clean out any stale sessions that have been orphaned by a dead
  108. # framework instance.
  109. #
  110. ::ActiveRecord::Base.connection_pool.with_connection do |conn|
  111. ::Mdm::Session.where(closed_at: nil).each do |db_session|
  112. if db_session.last_seen.nil? or ((Time.now.utc - db_session.last_seen) > (2*LAST_SEEN_INTERVAL))
  113. db_session.closed_at = db_session.last_seen || Time.now.utc
  114. db_session.close_reason = "Orphaned"
  115. db_session.save
  116. end
  117. end
  118. end
  119. end
  120. #
  121. # All session management falls apart when any exception is raised to this point. Log it.
  122. #
  123. rescue ::Exception => e
  124. respawn_cnt += 1
  125. elog("Exception #{respawn_cnt}/#{respawn_max} in monitor thread #{e.class} #{e}")
  126. elog("Call stack: \n#{e.backtrace.join("\n")}")
  127. if respawn_cnt < respawn_max
  128. ::IO.select(nil, nil, nil, 10.0)
  129. retry
  130. end
  131. end
  132. end
  133. end
  134. #
  135. # Dedicated worker threads for pulling data out of new sessions
  136. #
  137. def initialize_scheduler_threads
  138. self.scheduler_threads = []
  139. 1.upto(SCHEDULER_THREAD_COUNT) do |i|
  140. self.scheduler_threads << framework.threads.spawn("SessionScheduler-#{i}", true) do
  141. while true
  142. item = self.scheduler_queue.pop
  143. begin
  144. item.call()
  145. rescue ::Exception => e
  146. wlog("Exception in scheduler thread #{e.class} #{e}")
  147. wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
  148. end
  149. end
  150. end
  151. end
  152. end
  153. #
  154. # Add a new task to the loader thread queue. Task is assumed to be
  155. # a Proc or another object that responds to call()
  156. #
  157. def schedule(task)
  158. self.scheduler_queue.push(task)
  159. end
  160. #
  161. # Enumerates the sorted list of keys.
  162. #
  163. def each_sorted(&block)
  164. self.keys.sort.each(&block)
  165. end
  166. #
  167. # Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+
  168. # "can't add a new key into hash during iteration"
  169. # This allows us to register new sessions while other threads are enumerating the
  170. # session list.
  171. #
  172. def each(&block)
  173. list = []
  174. self.keys.sort.each do |sidx|
  175. list << [sidx, self[sidx]]
  176. end
  177. list.each(&block)
  178. end
  179. #
  180. # Registers the supplied session object with the framework and returns
  181. # a unique session identifier to the caller.
  182. #
  183. def register(session)
  184. if (session.sid)
  185. wlog("registered session passed to register again (sid #{session.sid}).")
  186. return nil
  187. end
  188. next_sid = allocate_sid
  189. # Initialize the session's sid and framework instance pointer
  190. session.sid = next_sid
  191. session.framework = framework
  192. # Only register if the session allows for it
  193. if session.register?
  194. # Insert the session into the session hash table
  195. self[next_sid.to_i] = session
  196. # Notify the framework that we have a new session opening up...
  197. # Don't let errant event handlers kill our session
  198. begin
  199. framework.events.on_session_open(session)
  200. rescue ::Exception => e
  201. wlog("Exception in on_session_open event handler: #{e.class}: #{e}")
  202. wlog("Call Stack\n#{e.backtrace.join("\n")}")
  203. end
  204. if session.respond_to?("console")
  205. session.console.on_command_proc = Proc.new { |command, error| framework.events.on_session_command(session, command) }
  206. session.console.on_print_proc = Proc.new { |output| framework.events.on_session_output(session, output) }
  207. end
  208. end
  209. return next_sid
  210. end
  211. #
  212. # Deregisters the supplied session object with the framework.
  213. #
  214. def deregister(session, reason='')
  215. return if not session.register?
  216. if (session.dead? and not self[session.sid.to_i])
  217. return
  218. end
  219. # Tell the framework that we have a parting session
  220. framework.events.on_session_close(session, reason) rescue nil
  221. # If this session implements the comm interface, remove any routes
  222. # that have been created for it.
  223. if (session.kind_of?(Msf::Session::Comm))
  224. Rex::Socket::SwitchBoard.remove_by_comm(session)
  225. end
  226. # Remove it from the hash
  227. self.delete(session.sid.to_i)
  228. # Mark the session as dead
  229. session.alive = false
  230. # Close it down
  231. session.cleanup
  232. end
  233. #
  234. # Returns the session associated with the supplied sid, if any.
  235. #
  236. def get(sid)
  237. session = nil
  238. sid = sid.to_i
  239. if sid > 0
  240. session = self[sid]
  241. elsif sid == -1
  242. sid = self.keys.sort[-1]
  243. session = self[sid]
  244. end
  245. session
  246. end
  247. #
  248. # Allocates the next Session ID
  249. #
  250. def allocate_sid
  251. self.mutex.synchronize do
  252. self.sid_pool += 1
  253. end
  254. end
  255. protected
  256. attr_accessor :sid_pool, :sessions # :nodoc:
  257. attr_accessor :monitor_thread # :nodoc:
  258. attr_accessor :scheduler_threads # :nodoc:
  259. attr_accessor :scheduler_queue # :nodoc:
  260. attr_accessor :mutex # :nodoc:
  261. end
  262. end