Skip to content

Commit

Permalink
Added hash algo field mappings, fixed escaping in regex and IN expres…
Browse files Browse the repository at this point in the history
…sions
  • Loading branch information
slincoln-systemtwo committed Oct 31, 2024
1 parent ea8bb89 commit eafa904
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 11 deletions.
40 changes: 29 additions & 11 deletions sigma/backends/secops/secops.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from importlib import resources
import re
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union

from sigma.conditions import ConditionAND, ConditionFieldEqualsValueExpression, ConditionItem, ConditionNOT, ConditionOR
Expand All @@ -9,7 +10,7 @@
from sigma.pipelines.secops.yara_l import yara_l_pipeline
from sigma.processing.pipeline import ProcessingPipeline
from sigma.rule import SigmaRule
from sigma.types import SigmaCompareExpression, SigmaString, SpecialChars
from sigma.types import SigmaCompareExpression, SigmaString, SpecialChars, SigmaRegularExpression


class SecOpsBackend(TextQueryBackend):
Expand Down Expand Up @@ -83,9 +84,10 @@ class SecOpsBackend(TextQueryBackend):
unbound_value_re_expression: ClassVar[str] = "{value}"

# String matching operators. if none is appropriate eq_token is used.
startswith_expression: ClassVar[Optional[str]] = "{field} = /^{value}.*/ nocase"
endswith_expression: ClassVar[Optional[str]] = "{field} = /.*{value}$/ nocase"
contains_expression: ClassVar[Optional[str]] = "{field} = /.*{value}.*/ nocase"
# Since we are using regex, we need to add '.*' where appropriate, but this is done in the convert_value_str method.
startswith_expression: ClassVar[Optional[str]] = "{field} = /^{value}/ nocase"
endswith_expression: ClassVar[Optional[str]] = "{field} = /{value}$/ nocase"
contains_expression: ClassVar[Optional[str]] = "{field} = /{value}/ nocase"
wildcard_match_expression: ClassVar[Optional[str]] = (
None # Special expression if wildcards can't be matched with the eq_token operator
)
Expand Down Expand Up @@ -122,6 +124,11 @@ def convert_value_str(self, s: SigmaString, state: ConversionState, quote_string
Override so when the wildcard is removed in startswith, endswith and contains expressions, we don't quote the string
"""
# Endswith, startswith and contains expressions are converted to regex, so we need to convert the SigmaString to a regex and then to a plain string.
if s.contains_special():
return s.to_regex().to_plain()

# If the string contains no special characters, we can use the normal conversion.
converted = s.convert(
self.escape_char,
self.wildcard_multi,
Expand Down Expand Up @@ -152,22 +159,23 @@ def convert_condition_field_eq_val_str(
expr = (
self.startswith_expression
) # If all conditions are fulfilled, use 'startswith' operator instead of equal token
value = cond.value[:-1]
value = cond.value
elif ( # Same as above but for 'endswith' operator: string starts with wildcard and doesn't contains further special characters
self.endswith_expression is not None
and cond.value.startswith(SpecialChars.WILDCARD_MULTI)
and not cond.value[1:].contains_special()
):
expr = self.endswith_expression
value = cond.value[1:]
value = cond.value
#value = SigmaString.from_str(cond.value.to_regex().to_plain())
elif ( # contains: string starts and ends with wildcard
self.contains_expression is not None
and cond.value.startswith(SpecialChars.WILDCARD_MULTI)
and cond.value.endswith(SpecialChars.WILDCARD_MULTI)
and not cond.value[1:-1].contains_special()
):
expr = self.contains_expression
value = cond.value[1:-1]
value = cond.value
elif ( # wildcard match expression: string contains wildcard
self.wildcard_match_expression is not None and cond.value.contains_special()
):
Expand Down Expand Up @@ -223,10 +231,20 @@ def convert_condition_as_in_expression(
)

def convert_value_for_in_expression(self, value, state):
if isinstance(value, SigmaString):
converted = self.convert_value_str(value, state).strip('"')
return converted.replace("*", ".*").replace("?", ".") if value.contains_special() else converted
return str(value).strip('"')
"""Convert a value for an IN expression. SecOps does not support the IN operator, so we have to use the eq_token operator with a regex.
Therefore, we also have to escape the regex characters in the value's.
Args:
value (SigmaString): The value to convert.
state (ConversionState): The conversion state.
Returns:
_type_: _description_
"""
if not isinstance(value, SigmaString):
value = SigmaString.from_str(str(value))
return value.to_regex().to_plain()


def finalize_query(
self,
Expand Down
5 changes: 5 additions & 0 deletions sigma/pipelines/secops/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def get_field_mappings() -> Dict[str, Dict[str, str]]:
"Hostname": "hostname",
"ComputerName": "hostname",
"Hashes": "hash",
"md5": "hash",
"sha1": "hash",
"sha256": "hash",
"sha512": "hash",
"imphash": "hash"
},
"process": {
"CommandLine": "target.process.command_line",
Expand Down
49 changes: 49 additions & 0 deletions tests/test_secops_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,52 @@ def test_set_event_type_already_set(secops_pipeline):
secops_pipeline.apply(rule)

assert rule.custom_attributes["event_types"] == {"PROCESS_UNCATEGORIZED"}


def test_hash_field_common_field_mappings(secops_pipeline):
rule = SigmaRule.from_yaml(
"""
title: Test Hash Field Common Field Mappings
logsource:
category: process_creation
product: windows
detection:
selection_hashes:
Hashes:
- 'MD5|1234567890abcdef1234567890abcdef'
- 'SHA1|92429d82a41e930486c6de5ebda9602d55c39986'
- 'SHA256|2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892'
selection_algos:
md5: '0987654321abcdef0987654321abcdef'
sha1: '6c66b4e46de761b856df0e14ad11098da2e6c351'
sha256: 'ba8fb484289ee92eb908642324f2a783586c5c1be12957c4a4b89524ad5b3acd'
condition: selection_hashes or selection_algos
"""
)

secops_pipeline.apply(rule)

assert rule.detection.detections["selection_hashes"].detection_items[0].field == "hash"
assert rule.detection.detections["selection_hashes"].detection_items[0].value[0] == SigmaString(
"1234567890abcdef1234567890abcdef"
)
assert rule.detection.detections["selection_hashes"].detection_items[0].value[1] == SigmaString(
"92429d82a41e930486c6de5ebda9602d55c39986"
)
assert rule.detection.detections["selection_hashes"].detection_items[0].value[2] == SigmaString(
"2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892"
)
assert rule.detection.detections["selection_algos"].detection_items[0].field == "hash"
assert rule.detection.detections["selection_algos"].detection_items[0].value[0] == SigmaString(
"0987654321abcdef0987654321abcdef"
)
assert rule.detection.detections["selection_algos"].detection_items[1].value[0] == SigmaString(
"6c66b4e46de761b856df0e14ad11098da2e6c351"
)
assert rule.detection.detections["selection_algos"].detection_items[2].value[0] == SigmaString(
"ba8fb484289ee92eb908642324f2a783586c5c1be12957c4a4b89524ad5b3acd"
)
assert len(rule.detection.detections["selection_algos"].detection_items) == 3
assert len(rule.detection.detections["selection_hashes"].detection_items) == 1

0 comments on commit eafa904

Please sign in to comment.