Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaspatzke committed Sep 8, 2024
2 parents 7550caf + 0119040 commit d86fc47
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 11 deletions.
20 changes: 15 additions & 5 deletions sigma/processing/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,13 +762,17 @@ def apply(
class ReplaceStringTransformation(StringValueTransformation):
"""
Replace string part matched by regular expresssion with replacement string that can reference
capture groups. It operates on the plain string parts of the SigmaString value.
capture groups. Normally, the replacement operates on the plain string representation of the
SigmaString. This allows also to include special characters and placeholders in the replacement.
By enabling the skip_special parameter, the replacement is only applied to the plain string
parts of a SigmaString and special characters and placeholders are left untouched.
This is basically an interface to re.sub() and can use all features available there.
The replacement is implemented with re.sub() and can use all features available there.
"""

regex: str
replacement: str
skip_special: bool = False

def __post_init__(self):
super().__post_init__()
Expand All @@ -781,9 +785,15 @@ def __post_init__(self):

def apply_string_value(self, field: str, val: SigmaString) -> SigmaString:
if isinstance(val, SigmaString):
return val.map_parts(
lambda s: self.re.sub(self.replacement, s), lambda p: isinstance(p, str)
)
if self.skip_special:
return val.map_parts(
lambda s: self.re.sub(self.replacement, s), lambda p: isinstance(p, str)
)
else:
sigma_string_plain = str(val)
replaced = self.re.sub(self.replacement, sigma_string_plain)
postprocessed_backslashes = re.sub(r"\\(?![*?])", r"\\\\", replaced)
return SigmaString(postprocessed_backslashes)


@dataclass
Expand Down
17 changes: 15 additions & 2 deletions sigma/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,9 +436,22 @@ def to_plain(self) -> Union[Dict[str, Union[str, int, None]], List[str]]:
)

if len(self.original_value) > 1:
value = [value.to_plain() for value in self.original_value]
value = [
(
value.to_plain(True)
if isinstance(value, SigmaString)
and SigmaRegularExpressionModifier in self.modifiers
else value.to_plain()
)
for value in self.original_value
]
else:
value = self.original_value[0].to_plain()
value = (
self.original_value[0].to_plain(True)
if isinstance(self.original_value[0], SigmaString)
and SigmaRegularExpressionModifier in self.modifiers
else self.original_value[0].to_plain()
)

if (
self.is_keyword() and len(self.modifiers) == 0
Expand Down
15 changes: 11 additions & 4 deletions sigma/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,17 @@ def __eq__(self, other: Union["SigmaString", str]) -> bool:
)

def __str__(self) -> str:
return self.to_plain()

def to_plain(self, regex: bool = False) -> str:
"""Generate string representation of SigmaString with or without regex escaping."""
rs = ""
for s in self.s:
if isinstance(s, str):
rs += s
if regex:
rs += s
else:
rs += s.replace("*", "\\*").replace("?", "\\?")
elif isinstance(s, SpecialChars):
rs += special_char_mapping[s]
elif isinstance(s, Placeholder):
Expand All @@ -372,9 +379,9 @@ def __str__(self) -> str:
def __repr__(self) -> str:
return str(f"SigmaString({self.s})")

def to_plain(self):
"""Return plain string representation of SigmaString, equivalent to converting it with str()."""
return str(self)
def to_plain_regex(self):
"""Return plain string representation of SigmaString with reduced escaping."""
return self._stringify(True)

def __bytes__(self) -> bytes:
return str(self).encode()
Expand Down
62 changes: 62 additions & 0 deletions tests/test_processing_transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,36 @@ def test_replace_string_specials(dummy_pipeline):
)
transformation = ReplaceStringTransformation("^.*\\\\", "/")
transformation.apply(dummy_pipeline, sigma_rule)
assert sigma_rule.detection.detections["test"] == SigmaDetection(
[
SigmaDetection(
[
SigmaDetectionItem("field1", [], [SigmaString("/value")]),
SigmaDetectionItem("field2", [], [SigmaNumber(123)]),
]
)
]
)


def test_replace_string_skip_specials(dummy_pipeline):
sigma_rule = SigmaRule.from_dict(
{
"title": "Test",
"logsource": {"category": "test"},
"detection": {
"test": [
{
"field1": "*\\value",
"field2": 123,
}
],
"condition": "test",
},
}
)
transformation = ReplaceStringTransformation("^.*\\\\", "/", True)
transformation.apply(dummy_pipeline, sigma_rule)
assert sigma_rule.detection.detections["test"] == SigmaDetection(
[
SigmaDetection(
Expand All @@ -1356,6 +1386,38 @@ def test_replace_string_specials(dummy_pipeline):
)


def test_replace_string_backslashes(dummy_pipeline):
sigma_rule = SigmaRule.from_dict(
{
"title": "Test",
"logsource": {"category": "test"},
"detection": {
"test": [
{
"field1": r"backslash\\value",
"field2": r"backslash\\\\value",
"field3": r"plainwildcard\*value",
}
],
"condition": "test",
},
}
)
transformation = ReplaceStringTransformation("value", "test")
transformation.apply(dummy_pipeline, sigma_rule)
assert sigma_rule.detection.detections["test"] == SigmaDetection(
[
SigmaDetection(
[
SigmaDetectionItem("field1", [], [SigmaString(r"backslash\\test")]),
SigmaDetectionItem("field2", [], [SigmaString(r"backslash\\\\test")]),
SigmaDetectionItem("field3", [], [SigmaString(r"plainwildcard\*test")]),
]
)
]
)


def test_replace_string_invalid():
with pytest.raises(SigmaRegularExpressionError, match="Regular expression.*invalid"):
ReplaceStringTransformation("*", "test")
Expand Down
14 changes: 14 additions & 0 deletions tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,20 @@ def test_strings_to_string():
assert str(SigmaString("test*?")) == "test*?"


def test_strings_with_plain_wildcards_to_string():
plain_s = "value\*with\?plain*wild?cards"
s = SigmaString(plain_s)
assert s.s == (
"value*with?plain",
SpecialChars.WILDCARD_MULTI,
"wild",
SpecialChars.WILDCARD_SINGLE,
"cards",
)
assert str(s) == plain_s
assert SigmaString(str(s)) == s


def test_strings_with_placeholder_to_string():
assert str(SigmaString("te%var%st").insert_placeholders()) == "te%var%st"

Expand Down

0 comments on commit d86fc47

Please sign in to comment.