add thread scope

This commit is contained in:
Yacine Elhamer
2023-06-28 13:08:11 +01:00
parent 0d38f85db7
commit 2b163edc0e
4 changed files with 63 additions and 2 deletions

View File

@@ -9,6 +9,7 @@
- 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

View File

@@ -74,6 +74,7 @@ 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"
@@ -81,6 +82,7 @@ class Scope(str, Enum):
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
@@ -115,6 +117,14 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
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,
@@ -160,7 +170,10 @@ 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 function features
SUPPORTED_FEATURES[FUNCTION_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
@@ -457,6 +470,15 @@ def build_statements(d, scope: str):
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")
@@ -1118,6 +1140,7 @@ class RuleSet:
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)
@@ -1129,6 +1152,7 @@ class RuleSet:
(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
)
@@ -1381,6 +1405,9 @@ class RuleSet:
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

View File

@@ -145,12 +145,25 @@ def test_ruleset():
"""
)
),
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):

View File

@@ -361,6 +361,21 @@ def test_subscope_rules():
"""
)
),
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 two rules:
@@ -372,8 +387,13 @@ def test_subscope_rules():
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
# - 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():