Skip to content

Commit b308d8c

Browse files
committed
AutorunEngine: RuleLoader: Skip enabled rule file if rule is already in database
1 parent 4555b55 commit b308d8c

File tree

2 files changed

+119
-89
lines changed

2 files changed

+119
-89
lines changed

core/main/autorun_engine/parser.rb

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,68 +18,63 @@ def initialize
1818
VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP']
1919
CHAIN_MODE = %w[sequential nested-forward]
2020
MAX_VER_LEN = 15
21-
# Parse a JSON ARE file and returns an Hash with the value mappings
22-
def parse(name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode)
23-
success = [true]
2421

25-
return [false, 'Illegal chain_mode definition'] unless CHAIN_MODE.include?(chain_mode)
26-
return [false, 'Illegal rule name'] unless BeEF::Filters.is_non_empty_string?(name)
27-
return [false, 'Illegal author name'] unless BeEF::Filters.is_non_empty_string?(author)
22+
def parse(name, author, browser, browser_version, os, os_version, modules, execution_order, execution_delay, chain_mode)
23+
raise ArgumentError, "Invalid rule name: #{name}" unless BeEF::Filters.is_non_empty_string?(name)
24+
raise ArgumentError, "Invalid author name: #{author}" unless BeEF::Filters.is_non_empty_string?(author)
25+
raise ArgumentError, "Invalid chain_mode definition: #{chain_mode}" unless CHAIN_MODE.include?(chain_mode)
26+
raise ArgumentError, "Invalid os definition: #{os}" unless OS.include?(os)
27+
28+
unless modules.size == execution_delay.size
29+
raise ArgumentError, "Number of execution_delay values (#{execution_delay.size}) must be consistent with number of modules (#{modules.size})"
30+
end
31+
execution_delay.each { |delay| raise TypeError, "Invalid execution_delay value: #{delay}. Values must be Integers." unless delay.is_a?(Integer) }
32+
33+
unless modules.size == execution_order.size
34+
raise ArgumentError, "Number of execution_order values (#{execution_order.size}) must be consistent with number of modules (#{modules.size})"
35+
end
36+
execution_order.each { |order| raise TypeError, "Invalid execution_order value: #{order}. Values must be Integers." unless order.is_a?(Integer) }
2837

2938
# if multiple browsers were specified, check each browser
3039
if browser.is_a?(Array)
3140
browser.each do |b|
32-
return [false, 'Illegal browser definition'] unless BROWSER.include?(b)
41+
raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(b)
3342
end
3443
# else, if only one browser was specified, check browser and browser version
3544
else
36-
return [false, 'Illegal browser definition'] unless BROWSER.include?(browser)
45+
raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(browser)
3746

3847
if browser_version != 'ALL' && !(VERSION.include?(browser_version[0, 2].gsub(/\s+/, '')) &&
3948
BeEF::Filters.is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/, '')) && browser_version.length < MAX_VER_LEN)
40-
return [false, 'Illegal browser_version definition']
49+
raise ArgumentError, "Invalid browser_version definition: #{browser_version}"
4150
end
4251
end
4352

4453
if os_version != 'ALL' && !(VERSION.include?(os_version[0, 2].gsub(/\s+/, '')) &&
4554
BeEF::Filters.is_valid_osversion?(os_version[2..-1].gsub(/\s+/, '')) && os_version.length < MAX_VER_LEN)
46-
return [false, 'Illegal os_version definition']
55+
return ArgumentError, "Invalid os_version definition: #{os_version}"
4756
end
4857

49-
return [false, 'Illegal os definition'] unless OS.include?(os)
50-
5158
# check if module names, conditions and options are ok
5259
modules.each do |cmd_mod|
5360
mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first
54-
if mod.nil?
55-
return [false, "The specified module name (#{cmd_mod['name']}) does not exist"]
56-
else
57-
modk = BeEF::Module.get_key_by_database_id(mod.id)
58-
mod_options = BeEF::Module.get_options(modk)
5961

