mirror of
https://github.com/mandiant/capa.git
synced 2025-12-23 07:28:34 -08:00
add thread scope
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
- Add unit tests for the new CAPE extractor #1563 @yelhamer
|
- 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 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 process scope for the dynamic analysis flavor #1517 @yelhamer
|
||||||
|
- Add a new thread scope for the dynamic analysis flavor #1517 @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
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ HIDDEN_META_KEYS = ("capa/nursery", "capa/path")
|
|||||||
class Scope(str, Enum):
|
class Scope(str, Enum):
|
||||||
FILE = "file"
|
FILE = "file"
|
||||||
PROCESS = "process"
|
PROCESS = "process"
|
||||||
|
THREAD = "thread"
|
||||||
FUNCTION = "function"
|
FUNCTION = "function"
|
||||||
BASIC_BLOCK = "basic block"
|
BASIC_BLOCK = "basic block"
|
||||||
INSTRUCTION = "instruction"
|
INSTRUCTION = "instruction"
|
||||||
@@ -81,6 +82,7 @@ class Scope(str, Enum):
|
|||||||
|
|
||||||
FILE_SCOPE = Scope.FILE.value
|
FILE_SCOPE = Scope.FILE.value
|
||||||
PROCESS_SCOPE = Scope.PROCESS.value
|
PROCESS_SCOPE = Scope.PROCESS.value
|
||||||
|
THREAD_SCOPE = Scope.THREAD.value
|
||||||
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
|
||||||
@@ -115,6 +117,14 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
|
|||||||
capa.features.common.Regex,
|
capa.features.common.Regex,
|
||||||
capa.features.common.Characteristic("embedded pe"),
|
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: {
|
FUNCTION_SCOPE: {
|
||||||
capa.features.common.MatchedRule,
|
capa.features.common.MatchedRule,
|
||||||
capa.features.basicblock.BasicBlock,
|
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[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])
|
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
|
# 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])
|
||||||
# all basic block scope features are also function scope features
|
# 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)
|
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":
|
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")
|
||||||
@@ -1118,6 +1140,7 @@ class RuleSet:
|
|||||||
|
|
||||||
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.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.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)
|
||||||
@@ -1129,6 +1152,7 @@ class RuleSet:
|
|||||||
(self._easy_process_rules_by_feature, self._hard_process_rules) = self._index_rules_by_feature(
|
(self._easy_process_rules_by_feature, self._hard_process_rules) = self._index_rules_by_feature(
|
||||||
self.process_rules
|
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._easy_function_rules_by_feature, self._hard_function_rules) = self._index_rules_by_feature(
|
||||||
self.function_rules
|
self.function_rules
|
||||||
)
|
)
|
||||||
@@ -1381,6 +1405,9 @@ class RuleSet:
|
|||||||
elif scope is Scope.PROCESS:
|
elif scope is Scope.PROCESS:
|
||||||
easy_rules_by_feature = self._easy_process_rules_by_feature
|
easy_rules_by_feature = self._easy_process_rules_by_feature
|
||||||
hard_rule_names = self._hard_process_rules
|
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:
|
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
|
||||||
|
|||||||
@@ -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.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
|
assert len(rules.process_rules) == 1
|
||||||
|
assert len(rules.thread_rules) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_match_across_scopes_file_function(z9324d_extractor):
|
def test_match_across_scopes_file_function(z9324d_extractor):
|
||||||
|
|||||||
@@ -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:
|
# the file rule scope will have two rules:
|
||||||
@@ -372,8 +387,13 @@ def test_subscope_rules():
|
|||||||
assert len(rules.function_rules) == 1
|
assert len(rules.function_rules) == 1
|
||||||
|
|
||||||
# the process rule scope has one rule:
|
# the process rule scope has one rule:
|
||||||
# - the rule on which `test process subscope` depends
|
# - the rule on which `test process subscope` and depends
|
||||||
assert len(rules.process_rules) == 1
|
# 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():
|
def test_duplicate_rules():
|
||||||
|
|||||||
Reference in New Issue
Block a user