rules: use Scope enum instead of constants

This commit is contained in:
Willi Ballenthin
2023-08-25 12:54:57 +00:00
parent 164b08276c
commit a734358377
8 changed files with 95 additions and 106 deletions

View File

@@ -500,13 +500,13 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
location = location_.to_capa() location = location_.to_capa()
parent2: CapaExplorerDataItem parent2: CapaExplorerDataItem
if capa.rules.FILE_SCOPE in rule.meta.scopes: if capa.rules.Scope.FILE in rule.meta.scopes:
parent2 = parent parent2 = parent
elif capa.rules.FUNCTION_SCOPE in rule.meta.scopes: elif capa.rules.Scope.FUNCTION in rule.meta.scopes:
parent2 = CapaExplorerFunctionItem(parent, location) parent2 = CapaExplorerFunctionItem(parent, location)
elif capa.rules.BASIC_BLOCK_SCOPE in rule.meta.scopes: elif capa.rules.Scope.BASIC_BLOCK in rule.meta.scopes:
parent2 = CapaExplorerBlockItem(parent, location) parent2 = CapaExplorerBlockItem(parent, location)
elif capa.rules.INSTRUCTION_SCOPE in rule.meta.scopes: elif capa.rules.Scope.INSTRUCTION in rule.meta.scopes:
parent2 = CapaExplorerInstructionItem(parent, location) parent2 = CapaExplorerInstructionItem(parent, location)
else: else:
raise RuntimeError("unexpected rule scope: " + str(rule.meta.scopes.static)) raise RuntimeError("unexpected rule scope: " + str(rule.meta.scopes.static))

View File