60-
opt_count = 0
61-
mod_options.each do |opt|
62-
if opt['name'] == cmd_mod['options'].keys[opt_count]
63-
opt_count += 1
64-
else
65-
return [false, "The specified option (#{cmd_mod['options'].keys[opt_count]
66-
}) for module (#{cmd_mod['name']}) does not exist"]
67-
end
62+
raise "The specified module name (#{cmd_mod['name']}) does not exist" if mod.nil?
63+
64+
modk = BeEF::Module.get_key_by_database_id(mod.id)
65+
mod_options = BeEF::Module.get_options(modk)
66+
67+
opt_count = 0
68+
mod_options.each do |opt|
69+
if opt['name'] != cmd_mod['options'].keys[opt_count]
70+
raise ArgumentError, "The specified option (#{cmd_mod['options'].keys[opt_count]}) for module (#{cmd_mod['name']}) was not specified"
6871
end
72+
73+
opt_count += 1
6974
end
7075
end
7176

72-
exec_order.each { |order| return [false, 'execution_order values must be Integers'] unless order.is_a?(Integer) }
73-
exec_delay.each { |delay| return [false, 'execution_delay values must be Integers'] unless delay.is_a?(Integer) }
74-
75-
return [false, 'execution_order and execution_delay values must be consistent with modules numbers'] unless
76-
modules.size == exec_order.size && modules.size == exec_delay.size
77-
78-
success
79-
rescue StandardError => e
80-
print_error e.message.to_s
81-
print_debug e.backtrace.join("\n").to_s
82-
[false, 'Something went wrong.']
77+
true
8378
end
8479
end
8580
end

core/main/autorun_engine/rule_loader.rb

Lines changed: 88 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,76 +14,111 @@ def initialize
1414
@debug_on = @config.get('beef.debug')
1515
end
1616

17-
# this expects parsed JSON as input
18-
def load(data)
19-
name = data['name']
20-
author = data['author']
17+
# Load an ARE rule set
18+
# @param [Hash] ARE ruleset as JSON
19+
# @return [Hash] {"success": Boolean, "rule_id": Integer, "error": String}
20+
def load_rule_json(data)
21+
name = data['name'] || ''
22+
author = data['author'] || ''
2123
browser = data['browser'] || 'ALL'
2224
browser_version = data['browser_version'] || 'ALL'
2325
os = data['os'] || 'ALL'
2426
os_version = data['os_version'] || 'ALL'
2527
modules = data['modules']
26-
exec_order = data['execution_order']
27-
exec_delay = data['execution_delay']
28-
chain_mode = data['chain_mode']
28+
execution_order = data['execution_order']
29+
execution_delay = data['execution_delay']
30+
chain_mode = data['chain_mode'] || 'sequential'
2931

30-
parser_result = BeEF::Core::AutorunEngine::Parser.instance.parse(
31-
name, author, browser, browser_version, os, os_version, modules, exec_order, exec_delay, chain_mode
32+
begin
33+
BeEF::Core::AutorunEngine::Parser.instance.parse(
34+
name,
35+
author,
36+
browser,
37+
browser_version,
38+
os,
39+
os_version,
40+
modules,
41+
execution_order,
42+
execution_delay,
43+
chain_mode
44+
)
45+
rescue => e
46+
print_error("[ARE] Error loading ruleset (#{name}): #{e.message}")
47+
return { 'success' => false, 'error' => e.message }
48+
end
49+
50+
existing_rule = BeEF::Core::Models::Rule.where(
51+
name: name,
52+
author: author,
53+
browser: browser,
54+
browser_version: browser_version,
55+
os: os,
56+
os_version: os_version,
57+
modules: modules.to_json,
58+
execution_order: execution_order.to_s,
59+
execution_delay: execution_delay.to_s,
60+
chain_mode: chain_mode
61+
).first
62+
63+
unless existing_rule.nil?
64+
msg = "Duplicate rule already exists in the database (ID: #{existing_rule.id})"
65+
print_info("[ARE] Skipping ruleset (#{name}): #{msg}")
66+
return { 'success' => false, 'error' => msg }
67+
end
68+
69+
are_rule = BeEF::Core::Models::Rule.new(
70+
name: name,
71+
author: author,
72+
browser: browser,
73+
browser_version: browser_version,
74+
os: os,
75+
os_version: os_version,
76+
modules: modules.to_json,
77+
execution_order: execution_order.to_s,
78+
execution_delay: execution_delay.to_s,
79+
chain_mode: chain_mode
3280
)
81+
are_rule.save
82+
83+
print_info("[ARE] Ruleset (#{name}) parsed and stored successfully.")
3384

34-
if parser_result.length == 1 && parser_result.first
35-
print_info "[ARE] Ruleset (#{name}) parsed and stored successfully."
36-
if @debug_on
37-
print_more "Target Browser: #{browser} (#{browser_version})"
38-
print_more "Target OS: #{os} (#{os_version})"
39-
print_more 'Modules to Trigger:'
40-
modules.each do |mod|
41-
print_more "(*) Name: #{mod['name']}"
42-
print_more "(*) Condition: #{mod['condition']}"
43-
print_more "(*) Code: #{mod['code']}"
44-
print_more '(*) Options:'
45-
mod['options'].each do |key, value|
46-
print_more "\t#{key}: (#{value})"
47-
end
85+
if @debug_on
86+
print_more "Target Browser: #{browser} (#{browser_version})"
87+
print_more "Target OS: #{os} (#{os_version})"
88+
print_more 'Modules to run:'
89+
modules.each do |mod|
90+
print_more "(*) Name: #{mod['name']}"
91+
print_more "(*) Condition: #{mod['condition']}"
92+
print_more "(*) Code: #{mod['code']}"
93+
print_more '(*) Options:'
94+
mod['options'].each do |key, value|
95+
print_more "\t#{key}: (#{value})"
4896
end
49-
print_more "Exec order: #{exec_order}"
50-
print_more "Exec delay: #{exec_delay}"
5197
end
52-
are_rule = BeEF::Core::Models::Rule.new(
53-
name: name,
54-
author: author,
55-
browser: browser,
56-
browser_version: browser_version,
57-
os: os,
58-
os_version: os_version,
59-
modules: modules.to_json,
60-
execution_order: exec_order,
61-
execution_delay: exec_delay,
62-
chain_mode: chain_mode
63-
)
64-
are_rule.save
65-
{ 'success' => true, 'rule_id' => are_rule.id }
66-
else
67-
print_error "[ARE] Ruleset (#{name}): ERROR. " + parser_result.last
68-
{ 'success' => false, 'error' => parser_result.last }
98+
print_more "Exec order: #{execution_order}"
99+
print_more "Exec delay: #{exec_delay}"
69100
end
70-
rescue StandardError => e
71-
err = 'Malformed JSON ruleset.'
72-
print_error "[ARE] Ruleset (#{name}): ERROR. #{e} #{e.backtrace}"
73-
{ 'success' => false, 'error' => err }
101+
102+
{ 'success' => true, 'rule_id' => are_rule.id }
103+
rescue TypeError, ArgumentError => e
104+
print_error("[ARE] Failed to load ruleset (#{name}): #{e.message}")
105+
{ 'success' => false, 'error' => e.message }
74106
end
75107

76-
def load_file(json_rule_path)
108+
# Load an ARE ruleset from file
109+
# @param [String] JSON ARE ruleset file path
110+
def load_rule_file(json_rule_path)
77111
rule_file = File.open(json_rule_path, 'r:UTF-8', &:read)
78-
self.load JSON.parse(rule_file)
79-
rescue StandardError => e
80-
print_error "[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}"
112+
self.load_rule_json(JSON.parse(rule_file))
113+
rescue => e
114+
print_error("[ARE] Failed to load ruleset from #{json_rule_path}: #{e.message}")
81115
end
82116

117+
# Load all JSON ARE rule files from arerules/enabled/ directory
83118
def load_directory
84-
Dir.glob("#{$root_dir}/arerules/enabled/**/*.json") do |rule|
85-
print_debug "[ARE] Processing rule: #{rule}"
86-
load_file rule
119+
Dir.glob("#{$root_dir}/arerules/enabled/**/*.json") do |rule_file|
120+
print_debug("[ARE] Processing ruleset file: #{rule_file}")
121+
load_rule_file(rule_file)
87122
end
88123
end
89124
end

0 commit comments

Comments
 (0)