Skip to content

Commit c8a8b6b

Browse files
authored
feat: add option to exclude raw rules upload (#52)
feat: add option to exclude raw rules upload
1 parent 62409bf commit c8a8b6b

2 files changed

Lines changed: 93 additions & 17 deletions

File tree

azul_plugin_yara/main.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class AzulPluginYara(BinaryPlugin):
4444
max_yara_include_depth=(int, 5),
4545
# Max number of yara streams to keep before dropping the rest.
4646
max_yara_hit_streams_to_keep=(Annotated[int, Field(gt=0, le=100)], 50),
47+
yara_upload_raw_rules_as_streams=(bool, True),
4748
)
4849

4950
FEATURES = [
@@ -175,23 +176,24 @@ def execute(self, job: Job):
175176
self.add_feature_values("yararule", rule)
176177
found_raw_rule[rule] = False
177178

178-
# Find the raw rule and save it as a file
179-
rule_file_path = self.namespace_to_rule_path[match.namespace]
180-
self.yara_include_depth = 0
181-
raw_rule = self.fetch_original_rule(rule_file_path, match.identifier, self.logger)
182-
if len(raw_rule) > 0:
183-
new_rule = md5(raw_rule).hexdigest() # noqa: S324
184-
# Rule was found
185-
found_raw_rule[rule] = True
186-
if new_rule not in seen_rules_md5s:
187-
seen_rules_md5s.append(new_rule)
188-
# Add the original yara rule that hit as an augmented stream. Stop at max allowed Augmented streams.
189-
if yara_rule_streams_added < self.cfg.max_yara_hit_streams_to_keep: # ty: ignore[unresolved-attribute] ty doesn't understand add_settings
190-
raw_rule_with_header = (
191-
f"// plugin: {self.NAME}, namespace_identifier: {rule}\n".encode() + raw_rule
192-
)
193-
self.add_data(label=DataLabel.YARA_RULE_HIT, tags={}, data=raw_rule_with_header)
194-
yara_rule_streams_added += 1
179+
if self.cfg.yara_upload_raw_rules_as_streams: # ty: ignore[unresolved-attribute] ty doesn't understand add_settings
180+
# Find the raw rule and save it as a file
181+
rule_file_path = self.namespace_to_rule_path[match.namespace]
182+
self.yara_include_depth = 0
183+
raw_rule = self.fetch_original_rule(rule_file_path, match.identifier, self.logger)
184+
if len(raw_rule) > 0:
185+
new_rule = md5(raw_rule).hexdigest() # noqa: S324
186+
# Rule was found
187+
found_raw_rule[rule] = True
188+
if new_rule not in seen_rules_md5s:
189+
seen_rules_md5s.append(new_rule)
190+
# Add the original yara rule that hit as an augmented stream. Stop at max allowed Augmented streams.
191+
if yara_rule_streams_added < self.cfg.max_yara_hit_streams_to_keep: # ty: ignore[unresolved-attribute] ty doesn't understand add_settings
192+
raw_rule_with_header = (
193+
f"// plugin: {self.NAME}, namespace_identifier: {rule}\n".encode() + raw_rule
194+
)
195+
self.add_data(label=DataLabel.YARA_RULE_HIT, tags={}, data=raw_rule_with_header)
196+
yara_rule_streams_added += 1
195197

196198
for match_data in match.patterns:
197199
var = match_data.identifier

tests/test_yara.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,80 @@ def test_yara_100_matching_rules(self):
386386
},
387387
)
388388

389+
def test_yara_no_rule_stream_upload(self):
390+
"""Ensure when there are a 100+ matching rules that ony the first 50 streams are kept.
391+
392+
This is because the binary ingestor can only process up to 100 streams before it rejects files anyway.
393+
"""
394+
with tempfile.TemporaryDirectory("-yara-test") as temp_rule_dir:
395+
for iteration in range(101):
396+
raw_rule = (
397+
"""rule Exploit_CVE_2015_0313_NewStream%d {
398+
meta:
399+
rule_group = "Exploit"
400+
401+
//required
402+
classification = "UNCLASSIFIED"
403+
description = "Looks for presence of code that could indicate ANGLER EK use of this flash vuln"
404+
exploit = "CVE-2015-0313"
405+
info = "SWF"
406+
organisation = "Defence"
407+
poc = "azul@asd.gov.au"
408+
rule_version = "1"
409+
yara_version = "1.6"
410+
411+
//optional
412+
weight = 51
413+
414+
strings:
415+
$ = "take_over_32("
416+
$ = "get_x86_shellcode("
417+
$ = "exploit_primordial_start("
418+
$ = "exploit_primarodial_finish("
419+
$ = "this.shellcodes.GetX86Shellcode("
420+
$ = "Shellcodes("
421+
$ = "attacking_buffer"
422+
$ = "take_over_buffer"
423+
$ = "make_spray_by_buffers_no_holes"
424+
$ = "fake_object_address"
425+
condition:
426+
any of them
427+
}"""
428+
% iteration
429+
)
430+
with open(os.path.join(temp_rule_dir, f"ruleVersion-{iteration}.yara"), "w+") as f:
431+
f.write(raw_rule)
432+
433+
result = self.do_execution(
434+
# This content should hit on the CVE-2015-0313 Angler EK rule in rules
435+
data_in=[("content", b'example -> "exploit_primarodial_finish(" <-')],
436+
config={
437+
"yara_rules_path": temp_rule_dir,
438+
"version_suffix": "0",
439+
"name_suffix": "0",
440+
"security_override": "OFFICIAL",
441+
},
442+
)
443+
self.assertEqual(len(result.data.keys()), 50, "Must be capping the returned yara hits to 50")
444+
445+
# If option is overridden the number of streams kept should match that.
446+
result = self.do_execution(
447+
# This content should hit on the CVE-2015-0313 Angler EK rule in rules
448+
data_in=[("content", b'example -> "exploit_primarodial_finish(" <-')],
449+
config={
450+
"yara_rules_path": temp_rule_dir,
451+
"version_suffix": "0",
452+
"name_suffix": "0",
453+
"security_override": "OFFICIAL",
454+
"yara_upload_raw_rules_as_streams": "False",
455+
},
456+
)
457+
self.assertEqual(
458+
len(result.data.keys()),
459+
0,
460+
f"Must not upload any rules as streams if yara_upload_raw_rules_as_streams==False",
461+
)
462+
389463
def test_yara_blacklist(self):
390464
"""Blacklist should filter the only rule."""
391465
path = os.path.join(os.path.dirname(__file__), rel_rules_dir)

0 commit comments

Comments
 (0)