@@ -1056,7 +1056,7 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti
matched_threads = set() matched_threads = set()
for rule_name, matches in capabilities.items(): for rule_name, matches in capabilities.items():
rule = rules[rule_name] rule = rules[rule_name]
if capa.rules.THREAD_SCOPE in rule.scopes: if capa.rules.Scope.THREAD in rule.scopes:
for addr, _ in matches: for addr, _ in matches:
assert addr in processes_by_thread assert addr in processes_by_thread
matched_threads.add(addr) matched_threads.add(addr)
@@ -1099,7 +1099,7 @@ def compute_static_layout(rules, extractor: StaticFeatureExtractor, capabilities
matched_bbs = set() matched_bbs = set()
for rule_name, matches in capabilities.items(): for rule_name, matches in capabilities.items():
rule = rules[rule_name] rule = rules[rule_name]
if capa.rules.BASIC_BLOCK_SCOPE in rule.scopes: if capa.rules.Scope.BASIC_BLOCK in rule.scopes:
for addr, _ in matches: for addr, _ in matches:
assert addr in functions_by_bb assert addr in functions_by_bb
matched_bbs.add(addr) matched_bbs.add(addr)

View File

@@ -214,7 +214,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
rows.append((key, v)) rows.append((key, v))
if capa.rules.FILE_SCOPE not in rule.meta.scopes: if capa.rules.Scope.FILE not in rule.meta.scopes:
locations = [m[0] for m in doc.rules[rule.meta.name].matches] locations = [m[0] for m in doc.rules[rule.meta.name].matches]
rows.append(("matches", "\n".join(map(format_address, locations)))) rows.append(("matches", "\n".join(map(format_address, locations))))

View File

@@ -357,7 +357,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
if capa.rules.FILE_SCOPE in rule.meta.scopes: if capa.rules.Scope.FILE in rule.meta.scopes:
matches = doc.rules[rule.meta.name].matches matches = doc.rules[rule.meta.name].matches
if len(matches) != 1: if len(matches) != 1:
# i think there should only ever be one match per file-scope rule, # i think there should only ever be one match per file-scope rule,
@@ -379,13 +379,13 @@ def render_rules(ostream, doc: rd.ResultDocument):
ostream.write(" @ ") ostream.write(" @ ")
ostream.write(capa.render.verbose.format_address(location)) ostream.write(capa.render.verbose.format_address(location))
if capa.rules.BASIC_BLOCK_SCOPE in rule.meta.scopes: if capa.rules.Scope.BASIC_BLOCK in rule.meta.scopes:
ostream.write( ostream.write(
" in function " " in function "
+ capa.render.verbose.format_address(frz.Address.from_capa(functions_by_bb[location.to_capa()])) + capa.render.verbose.format_address(frz.Address.from_capa(functions_by_bb[location.to_capa()]))
) )
if capa.rules.THREAD_SCOPE in rule.meta.scopes: if capa.rules.Scope.THREAD in rule.meta.scopes:
ostream.write( ostream.write(
" in process " " in process "
+ capa.render.verbose.format_address( + capa.render.verbose.format_address(

View File

@@ -82,46 +82,37 @@ class Scope(str, Enum):
BASIC_BLOCK = "basic block" BASIC_BLOCK = "basic block"
INSTRUCTION = "instruction" INSTRUCTION = "instruction"
FILE_SCOPE = Scope.FILE.value
PROCESS_SCOPE = Scope.PROCESS.value
THREAD_SCOPE = Scope.THREAD.value
CALL_SCOPE = Scope.CALL.value
FUNCTION_SCOPE = Scope.FUNCTION.value
BASIC_BLOCK_SCOPE = Scope.BASIC_BLOCK.value
INSTRUCTION_SCOPE = Scope.INSTRUCTION.value
# used only to specify supported features per scope. # used only to specify supported features per scope.
# not used to validate rules. # not used to validate rules.
GLOBAL_SCOPE = "global" GLOBAL = "global"
# these literals are used to check if the flavor # these literals are used to check if the flavor
# of a rule is correct. # of a rule is correct.
STATIC_SCOPES = { STATIC_SCOPES = {
FILE_SCOPE, Scope.FILE,
GLOBAL_SCOPE, Scope.GLOBAL,
FUNCTION_SCOPE, Scope.FUNCTION,
BASIC_BLOCK_SCOPE, Scope.BASIC_BLOCK,
INSTRUCTION_SCOPE, Scope.INSTRUCTION,
} }
DYNAMIC_SCOPES = { DYNAMIC_SCOPES = {
FILE_SCOPE, Scope.FILE,
GLOBAL_SCOPE, Scope.GLOBAL,
PROCESS_SCOPE, Scope.PROCESS,
THREAD_SCOPE, Scope.THREAD,
CALL_SCOPE, Scope.CALL,
} }
@dataclass @dataclass
class Scopes: class Scopes:
# when None, the scope is not supported by a rule # when None, the scope is not supported by a rule
static: Optional[str] = None static: Optional[Scope] = None
# when None, the scope is not supported by a rule # when None, the scope is not supported by a rule
dynamic: Optional[str] = None dynamic: Optional[Scope] = None
def __contains__(self, scope: Union[Scope, str]) -> bool: def __contains__(self, scope: Scope) -> bool:
assert isinstance(scope, (Scope, str))
return (scope == self.static) or (scope == self.dynamic) return (scope == self.static) or (scope == self.dynamic)
def __repr__(self) -> str: def __repr__(self) -> str:
@@ -135,56 +126,55 @@ class Scopes:
raise ValueError("invalid rules class. at least one scope must be specified") raise ValueError("invalid rules class. at least one scope must be specified")
@classmethod @classmethod
def from_dict(self, scopes: Dict) -> "Scopes": def from_dict(self, scopes: Dict[str, str]) -> "Scopes":
assert isinstance(scopes, dict) # make local copy so we don't make changes outside of this routine.
# we'll use the value None to indicate the scope is not supported.
# make local copy so we don't make changes outside of this routine scopes_: Dict[str, Optional[str]] = dict(scopes)
scopes = dict(scopes)
# mark non-specified scopes as invalid # mark non-specified scopes as invalid
if "static" not in scopes: if "static" not in scopes_:
raise InvalidRule("static scope must be provided") raise InvalidRule("static scope must be provided")
if "dynamic" not in scopes: if "dynamic" not in scopes_:
raise InvalidRule("dynamic scope must be provided") raise InvalidRule("dynamic scope must be provided")
# check the syntax of the meta `scopes` field # check the syntax of the meta `scopes` field
if sorted(scopes) != ["dynamic", "static"]: if sorted(scopes_) != ["dynamic", "static"]:
raise InvalidRule("scope flavors can be either static or dynamic") raise InvalidRule("scope flavors can be either static or dynamic")
if scopes["static"] == "unsupported": if scopes_["static"] == "unsupported":
scopes["static"] = None scopes_["static"] = None
if scopes["dynamic"] == "unsupported": if scopes_["dynamic"] == "unsupported":
scopes["dynamic"] = None scopes_["dynamic"] = None
# unspecified is used to indicate a rule is yet to be migrated. # unspecified is used to indicate a rule is yet to be migrated.
# TODO(williballenthin): this scope term should be removed once all rules have been migrated. # TODO(williballenthin): this scope term should be removed once all rules have been migrated.
# https://github.com/mandiant/capa/issues/1747 # https://github.com/mandiant/capa/issues/1747
if scopes["static"] == "unspecified": if scopes_["static"] == "unspecified":
scopes["static"] = None scopes_["static"] = None
if scopes["dynamic"] == "unspecified": if scopes_["dynamic"] == "unspecified":
scopes["dynamic"] = None scopes_["dynamic"] = None
if (not scopes["static"]) and (not scopes["dynamic"]): if (not scopes_["static"]) and (not scopes_["dynamic"]):
raise InvalidRule("invalid scopes value. At least one scope must be specified") raise InvalidRule("invalid scopes value. At least one scope must be specified")
# check that all the specified scopes are valid # check that all the specified scopes are valid
if scopes["static"] and scopes["static"] not in STATIC_SCOPES: if scopes_["static"] and scopes_["static"] not in STATIC_SCOPES:
raise InvalidRule(f"{scopes['static']} is not a valid static scope") raise InvalidRule(f"{scopes_['static']} is not a valid static scope")
if scopes["dynamic"] and scopes["dynamic"] not in DYNAMIC_SCOPES: if scopes_["dynamic"] and scopes_["dynamic"] not in DYNAMIC_SCOPES:
raise InvalidRule(f"{scopes['dynamic']} is not a valid dynamic scope") raise InvalidRule(f"{scopes_['dynamic']} is not a valid dynamic scope")
return Scopes(static=scopes["static"], dynamic=scopes["dynamic"]) return Scopes(static=Scope(scopes_["static"]), dynamic=Scope(scopes_["dynamic"]))
SUPPORTED_FEATURES: Dict[str, Set] = { SUPPORTED_FEATURES: Dict[str, Set] = {
GLOBAL_SCOPE: { Scope.GLOBAL: {
# these will be added to other scopes, see below. # these will be added to other scopes, see below.
capa.features.common.OS, capa.features.common.OS,
capa.features.common.Arch, capa.features.common.Arch,
capa.features.common.Format, capa.features.common.Format,
}, },
FILE_SCOPE: { Scope.FILE: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
capa.features.file.Export, capa.features.file.Export,
capa.features.file.Import, capa.features.file.Import,
@@ -197,11 +187,11 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
capa.features.common.Characteristic("mixed mode"), capa.features.common.Characteristic("mixed mode"),
capa.features.common.Characteristic("forwarded export"), capa.features.common.Characteristic("forwarded export"),
}, },
PROCESS_SCOPE: { Scope.PROCESS: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
}, },
THREAD_SCOPE: set(), Scope.THREAD: set(),
CALL_SCOPE: { Scope.CALL: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
capa.features.common.Regex, capa.features.common.Regex,
capa.features.common.String, capa.features.common.String,
@@ -209,7 +199,7 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
capa.features.insn.API, capa.features.insn.API,
capa.features.insn.Number, capa.features.insn.Number,
}, },
FUNCTION_SCOPE: { Scope.FUNCTION: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
capa.features.basicblock.BasicBlock, capa.features.basicblock.BasicBlock,
capa.features.common.Characteristic("calls from"), capa.features.common.Characteristic("calls from"),
@@ -218,13 +208,13 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
capa.features.common.Characteristic("recursive call"), capa.features.common.Characteristic("recursive call"),
# plus basic block scope features, see below # plus basic block scope features, see below
}, },
BASIC_BLOCK_SCOPE: { Scope.BASIC_BLOCK: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
capa.features.common.Characteristic("tight loop"), capa.features.common.Characteristic("tight loop"),
capa.features.common.Characteristic("stack string"), capa.features.common.Characteristic("stack string"),
# plus instruction scope features, see below # plus instruction scope features, see below
}, },
INSTRUCTION_SCOPE: { Scope.INSTRUCTION: {
capa.features.common.MatchedRule, capa.features.common.MatchedRule,
capa.features.insn.API, capa.features.insn.API,
capa.features.insn.Property, capa.features.insn.Property,
@@ -249,24 +239,24 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
} }
# global scope features are available in all other scopes # global scope features are available in all other scopes
SUPPORTED_FEATURES[INSTRUCTION_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.INSTRUCTION].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.BASIC_BLOCK].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.FUNCTION].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[FILE_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.FILE].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.PROCESS].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[THREAD_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.THREAD].update(SUPPORTED_FEATURES[Scope.GLOBAL])
SUPPORTED_FEATURES[CALL_SCOPE].update(SUPPORTED_FEATURES[GLOBAL_SCOPE]) SUPPORTED_FEATURES[Scope.CALL].update(SUPPORTED_FEATURES[Scope.GLOBAL])
# all call scope features are also thread features # all call scope features are also thread features
SUPPORTED_FEATURES[THREAD_SCOPE].update(SUPPORTED_FEATURES[CALL_SCOPE]) SUPPORTED_FEATURES[Scope.THREAD].update(SUPPORTED_FEATURES[Scope.CALL])
# all thread scope features are also process features # all thread scope features are also process features
SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[THREAD_SCOPE]) SUPPORTED_FEATURES[Scope.PROCESS].update(SUPPORTED_FEATURES[Scope.THREAD])
# 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[Scope.BASIC_BLOCK].update(SUPPORTED_FEATURES[Scope.INSTRUCTION])
# all basic block scope features are also function scope features # all basic block scope features are also function scope features
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE]) SUPPORTED_FEATURES[Scope.FUNCTION].update(SUPPORTED_FEATURES[Scope.BASIC_BLOCK])
class InvalidRule(ValueError): class InvalidRule(ValueError):
@@ -558,66 +548,66 @@ def build_statements(d, scopes: Scopes):
return ceng.Some(0, [build_statements(dd, scopes) for dd in d[key]], description=description) return ceng.Some(0, [build_statements(dd, scopes) for dd in d[key]], description=description)
elif key == "process": elif key == "process":
if FILE_SCOPE not in scopes: if Scope.FILE not in scopes:
raise InvalidRule("process subscope supported only for file scope") raise InvalidRule("process subscope supported only for file scope")
if len(d[key]) != 1: if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement") raise InvalidRule("subscope must have exactly one child statement")
return ceng.Subscope( return ceng.Subscope(
PROCESS_SCOPE, build_statements(d[key][0], Scopes(dynamic=PROCESS_SCOPE)), description=description Scope.PROCESS, build_statements(d[key][0], Scopes(dynamic=Scope.PROCESS)), description=description
) )
elif key == "thread": elif key == "thread":
if all(s not in scopes for s in (FILE_SCOPE, PROCESS_SCOPE)): if all(s not in scopes for s in (Scope.FILE, Scope.PROCESS)):
raise InvalidRule("thread subscope supported only for the process scope") raise InvalidRule("thread subscope supported only for the process scope")
if len(d[key]) != 1: if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement") raise InvalidRule("subscope must have exactly one child statement")
return ceng.Subscope( return ceng.Subscope(
THREAD_SCOPE, build_statements(d[key][0], Scopes(dynamic=THREAD_SCOPE)), description=description Scope.THREAD, build_statements(d[key][0], Scopes(dynamic=Scope.THREAD)), description=description
) )
elif key == "call": elif key == "call":
if all(s not in scopes for s in (FILE_SCOPE, PROCESS_SCOPE, THREAD_SCOPE)): if all(s not in scopes for s in (Scope.FILE, Scope.PROCESS, Scope.THREAD)):
raise InvalidRule("call subscope supported only for the process and thread scopes") raise InvalidRule("call subscope supported only for the process and thread scopes")
if len(d[key]) != 1: if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement") raise InvalidRule("subscope must have exactly one child statement")
return ceng.Subscope( return ceng.Subscope(
CALL_SCOPE, build_statements(d[key][0], Scopes(dynamic=CALL_SCOPE)), description=description Scope.CALL, build_statements(d[key][0], Scopes(dynamic=Scope.CALL)), description=description
) )
elif key == "function": elif key == "function":
if FILE_SCOPE not in scopes: if Scope.FILE not in scopes:
raise InvalidRule("function subscope supported only for file scope") raise InvalidRule("function subscope supported only for file scope")
if len(d[key]) != 1: if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement") raise InvalidRule("subscope must have exactly one child statement")
return ceng.Subscope( return ceng.Subscope(
FUNCTION_SCOPE, build_statements(d[key][0], Scopes(static=FUNCTION_SCOPE)), description=description Scope.FUNCTION, build_statements(d[key][0], Scopes(static=Scope.FUNCTION)), description=description
) )
elif key == "basic block": elif key == "basic block":
if FUNCTION_SCOPE not in scopes: if Scope.FUNCTION not in scopes:
raise InvalidRule("basic block subscope supported only for function scope") raise InvalidRule("basic block subscope supported only for function scope")
if len(d[key]) != 1: if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement") raise InvalidRule("subscope must have exactly one child statement")
return ceng.Subscope( return ceng.Subscope(
BASIC_BLOCK_SCOPE, build_statements(d[key][0], Scopes(static=BASIC_BLOCK_SCOPE)), description=description Scope.BASIC_BLOCK, build_statements(d[key][0], Scopes(static=Scope.BASIC_BLOCK)), description=description
) )
elif key == "instruction": elif key == "instruction":
if all(s not in scopes for s in (FUNCTION_SCOPE, BASIC_BLOCK_SCOPE)): if all(s not in scopes for s in (Scope.FUNCTION, Scope.BASIC_BLOCK)):
raise InvalidRule("instruction subscope supported only for function and basic block scope") raise InvalidRule("instruction subscope supported only for function and basic block scope")
if len(d[key]) == 1: if len(d[key]) == 1:
statements = build_statements(d[key][0], Scopes(static=INSTRUCTION_SCOPE)) statements = build_statements(d[key][0], Scopes(static=Scope.INSTRUCTION))
else: else:
# for instruction subscopes, we support a shorthand in which the top level AND is implied. # for instruction subscopes, we support a shorthand in which the top level AND is implied.
# the following are equivalent: # the following are equivalent:
@@ -631,9 +621,9 @@ def build_statements(d, scopes: Scopes):
# - arch: i386 # - arch: i386
# - mnemonic: cmp # - mnemonic: cmp
# #
statements = ceng.And([build_statements(dd, Scopes(static=INSTRUCTION_SCOPE)) for dd in d[key]]) statements = ceng.And([build_statements(dd, Scopes(static=Scope.INSTRUCTION)) for dd in d[key]])
return ceng.Subscope(INSTRUCTION_SCOPE, statements, description=description) return ceng.Subscope(Scope.INSTRUCTION, statements, description=description)
elif key.startswith("count(") and key.endswith(")"): elif key.startswith("count(") and key.endswith(")"):
# e.g.: # e.g.:
@@ -1140,10 +1130,9 @@ class Rule:
return doc return doc
def get_rules_with_scope(rules, scope) -> List[Rule]: def get_rules_with_scope(rules, scope: Scope) -> List[Rule]:
""" """
from the given collection of rules, select those with the given scope. from the given collection of rules, select those with the given scope.
`scope` is one of the capa.rules.*_SCOPE constants.
""" """
return [rule for rule in rules if scope in rule.scopes] return [rule for rule in rules if scope in rule.scopes]
@@ -1295,13 +1284,13 @@ 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, Scope.FILE)
self.process_rules = self._get_rules_for_scope(rules, PROCESS_SCOPE) self.process_rules = self._get_rules_for_scope(rules, Scope.PROCESS)
self.thread_rules = self._get_rules_for_scope(rules, THREAD_SCOPE) self.thread_rules = self._get_rules_for_scope(rules, Scope.THREAD)
self.call_rules = self._get_rules_for_scope(rules, CALL_SCOPE) self.call_rules = self._get_rules_for_scope(rules, Scope.CALL)
self.function_rules = self._get_rules_for_scope(rules, FUNCTION_SCOPE) self.function_rules = self._get_rules_for_scope(rules, Scope.FUNCTION)
self.basic_block_rules = self._get_rules_for_scope(rules, BASIC_BLOCK_SCOPE) self.basic_block_rules = self._get_rules_for_scope(rules, Scope.BASIC_BLOCK)
self.instruction_rules = self._get_rules_for_scope(rules, INSTRUCTION_SCOPE) self.instruction_rules = self._get_rules_for_scope(rules, Scope.INSTRUCTION)
self.rules = {rule.name: rule for rule in rules} self.rules = {rule.name: rule for rule in rules}
self.rules_by_namespace = index_rules_by_namespace(rules) self.rules_by_namespace = index_rules_by_namespace(rules)

