Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/core/module.rb
1146 views
1
#
2
# Copyright (c) 2006-2025 Wade Alcorn - [email protected]
3
# Browser Exploitation Framework (BeEF) - https://beefproject.com
4
# See the file 'doc/COPYING' for copying permission
5
#
6
module BeEF
7
module Module
8
# Checks to see if module key is in configuration
9
# @param [String] mod module key
10
# @return [Boolean] if the module key exists in BeEF's configuration
11
def self.is_present(mod)
12
BeEF::Core::Configuration.instance.get('beef.module').key? mod.to_s
13
end
14
15
# Checks to see if module is enabled in configuration
16
# @param [String] mod module key
17
# @return [Boolean] if the module key is enabled in BeEF's configuration
18
def self.is_enabled(mod)
19
(is_present(mod) && BeEF::Core::Configuration.instance.get("beef.module.#{mod}.enable") == true)
20
end
21
22
# Checks to see if the module reports that it has loaded through the configuration
23
# @param [String] mod module key
24
# @return [Boolean] if the module key is loaded in BeEF's configuration
25
def self.is_loaded(mod)
26
(is_enabled(mod) && BeEF::Core::Configuration.instance.get("beef.module.#{mod}.loaded") == true)
27
end
28
29
# Returns module class definition
30
# @param [String] mod module key
31
# @return [Class] the module class
32
def self.get_definition(mod)
33
BeEF::Core::Command.const_get(BeEF::Core::Configuration.instance.get("beef.module.#{mod}.class"))
34
end
35
36
# Gets all module options
37
# @param [String] mod module key
38
# @return [Hash] a hash of all the module options
39
# @note API Fire: get_options
40
def self.get_options(mod)
41
if BeEF::API::Registrar.instance.matched? BeEF::API::Module, 'get_options', [mod]
42
options = BeEF::API::Registrar.instance.fire BeEF::API::Module, 'get_options', mod
43
mo = []
44
options.each do |o|
45
unless o[:data].is_a?(Array)
46
print_debug 'API Warning: return result for BeEF::Module.get_options() was not an array.'
47
next
48
end
49
mo += o[:data]
50
end
51
return mo
52
end
53
54
unless check_hard_load mod
55
print_debug "get_opts called on unloaded module '#{mod}'"
56
return []
57
end
58
59
class_name = BeEF::Core::Configuration.instance.get "beef.module.#{mod}.class"
60
class_symbol = BeEF::Core::Command.const_get class_name
61
62
return [] unless class_symbol && class_symbol.respond_to?(:options)
63
64
class_symbol.options
65
end
66
67
# Gets all module payload options
68
# @param [String] mod module key
69
# @return [Hash] a hash of all the module options
70
# @note API Fire: get_options
71
def self.get_payload_options(mod, payload)
72
return [] unless BeEF::API::Registrar.instance.matched?(BeEF::API::Module, 'get_payload_options', [mod, nil])
73
74
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'get_payload_options', mod, payload)
75
end
76
77
# Soft loads a module
78
# @note A soft load consists of only loading the modules configuration (ie not the module.rb)
79
# @param [String] mod module key
80
# @return [Boolean] whether or not the soft load process was successful
81
# @note API Fire: pre_soft_load
82
# @note API Fire: post_soft_load
83
def self.soft_load(mod)
84
# API call for pre-soft-load module
85
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'pre_soft_load', mod)
86
config = BeEF::Core::Configuration.instance
87
88
mod_str = "beef.module.#{mod}"
89
if config.get("#{mod_str}.loaded")
90
print_error "Unable to load module '#{mod}'"
91
return false
92
end
93
94
mod_path = "#{$root_dir}/#{config.get("#{mod_str}.path")}/module.rb"
95
unless File.exist? mod_path
96
print_debug "Unable to locate module file: #{mod_path}"
97
return false
98
end
99
100
BeEF::Core::Configuration.instance.set("#{mod_str}.class", mod.capitalize)
101
parse_targets mod
102
print_debug "Soft Load module: '#{mod}'"
103
104
# API call for post-soft-load module
105
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_soft_load', mod)
106
true
107
rescue StandardError => e
108
print_error "There was a problem soft loading the module '#{mod}': #{e.message}"
109
false
110
end
111
112
# Hard loads a module
113
# @note A hard load consists of loading a pre-soft-loaded module by requiring the module.rb
114
# @param [String] mod module key
115
# @return [Boolean] whether or not the hard load was successful
116
# @note API Fire: pre_hard_load
117
# @note API Fire: post_hard_load
118
def self.hard_load(mod)
119
# API call for pre-hard-load module
120
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'pre_hard_load', mod)
121
config = BeEF::Core::Configuration.instance
122
123
unless is_enabled mod
124
print_error "Hard load attempted on module '#{mod}' that is not enabled."
125
return false
126
end
127
128
mod_str = "beef.module.#{mod}"
129
mod_path = "#{config.get("#{mod_str}.path")}/module.rb"
130
require mod_path
131
132
unless exists? config.get("#{mod_str}.class")
133
print_error "Hard loaded module '#{mod}' but the class BeEF::Core::Commands::#{mod.capitalize} does not exist"
134
return false
135
end
136
137
# start server mount point
138
BeEF::Core::Server.instance.mount("/command/#{mod}.js", BeEF::Core::Handlers::Commands, mod)
139
BeEF::Core::Configuration.instance.set("#{mod_str}.mount", "/command/#{mod}.js")
140
BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", true)
141
print_debug "Hard Load module: '#{mod}'"
142
143
# API call for post-hard-load module
144
BeEF::API::Registrar.instance.fire(BeEF::API::Module, 'post_hard_load', mod)
145
true
146
rescue StandardError => e
147
BeEF::Core::Configuration.instance.set("#{mod_str}.loaded", false)
148
print_error "There was a problem loading the module '#{mod}'"
149
print_debug "Hard load module syntax error: #{e}"
150
false
151
end
152
153
# Checks to see if a module has been hard loaded, if not a hard load is attempted
154
# @param [String] mod module key
155
# @return [Boolean] if already hard loaded then true otherwise (see #hard_load)
156
def self.check_hard_load(mod)
157
return true if is_loaded mod
158
159
hard_load mod
160
end
161
162
# Get module key by database ID
163
# @param [Integer] id module database ID
164
# @return [String] module key
165
def self.get_key_by_database_id(id)
166
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v|
167
v.key?('db') && v['db']['id'].to_i == id.to_i
168
end
169
ret.is_a?(Array) ? ret.first.first : ret.keys.first
170
end
171
172
# Get module key by module class
173
# @param [Class] c module class
174
# @return [String] module key
175
def self.get_key_by_class(c)
176
ret = BeEF::Core::Configuration.instance.get('beef.module').select do |_k, v|
177
v.key?('class') && v['class'].to_s.eql?(c.to_s)
178
end
179
ret.is_a?(Array) ? ret.first.first : ret.keys.first
180
end
181
182
# Checks to see if module class exists
183
# @param [String] mod module key
184
# @return [Boolean] returns whether or not the class exists
185
def self.exists?(mod)
186
kclass = BeEF::Core::Command.const_get mod.capitalize
187
kclass.is_a? Class
188
rescue NameError
189
false
190
end
191
192
# Checks target configuration to see if browser / version / operating system is supported
193
# @param [String] mod module key
194
# @param [Hash] opts hash of module support information
195
# @return [Constant, nil] returns a resulting defined constant BeEF::Core::Constants::CommandModule::*
196
# @note Support uses a rating system to provide the most accurate results.
197
# 1 = All match. ie: All was defined.
198
# 2 = String match. ie: Firefox was defined as working.
199
# 3 = Hash match. ie: Firefox defined with 1 additional parameter (eg max_ver).
200
# 4+ = As above but with extra parameters.
201
# Please note this rating system has no correlation to the return constant value BeEF::Core::Constants::CommandModule::*
202
def self.support(mod, opts)
203
target_config = BeEF::Core::Configuration.instance.get("beef.module.#{mod}.target")
204
return nil unless target_config
205
return nil unless opts.is_a? Hash
206
207
unless opts.key? 'browser'
208
print_error 'BeEF::Module.support() was passed a hash without a valid browser constant'
209
return nil
210
end
211
212
results = []
213
target_config.each do |k, m|
214
m.each do |v|
215
case v
216
when String
217
if opts['browser'] == v
218
# if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
219
# rating += 1
220
# end
221
results << { 'rating' => 2, 'const' => k }
222
end
223
when Hash
224
break if opts['browser'] != v.keys.first && v.keys.first != BeEF::Core::Constants::Browsers::ALL
225
226
subv = v[v.keys.first]
227
rating = 1
228
229
# version check
230
if opts.key?('ver')
231
if subv.key?('min_ver')
232
break unless subv['min_ver'].is_a?(Integer) && opts['ver'].to_i >= subv['min_ver']
233
rating += 1
234
end
235
if subv.key?('max_ver')
236
break unless (subv['max_ver'].is_a?(Integer) && opts['ver'].to_i <= subv['max_ver']) || subv['max_ver'] == 'latest'
237
rating += 1
238
end
239
end
240
241
# os check
242
if opts.key?('os') && subv.key?('os')
243
match = false
244
opts['os'].each do |o|
245
case subv['os']
246
when String
247
if o == subv['os']
248
rating += 1
249
match = true
250
elsif subv['os'].eql? BeEF::Core::Constants::Os::OS_ALL_UA_STR
251
match = true
252
end
253
when Array
254
subv['os'].each do |p|
255
if o == p
256
rating += 1
257
match = true
258
elsif p.eql? BeEF::Core::Constants::Os::OS_ALL_UA_STR
259
match = true
260
end
261
end
262
end
263
end
264
break unless match
265
end
266
267
if rating.positive?
268
# if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
269
# rating += 1
270
# end
271
results << { 'rating' => rating, 'const' => k }
272
end
273
end
274
275
next unless v.eql? BeEF::Core::Constants::Browsers::ALL
276
277
rating = 1
278
rating = 1 if k == BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
279
280
results << { 'rating' => rating, 'const' => k }
281
end
282
end
283
284
return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN unless results.count.positive?
285
286
result = {}
287
results.each do |r|
288
result = { 'rating' => r['rating'], 'const' => r['const'] } if result == {} || r['rating'] > result['rating']
289
end
290
291
result['const']
292
end
293
294
# Translates module target configuration
295
# @note Takes the user defined target configuration and replaces it with equivalent a constant based generated version
296
# @param [String] mod module key
297
def self.parse_targets(mod)
298
mod_str = "beef.module.#{mod}"
299
target_config = BeEF::Core::Configuration.instance.get("#{mod_str}.target")
300
return unless target_config
301
302
targets = {}
303
target_config.each do |k, v|
304
# Convert the key to a string if it's not already one
305
k_str = k.to_s.upcase
306
307
# Use the adjusted string key for the rest of the process
308
next unless BeEF::Core::Constants::CommandModule.const_defined? "VERIFIED_#{k_str}"
309
310
key = BeEF::Core::Constants::CommandModule.const_get "VERIFIED_#{k_str}"
311
targets[key] = [] unless targets.key? key
312
browser = nil
313
314
case v
315
when String
316
browser = match_target_browser v
317
targets[key] << browser if browser
318
when Array
319
v.each do |c|
320
browser = match_target_browser c
321
targets[key] << browser if browser
322
end
323
when Hash
324
v.each do |k, c|
325
browser = match_target_browser k
326
next unless browser
327
328
case c
329
when TrueClass
330
targets[key] << browser
331
when Hash
332
details = match_target_browser_spec c
333
targets[key] << { browser => details } if details
334
end
335
end
336
end
337
rescue NameError
338
print_error "Module '#{mod}' configuration has invalid target status defined '#{k}'"
339
end
340
341
BeEF::Core::Configuration.instance.clear "#{mod_str}.target"
342
BeEF::Core::Configuration.instance.set "#{mod_str}.target", targets
343
end
344
345
# Translates simple browser target configuration
346
# @note Takes a user defined browser type and translates it into a BeEF constant
347
# @param [String] v user defined browser
348
# @return [Constant] a BeEF browser constant
349
def self.match_target_browser(v)
350
unless v.instance_of?(String)
351
print_error 'Invalid datatype passed to BeEF::Module.match_target_browser()'
352
return false
353
end
354
355
return false unless BeEF::Core::Constants::Browsers.const_defined? v.upcase
356
357
BeEF::Core::Constants::Browsers.const_get v.upcase
358
rescue NameError
359
print_error "Could not identify browser target specified as '#{v}'"
360
false
361
end
362
363
# Translates complex browser target configuration
364
# @note Takes a complex user defined browser hash and converts it to applicable BeEF constants
365
# @param [Hash] v user defined browser hash
366
# @return [Hash] BeEF constants hash
367
def self.match_target_browser_spec(v)
368
unless v.instance_of?(Hash)
369
print_error 'Invalid datatype passed to BeEF::Module.match_target_browser_spec()'
370
return {}
371
end
372
373
browser = {}
374
browser['max_ver'] = v['max_ver'] if v.key?('max_ver') && (v['max_ver'].is_a?(Integer) || v['max_ver'].is_a?(Float) || v['max_ver'] == 'latest')
375
376
browser['min_ver'] = v['min_ver'] if v.key?('min_ver') && (v['min_ver'].is_a?(Integer) || v['min_ver'].is_a?(Float))
377
378
return browser unless v.key?('os')
379
380
case v['os']
381
when String
382
os = match_target_os v['os']
383
browser['os'] = os if os
384
when Array
385
browser['os'] = []
386
v['os'].each do |c|
387
os = match_target_os c
388
browser['os'] << os if os
389
end
390
end
391
392
browser
393
end
394
395
# Translates simple OS target configuration
396
# @note Takes user defined OS specification and translates it into BeEF constants
397
# @param [String] v user defined OS string
398
# @return [Constant] BeEF OS Constant
399
def self.match_target_os(v)
400
unless v.instance_of?(String)
401
print_error 'Invalid datatype passed to BeEF::Module.match_target_os()'
402
return false
403
end
404
405
return false unless BeEF::Core::Constants::Os.const_defined? "OS_#{v.upcase}_UA_STR"
406
407
BeEF::Core::Constants::Os.const_get "OS_#{v.upcase}_UA_STR"
408
rescue NameError
409
print_error "Could not identify OS target specified as '#{v}'"
410
false
411
end
412
413
# Executes a module
414
# @param [String] mod module key
415
# @param [String] hbsession hooked browser session
416
# @param [Array] opts array of module execute options (see #get_options)
417
# @return [Integer] the command_id associated to the module execution when info is persisted. nil if there are errors.
418
# @note The return value of this function does not specify if the module was successful, only that it was executed within the framework
419
def self.execute(mod, hbsession, opts = [])
420
unless is_present(mod) && is_enabled(mod)
421
print_error "Module not found '#{mod}'. Failed to execute module."
422
return nil
423
end
424
425
if BeEF::API::Registrar.instance.matched? BeEF::API::Module, 'override_execute', [mod, nil, nil]
426
BeEF::API::Registrar.instance.fire BeEF::API::Module, 'override_execute', mod, hbsession, opts
427
# @note We return not_nil by default as we cannot determine the correct status if multiple API hooks have been called
428
# @note using metasploit, we cannot know if the module execution was successful or not
429
return 'not_available'
430
end
431
432
hb = BeEF::HBManager.get_by_session hbsession
433
unless hb
434
print_error "Could not find hooked browser when attempting to execute module '#{mod}'"
435
return nil
436
end
437
438
check_hard_load mod
439
command_module = get_definition(mod).new(mod)
440
command_module.pre_execute if command_module.respond_to?(:pre_execute)
441
merge_options(mod, [])
442
c = BeEF::Core::Models::Command.create(
443
data: merge_options(mod, opts).to_json,
444
hooked_browser_id: hb.id,
445
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"),
446
creationdate: Time.new.to_i
447
)
448
c.id
449
end
450
451
# Merges default module options with array of custom options
452
# @param [String] mod module key
453
# @param [Hash] opts module options customised by user input
454
# @return [Hash, nil] returns merged options
455
def self.merge_options(mod, opts)
456
return nil unless is_present mod
457
458
check_hard_load mod
459
merged = []
460
defaults = get_options mod
461
defaults.each do |v|
462
mer = nil
463
opts.each do |o|
464
mer = v.deep_merge o if v.key?('name') && o.key?('name') && v['name'] == o['name']
465
end
466
467
mer.nil? ? merged.push(v) : merged.push(mer)
468
end
469
470
merged
471
end
472
end
473
end
474
475