Skip to content

Commit d58ef4a

Browse files
authored
fix: yara ingest fails on too many streams. (#36)
fix: yara ingest fails on too many streams. --------- Co-authored-by: dan-acsc <dan-acsc@users.noreply.github.com>
1 parent fcd8eb8 commit d58ef4a

3 files changed

Lines changed: 359 additions & 264 deletions

File tree

azul_plugin_yara/main.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
from hashlib import md5
88
from pathlib import Path
9-
from typing import Any, Optional
9+
from typing import Annotated, Any, Optional
1010

1111
import yara_x
1212
from azul_runner import (
@@ -21,6 +21,7 @@
2121
cmdline_run,
2222
settings,
2323
)
24+
from pydantic import Field
2425

2526
YARA_EXTENSIONS = [".yar", ".yara"]
2627

@@ -41,6 +42,8 @@ class AzulPluginYara(BinaryPlugin):
4142
size_before_disk=(int, 2**24),
4243
# Max number of yara includes to follow before giving up on looking for rules.
4344
max_yara_include_depth=(int, 5),
45+
# Max number of yara streams to keep before dropping the rest.
46+
max_yara_hit_streams_to_keep=(Annotated[int, Field(gt=0, le=100)], 50),
4447
)
4548

4649
FEATURES = [
@@ -161,6 +164,8 @@ def execute(self, job: Job):
161164
# Read file from disk as multiple seek/read operations will be required
162165
fpath = job.get_data().get_filepath()
163166
found_raw_rule = dict()
167+
168+
yara_rule_streams_added = 0
164169
for match in matches.matching_rules:
165170
rule = match.namespace + "." + match.identifier
166171
self.add_feature_values("yararule", rule)
@@ -176,10 +181,13 @@ def execute(self, job: Job):
176181
found_raw_rule[rule] = True
177182
if new_rule not in seen_rules_md5s:
178183
seen_rules_md5s.append(new_rule)
179-
raw_rule_with_header = (
180-
f"// plugin: {self.NAME}, namespace_identifier: {rule}\n".encode() + raw_rule
181-
)
182-
self.add_data(label=DataLabel.YARA_RULE_HIT, tags={}, data=raw_rule_with_header)
184+
# Add the original yara rule that hit as an augmented stream. Stop at max allowed Augmented streams.
185+
if yara_rule_streams_added < self.cfg.max_yara_hit_streams_to_keep:
186+
raw_rule_with_header = (
187+
f"// plugin: {self.NAME}, namespace_identifier: {rule}\n".encode() + raw_rule
188+
)
189+
self.add_data(label=DataLabel.YARA_RULE_HIT, tags={}, data=raw_rule_with_header)
190+
yara_rule_streams_added += 1
183191

184192
for match_data in match.patterns:
185193
var = match_data.identifier

tests/test_yara.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import tempfile
12
import os
2-
3+
from pydantic_core import ValidationError
34
from azul_runner import FV, APIFeatureValue, Event, JobResult, State, test_template
45
from azul_runner.models import EventData
56

@@ -296,6 +297,95 @@ def test_yara_match_result_disk(self):
296297
inspect_data=True,
297298
)
298299

300+
def test_yara_100_matching_rules(self):
301+
"""Ensure when there are a 100+ matching rules that ony the first 50 streams are kept.
302+
303+
This is because the binary ingestor can only process up to 100 streams before it rejects files anyway.
304+
"""
305+
with tempfile.TemporaryDirectory("-yara-test") as temp_rule_dir:
306+
for iteration in range(101):
307+
raw_rule = (
308+
"""rule Exploit_CVE_2015_0313_NewStream%d {
309+
meta:
310+
rule_group = "Exploit"
311+
312+
//required
313+
classification = "UNCLASSIFIED"
314+
description = "Looks for presence of code that could indicate ANGLER EK use of this flash vuln"
315+
exploit = "CVE-2015-0313"
316+
info = "SWF"
317+
organisation = "Defence"
318+
poc = "azul@asd.gov.au"
319+
rule_version = "1"
320+
yara_version = "1.6"
321+
322+
//optional
323+
weight = 51
324+
325+
strings:
326+
$ = "take_over_32("
327+
$ = "get_x86_shellcode("
328+
$ = "exploit_primordial_start("
329+
$ = "exploit_primarodial_finish("
330+
$ = "this.shellcodes.GetX86Shellcode("
331+
$ = "Shellcodes("
332+
$ = "attacking_buffer"
333+
$ = "take_over_buffer"
334+
$ = "make_spray_by_buffers_no_holes"
335+
$ = "fake_object_address"
336+
condition:
337+
any of them
338+
}"""
339+
% iteration
340+
)
341+
with open(os.path.join(temp_rule_dir, f"ruleVersion-{iteration}.yara"), "w+") as f:
342+
f.write(raw_rule)
343+
344+
result = self.do_execution(
345+
# This content should hit on the CVE-2015-0313 Angler EK rule in rules
346+
data_in=[("content", b'example -> "exploit_primarodial_finish(" <-')],
347+
config={
348+
"yara_rules_path": temp_rule_dir,
349+
"version_suffix": "0",
350+
"name_suffix": "0",
351+
"security_override": "OFFICIAL",
352+
},
353+
)
354+
self.assertEqual(len(result.data.keys()), 50, "Must be capping the returned yara hits to 50")
355+
356+
# If option is overridden the number of streams kept should match that.
357+
CONFIGURATION_OVERRIDE_FOR_MAX_YARA_STREAMS = 60
358+
result = self.do_execution(
359+
# This content should hit on the CVE-2015-0313 Angler EK rule in rules
360+
data_in=[("content", b'example -> "exploit_primarodial_finish(" <-')],
361+
config={
362+
"yara_rules_path": temp_rule_dir,
363+
"version_suffix": "0",
364+
"name_suffix": "0",
365+
"security_override": "OFFICIAL",
366+
"max_yara_hit_streams_to_keep": CONFIGURATION_OVERRIDE_FOR_MAX_YARA_STREAMS,
367+
},
368+
)
369+
self.assertEqual(
370+
len(result.data.keys()),
371+
CONFIGURATION_OVERRIDE_FOR_MAX_YARA_STREAMS,
372+
f"Must be capping the returned yara hits to {CONFIGURATION_OVERRIDE_FOR_MAX_YARA_STREAMS}",
373+
)
374+
375+
# Fails when attempting to configure a value that must be less than 100.
376+
with self.assertRaises(ValidationError):
377+
result = self.do_execution(
378+
# This content should hit on the CVE-2015-0313 Angler EK rule in rules
379+
data_in=[("content", b'example -> "exploit_primarodial_finish(" <-')],
380+
config={
381+
"yara_rules_path": temp_rule_dir,
382+
"version_suffix": "0",
383+
"name_suffix": "0",
384+
"security_override": "OFFICIAL",
385+
"max_yara_hit_streams_to_keep": 200,
386+
},
387+
)
388+
299389
def test_yara_blacklist(self):
300390
"""Blacklist should filter the only rule."""
301391
path = os.path.join(os.path.dirname(__file__), rel_rules_dir)

0 commit comments

Comments
 (0)