mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 15:16:22 -08:00
update changelog
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- Utility script to detect feature overlap between new and existing CAPA rules [#1451](https://github.com/mandiant/capa/issues/1451) [@Aayush-Goel-04](https://github.com/aayush-goel-04)
|
- Utility script to detect feature overlap between new and existing CAPA rules [#1451](https://github.com/mandiant/capa/issues/1451) [@Aayush-Goel-04](https://github.com/aayush-goel-04)
|
||||||
- Add a dynamic feature extractor for the CAPE sandbox @yelhamer [#1535](https://github.com/mandiant/capa/issues/1535)
|
- Add a dynamic feature extractor for the CAPE sandbox @yelhamer [#1535](https://github.com/mandiant/capa/issues/1535)
|
||||||
- Add unit tests for the new CAPE extractor #1563 @yelhamer
|
- Add unit tests for the new CAPE extractor #1563 @yelhamer
|
||||||
|
- Add a new process scope for the dynamic analysis flavor @yelhamer
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
- Update Metadata type in capa main [#1411](https://github.com/mandiant/capa/issues/1411) [@Aayush-Goel-04](https://github.com/aayush-goel-04) @manasghandat
|
- Update Metadata type in capa main [#1411](https://github.com/mandiant/capa/issues/1411) [@Aayush-Goel-04](https://github.com/aayush-goel-04) @manasghandat
|
||||||
|
|||||||
@@ -73,12 +73,14 @@ HIDDEN_META_KEYS = ("capa/nursery", "capa/path")
|
|||||||
|
|
||||||
class Scope(str, Enum):
|
class Scope(str, Enum):
|
||||||
FILE = "file"
|
FILE = "file"
|
||||||
|
PROCESS = "process"
|
||||||
FUNCTION = "function"
|
FUNCTION = "function"
|
||||||
BASIC_BLOCK = "basic block"
|
BASIC_BLOCK = "basic block"
|
||||||
INSTRUCTION = "instruction"
|
INSTRUCTION = "instruction"
|
||||||
|
|
||||||
|
|
||||||
FILE_SCOPE = Scope.FILE.value
|
FILE_SCOPE = Scope.FILE.value
|
||||||
|
PROCESS_SCOPE = Scope.PROCESS
|
||||||
FUNCTION_SCOPE = Scope.FUNCTION.value
|
FUNCTION_SCOPE = Scope.FUNCTION.value
|
||||||
BASIC_BLOCK_SCOPE = Scope.BASIC_BLOCK.value
|
BASIC_BLOCK_SCOPE = Scope.BASIC_BLOCK.value
|
||||||
INSTRUCTION_SCOPE = Scope.INSTRUCTION.value
|
INSTRUCTION_SCOPE = Scope.INSTRUCTION.value
|
||||||
@@ -106,6 +108,12 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
|
|||||||
capa.features.common.Namespace,
|
capa.features.common.Namespace,
|
||||||
capa.features.common.Characteristic("mixed mode"),
|
capa.features.common.Characteristic("mixed mode"),
|
||||||
},
|
},
|
||||||
|
PROCESS_SCOPE: {
|
||||||
|
capa.features.common.String,
|
||||||
|
capa.features.common.Substring,
|
||||||
|
capa.features.common.Regex,
|
||||||
|
capa.features.common.Characteristic("embedded pe"),
|
||||||
|
},
|
||||||
FUNCTION_SCOPE: {
|
FUNCTION_SCOPE: {
|
||||||
capa.features.common.MatchedRule,
|
capa.features.common.MatchedRule,
|
||||||
capa.features.basicblock.BasicBlock,
|
capa.features.basicblock.BasicBlock,
|
||||||
@@ -150,6 +158,7 @@ SUPPORTED_FEATURES[INSTRUCTION_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
|||||||
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||||
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||||
SUPPORTED_FEATURES[FILE_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
SUPPORTED_FEATURES[FILE_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||||
|
SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||||
|
|
||||||
# all instruction scope features are also basic block features
|
# all instruction scope features are also basic block features
|
||||||
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[INSTRUCTION_SCOPE])
|
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[INSTRUCTION_SCOPE])
|
||||||
@@ -438,6 +447,15 @@ def build_statements(d, scope: str):
|
|||||||
# like with `write file`, we might say that `WriteFile` is optionally found alongside `CreateFileA`.
|
# like with `write file`, we might say that `WriteFile` is optionally found alongside `CreateFileA`.
|
||||||
return ceng.Some(0, [build_statements(dd, scope) for dd in d[key]], description=description)
|
return ceng.Some(0, [build_statements(dd, scope) for dd in d[key]], description=description)
|
||||||
|
|
||||||
|
elif key == "process":
|
||||||
|
if scope != FILE_SCOPE:
|
||||||
|
raise InvalidRule("process subscope supported only for file scope")
|
||||||
|
|
||||||
|
if len(d[key]) != 1:
|
||||||
|
raise InvalidRule("subscope must have exactly one child statement")
|
||||||
|
|
||||||
|
return ceng.Subscope(PROCESS_SCOPE, build_statements(d[key][0], PROCESS_SCOPE), description=description)
|
||||||
|
|
||||||
elif key == "function":
|
elif key == "function":
|
||||||
if scope != FILE_SCOPE:
|
if scope != FILE_SCOPE:
|
||||||
raise InvalidRule("function subscope supported only for file scope")
|
raise InvalidRule("function subscope supported only for file scope")
|
||||||
@@ -1098,6 +1116,7 @@ class RuleSet:
|
|||||||
rules = capa.optimizer.optimize_rules(rules)
|
rules = capa.optimizer.optimize_rules(rules)
|
||||||
|
|
||||||
self.file_rules = self._get_rules_for_scope(rules, FILE_SCOPE)
|
self.file_rules = self._get_rules_for_scope(rules, FILE_SCOPE)
|
||||||
|
self.process_rules = self._get_rules_for_scope(rules, PROCESS_SCOPE)
|
||||||
self.function_rules = self._get_rules_for_scope(rules, FUNCTION_SCOPE)
|
self.function_rules = self._get_rules_for_scope(rules, FUNCTION_SCOPE)
|
||||||
self.basic_block_rules = self._get_rules_for_scope(rules, BASIC_BLOCK_SCOPE)
|
self.basic_block_rules = self._get_rules_for_scope(rules, BASIC_BLOCK_SCOPE)
|
||||||
self.instruction_rules = self._get_rules_for_scope(rules, INSTRUCTION_SCOPE)
|
self.instruction_rules = self._get_rules_for_scope(rules, INSTRUCTION_SCOPE)
|
||||||
@@ -1106,6 +1125,9 @@ class RuleSet:
|
|||||||
|
|
||||||
# unstable
|
# unstable
|
||||||
(self._easy_file_rules_by_feature, self._hard_file_rules) = self._index_rules_by_feature(self.file_rules)
|
(self._easy_file_rules_by_feature, self._hard_file_rules) = self._index_rules_by_feature(self.file_rules)
|
||||||
|
(self._easy_process_rules_by_feature, self._hard_process_rules) = self._index_rules_by_feature(
|
||||||
|
self.process_rules
|
||||||
|
)
|
||||||
(self._easy_function_rules_by_feature, self._hard_function_rules) = self._index_rules_by_feature(
|
(self._easy_function_rules_by_feature, self._hard_function_rules) = self._index_rules_by_feature(
|
||||||
self.function_rules
|
self.function_rules
|
||||||
)
|
)
|
||||||
@@ -1355,6 +1377,9 @@ class RuleSet:
|
|||||||
if scope is Scope.FILE:
|
if scope is Scope.FILE:
|
||||||
easy_rules_by_feature = self._easy_file_rules_by_feature
|
easy_rules_by_feature = self._easy_file_rules_by_feature
|
||||||
hard_rule_names = self._hard_file_rules
|
hard_rule_names = self._hard_file_rules
|
||||||
|
elif scope is Scope.PROCESS:
|
||||||
|
easy_rules_by_feature = self._easy_process_rules_by_feature
|
||||||
|
hard_rule_names = self._hard_process_rules
|
||||||
elif scope is Scope.FUNCTION:
|
elif scope is Scope.FUNCTION:
|
||||||
easy_rules_by_feature = self._easy_function_rules_by_feature
|
easy_rules_by_feature = self._easy_function_rules_by_feature
|
||||||
hard_rule_names = self._hard_function_rules
|
hard_rule_names = self._hard_function_rules
|
||||||
|
|||||||
@@ -133,11 +133,24 @@ def test_ruleset():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
capa.rules.Rule.from_yaml(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: process rule
|
||||||
|
scope: process
|
||||||
|
features:
|
||||||
|
- string: "explorer.exe"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert len(rules.file_rules) == 1
|
assert len(rules.file_rules) == 1
|
||||||
assert len(rules.function_rules) == 1
|
assert len(rules.function_rules) == 1
|
||||||
assert len(rules.basic_block_rules) == 1
|
assert len(rules.basic_block_rules) == 1
|
||||||
|
assert len(rules.process_rules) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_match_across_scopes_file_function(z9324d_extractor):
|
def test_match_across_scopes_file_function(z9324d_extractor):
|
||||||
|
|||||||
@@ -277,6 +277,20 @@ def test_invalid_rule_feature():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with pytest.raises(capa.rules.InvalidRule):
|
||||||
|
capa.rules.Rule.from_yaml(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test rule
|
||||||
|
scope: process
|
||||||
|
features:
|
||||||
|
- mnemonic: xor
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_lib_rules():
|
def test_lib_rules():
|
||||||
rules = capa.rules.RuleSet(
|
rules = capa.rules.RuleSet(
|
||||||
@@ -319,7 +333,7 @@ def test_subscope_rules():
|
|||||||
"""
|
"""
|
||||||
rule:
|
rule:
|
||||||
meta:
|
meta:
|
||||||
name: test rule
|
name: test function subscope
|
||||||
scope: file
|
scope: file
|
||||||
features:
|
features:
|
||||||
- and:
|
- and:
|
||||||
@@ -330,17 +344,37 @@ def test_subscope_rules():
|
|||||||
- characteristic: loop
|
- characteristic: loop
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
capa.rules.Rule.from_yaml(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test process subscope
|
||||||
|
scope: file
|
||||||
|
features:
|
||||||
|
- and:
|
||||||
|
- import: WININET.dll.HttpOpenRequestW
|
||||||
|
- process:
|
||||||
|
- and:
|
||||||
|
- substring: "http://"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
# the file rule scope will have one rules:
|
# the file rule scope will have two rules:
|
||||||
# - `test rule`
|
# - `test function subscope` and `test process subscope`
|
||||||
assert len(rules.file_rules) == 1
|
assert len(rules.file_rules) == 2
|
||||||
|
|
||||||
# the function rule scope have one rule:
|
# the function rule scope have one rule:
|
||||||
# - the rule on which `test rule` depends
|
# - the rule on which `test function subscope` depends
|
||||||
assert len(rules.function_rules) == 1
|
assert len(rules.function_rules) == 1
|
||||||
|
|
||||||
|
# the process rule scope has one rule:
|
||||||
|
# - the rule on which `test process subscope` depends
|
||||||
|
assert len(rules.process_rules) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_rules():
|
def test_duplicate_rules():
|
||||||
with pytest.raises(capa.rules.InvalidRule):
|
with pytest.raises(capa.rules.InvalidRule):
|
||||||
|
|||||||
Reference in New Issue
Block a user