View File

@@ -115,10 +115,10 @@ def render_matches_by_function(doc: rd.ResultDocument):
matches_by_function = collections.defaultdict(set) matches_by_function = collections.defaultdict(set)
for rule in rutils.capability_rules(doc): for rule in rutils.capability_rules(doc):
if capa.rules.FUNCTION_SCOPE in rule.meta.scopes: if capa.rules.Scope.FUNCTION in rule.meta.scopes:
for addr, _ in rule.matches: for addr, _ in rule.matches:
matches_by_function[addr].add(rule.meta.name) matches_by_function[addr].add(rule.meta.name)
elif capa.rules.BASIC_BLOCK_SCOPE in rule.meta.scopes: elif capa.rules.Scope.BASIC_BLOCK in rule.meta.scopes:
for addr, _ in rule.matches: for addr, _ in rule.matches:
function = functions_by_bb[addr] function = functions_by_bb[addr]
matches_by_function[function].add(rule.meta.name) matches_by_function[function].add(rule.meta.name)

View File

@@ -116,10 +116,10 @@ def test_addr_to_pb2():
def test_scope_to_pb2(): def test_scope_to_pb2():
assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.FILE_SCOPE)) == capa_pb2.SCOPE_FILE assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.Scope.FILE)) == capa_pb2.SCOPE_FILE
assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.FUNCTION_SCOPE)) == capa_pb2.SCOPE_FUNCTION assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.Scope.FUNCTION)) == capa_pb2.SCOPE_FUNCTION
assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.BASIC_BLOCK_SCOPE)) == capa_pb2.SCOPE_BASIC_BLOCK assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.Scope.BASIC_BLOCK)) == capa_pb2.SCOPE_BASIC_BLOCK
assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.INSTRUCTION_SCOPE)) == capa_pb2.SCOPE_INSTRUCTION assert capa.render.proto.scope_to_pb2(capa.rules.Scope(capa.rules.Scope.INSTRUCTION)) == capa_pb2.SCOPE_INSTRUCTION
def cmp_optional(a: Any, b: Any) -> bool: def cmp_optional(a: Any, b: Any) -> bool:

View File

@@ -40,7 +40,7 @@ ADDR4 = capa.features.address.AbsoluteVirtualAddress(0x401004)
def test_rule_ctor(): def test_rule_ctor():
r = capa.rules.Rule( r = capa.rules.Rule(
"test rule", capa.rules.Scopes(capa.rules.FUNCTION_SCOPE, capa.rules.FILE_SCOPE), Or([Number(1)]), {} "test rule", capa.rules.Scopes(capa.rules.Scope.FUNCTION, capa.rules.Scope.FILE), Or([Number(1)]), {}
) )
assert bool(r.evaluate({Number(0): {ADDR1}})) is False assert bool(r.evaluate({Number(0): {ADDR1}})) is False
assert bool(r.evaluate({Number(1): {ADDR2}})) is True assert bool(r.evaluate({Number(1): {ADDR2}})) is True