mirror of
https://github.com/mandiant/capa.git
synced 2025-12-12 15:49:46 -08:00
Merge pull request #1576 from yelhamer/process-scope
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
- 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 a CAPE file format and CAPE-based dynamic feature extraction to scripts/show-features.py #1566 @yelhamer
|
||||
- Add a new process scope for the dynamic analysis flavor #1517 @yelhamer
|
||||
- Add a new thread scope for the dynamic analysis flavor #1517 @yelhamer
|
||||
|
||||
### 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
|
||||
|
||||
@@ -73,12 +73,16 @@ HIDDEN_META_KEYS = ("capa/nursery", "capa/path")
|
||||
|
||||
class Scope(str, Enum):
|
||||
FILE = "file"
|
||||
PROCESS = "process"
|
||||
THREAD = "thread"
|
||||
FUNCTION = "function"
|
||||
BASIC_BLOCK = "basic block"
|
||||
INSTRUCTION = "instruction"
|
||||
|
||||
|
||||
FILE_SCOPE = Scope.FILE.value
|
||||
PROCESS_SCOPE = Scope.PROCESS.value
|
||||
THREAD_SCOPE = Scope.THREAD.value
|
||||
FUNCTION_SCOPE = Scope.FUNCTION.value
|
||||
BASIC_BLOCK_SCOPE = Scope.BASIC_BLOCK.value
|
||||
INSTRUCTION_SCOPE = Scope.INSTRUCTION.value
|
||||
@@ -106,6 +110,21 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
|
||||
capa.features.common.Namespace,
|
||||
capa.features.common.Characteristic("mixed mode"),
|
||||
},
|
||||
PROCESS_SCOPE: {
|
||||
capa.features.common.MatchedRule,
|
||||
capa.features.common.String,
|
||||
capa.features.common.Substring,
|
||||
capa.features.common.Regex,
|
||||
capa.features.common.Characteristic("embedded pe"),
|
||||
},
|
||||
THREAD_SCOPE: {
|
||||
capa.features.common.MatchedRule,
|
||||
capa.features.common.String,
|
||||
capa.features.common.Substring,
|
||||
capa.features.common.Regex,
|
||||
capa.features.insn.API,
|
||||
capa.features.insn.Number,
|
||||
},
|
||||
FUNCTION_SCOPE: {
|
||||
capa.features.common.MatchedRule,
|
||||
capa.features.basicblock.BasicBlock,
|
||||
@@ -150,7 +169,11 @@ SUPPORTED_FEATURES[INSTRUCTION_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[FILE_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||
SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||
SUPPORTED_FEATURES[THREAD_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE])
|
||||
|
||||
# all thread scope features are also process features
|
||||
SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[THREAD_SCOPE])
|
||||
# all instruction scope features are also basic block features
|
||||
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[INSTRUCTION_SCOPE])
|
||||
# all basic block scope features are also function scope features
|
||||
@@ -438,6 +461,24 @@ def build_statements(d, scope: str):
|
||||
# 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)
|
||||
|
||||
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 == "thread":
|
||||
if scope != PROCESS_SCOPE:
|
||||
raise InvalidRule("thread subscope supported only for the process scope")
|
||||
|
||||
if len(d[key]) != 1:
|
||||
raise InvalidRule("subscope must have exactly one child statement")
|
||||
|
||||
return ceng.Subscope(THREAD_SCOPE, build_statements(d[key][0], THREAD_SCOPE), description=description)
|
||||
|
||||
elif key == "function":
|
||||
if scope != FILE_SCOPE:
|
||||
raise InvalidRule("function subscope supported only for file scope")
|
||||
@@ -1098,6 +1139,8 @@ class RuleSet:
|
||||
rules = capa.optimizer.optimize_rules(rules)
|
||||
|
||||
self.file_rules = self._get_rules_for_scope(rules, FILE_SCOPE)
|
||||
self.process_rules = self._get_rules_for_scope(rules, PROCESS_SCOPE)
|
||||
self.thread_rules = self._get_rules_for_scope(rules, THREAD_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.instruction_rules = self._get_rules_for_scope(rules, INSTRUCTION_SCOPE)
|
||||
@@ -1106,6 +1149,10 @@ class RuleSet:
|
||||
|
||||
# unstable
|
||||
(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_thread_rules_by_feature, self._hard_thread_rules) = self._index_rules_by_feature(self.thread_rules)
|
||||
(self._easy_function_rules_by_feature, self._hard_function_rules) = self._index_rules_by_feature(
|
||||
self.function_rules
|
||||
)
|
||||
@@ -1355,6 +1402,12 @@ class RuleSet:
|
||||
if scope is Scope.FILE:
|
||||
easy_rules_by_feature = self._easy_file_rules_by_feature
|
||||
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.THREAD:
|
||||
easy_rules_by_feature = self._easy_thread_rules_by_feature
|
||||
hard_rule_names = self._hard_thread_rules
|
||||
elif scope is Scope.FUNCTION:
|
||||
easy_rules_by_feature = self._easy_function_rules_by_feature
|
||||
hard_rule_names = self._hard_function_rules
|
||||
|
||||
@@ -133,11 +133,37 @@ def test_ruleset():
|
||||
"""
|
||||
)
|
||||
),
|
||||
capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: process rule
|
||||
scope: process
|
||||
features:
|
||||
- string: "explorer.exe"
|
||||
"""
|
||||
)
|
||||
),
|
||||
capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: thread rule
|
||||
scope: thread
|
||||
features:
|
||||
- api: RegDeleteKey
|
||||
"""
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
assert len(rules.file_rules) == 1
|
||||
assert len(rules.function_rules) == 1
|
||||
assert len(rules.basic_block_rules) == 1
|
||||
assert len(rules.process_rules) == 1
|
||||
assert len(rules.thread_rules) == 1
|
||||
|
||||
|
||||
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():
|
||||
rules = capa.rules.RuleSet(
|
||||
@@ -319,7 +333,7 @@ def test_subscope_rules():
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
name: test function subscope
|
||||
scope: file
|
||||
features:
|
||||
- and:
|
||||
@@ -330,17 +344,57 @@ def test_subscope_rules():
|
||||
- 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://"
|
||||
"""
|
||||
)
|
||||
),
|
||||
capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test thread subscope
|
||||
scope: process
|
||||
features:
|
||||
- and:
|
||||
- string: "explorer.exe"
|
||||
- thread:
|
||||
- api: HttpOpenRequestW
|
||||
"""
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
# the file rule scope will have one rules:
|
||||
# - `test rule`
|
||||
assert len(rules.file_rules) == 1
|
||||
# the file rule scope will have two rules:
|
||||
# - `test function subscope` and `test process subscope`
|
||||
assert len(rules.file_rules) == 2
|
||||
|
||||
# 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
|
||||
|
||||
# the process rule scope has one rule:
|
||||
# - the rule on which `test process subscope` and depends
|
||||
# as well as `test thread scope`
|
||||
assert len(rules.process_rules) == 2
|
||||
|
||||
# the thread rule scope has one rule:
|
||||
# - the rule on which `test thread subscope` depends
|
||||
assert len(rules.thread_rules) == 1
|
||||
|
||||
|
||||
def test_duplicate_rules():
|
||||
with pytest.raises(capa.rules.InvalidRule):
|
||||
|
||||
Reference in New Issue
Block a user