mirror of
https://github.com/mandiant/capa.git
synced 2025-12-12 15:49:46 -08:00
Merge pull request #1671 from yelhamer/rule-statement-building
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- Add support for flavor-based rule scopes @yelhamer
|
||||
- Add ProcessesAddress and ThreadAddress #1612 @yelhamer
|
||||
- Add dynamic capability extraction @yelhamer
|
||||
- Add support for mixed-scopes rules @yelhamer
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
|
||||
@@ -458,6 +458,13 @@ FORMAT_AUTO = "auto"
|
||||
FORMAT_SC32 = "sc32"
|
||||
FORMAT_SC64 = "sc64"
|
||||
FORMAT_CAPE = "cape"
|
||||
STATIC_FORMATS = (
|
||||
FORMAT_SC32,
|
||||
FORMAT_SC64,
|
||||
FORMAT_PE,
|
||||
FORMAT_ELF,
|
||||
FORMAT_DOTNET,
|
||||
)
|
||||
DYNAMIC_FORMATS = (FORMAT_CAPE,)
|
||||
FORMAT_FREEZE = "freeze"
|
||||
FORMAT_RESULT = "result"
|
||||
|
||||
32
capa/main.py
32
capa/main.py
@@ -20,6 +20,7 @@ import textwrap
|
||||
import itertools
|
||||
import contextlib
|
||||
import collections
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Tuple, Callable, Optional
|
||||
from pathlib import Path
|
||||
|
||||
@@ -78,6 +79,8 @@ from capa.features.common import (
|
||||
FORMAT_DOTNET,
|
||||
FORMAT_FREEZE,
|
||||
FORMAT_RESULT,
|
||||
STATIC_FORMATS,
|
||||
DYNAMIC_FORMATS,
|
||||
)
|
||||
from capa.features.address import NO_ADDRESS, Address
|
||||
from capa.features.extractors.base_extractor import (
|
||||
@@ -113,6 +116,15 @@ E_UNSUPPORTED_IDA_VERSION = 19
|
||||
logger = logging.getLogger("capa")
|
||||
|
||||
|
||||
class ExecutionContext(str, Enum):
|
||||
STATIC = "static"
|
||||
DYNAMIC = "dynamic"
|
||||
|
||||
|
||||
STATIC_CONTEXT = ExecutionContext.STATIC
|
||||
DYNAMIC_CONTEXT = ExecutionContext.DYNAMIC
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def timing(msg: str):
|
||||
t0 = time.time()
|
||||
@@ -823,6 +835,7 @@ def get_rules(
|
||||
rule_paths: List[RulePath],
|
||||
cache_dir=None,
|
||||
on_load_rule: Callable[[RulePath, int, int], None] = on_load_rule_default,
|
||||
analysis_context: Optional[ExecutionContext] = None,
|
||||
) -> RuleSet:
|
||||
"""
|
||||
args:
|
||||
@@ -861,7 +874,14 @@ def get_rules(
|
||||
rules.append(rule)
|
||||
logger.debug("loaded rule: '%s' with scope: %s", rule.name, rule.scopes)
|
||||
|
||||
ruleset = capa.rules.RuleSet(rules)
|
||||
# filter rules according to the execution context
|
||||
if analysis_context is STATIC_CONTEXT:
|
||||
ruleset = capa.rules.RuleSet(rules, rules_filter_func=lambda rule: rule.scopes.static)
|
||||
elif analysis_context is DYNAMIC_CONTEXT:
|
||||
ruleset = capa.rules.RuleSet(rules, rules_filter_func=lambda rule: rule.scopes.dynamic)
|
||||
else:
|
||||
# default: load all rules
|
||||
ruleset = capa.rules.RuleSet(rules)
|
||||
|
||||
capa.rules.cache.cache_ruleset(cache_dir, ruleset)
|
||||
|
||||
@@ -1382,7 +1402,15 @@ def main(argv: Optional[List[str]] = None):
|
||||
else:
|
||||
cache_dir = capa.rules.cache.get_default_cache_directory()
|
||||
|
||||
rules = get_rules(args.rules, cache_dir=cache_dir)
|
||||
if format_ in STATIC_FORMATS:
|
||||
analysis_context = STATIC_CONTEXT
|
||||
elif format_ in DYNAMIC_FORMATS:
|
||||
analysis_context = DYNAMIC_CONTEXT
|
||||
else:
|
||||
# freeze or result formats
|
||||
analysis_context = None
|
||||
|
||||
rules = get_rules(args.rules, cache_dir=cache_dir, analysis_context=analysis_context)
|
||||
|
||||
logger.debug(
|
||||
"successfully loaded %s rules",
|
||||
|
||||
@@ -25,7 +25,7 @@ except ImportError:
|
||||
# https://github.com/python/mypy/issues/1153
|
||||
from backports.functools_lru_cache import lru_cache # type: ignore
|
||||
|
||||
from typing import Any, Set, Dict, List, Tuple, Union, Iterator
|
||||
from typing import Any, Set, Dict, List, Tuple, Union, Iterator, Optional
|
||||
from dataclasses import asdict, dataclass
|
||||
|
||||
import yaml
|
||||
@@ -91,7 +91,6 @@ INSTRUCTION_SCOPE = Scope.INSTRUCTION.value
|
||||
# used only to specify supported features per scope.
|
||||
# not used to validate rules.
|
||||
GLOBAL_SCOPE = "global"
|
||||
DEV_SCOPE = "dev"
|
||||
|
||||
|
||||
# these literals are used to check if the flavor
|
||||
@@ -108,29 +107,56 @@ DYNAMIC_SCOPES = (
|
||||
GLOBAL_SCOPE,
|
||||
PROCESS_SCOPE,
|
||||
THREAD_SCOPE,
|
||||
DEV_SCOPE,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Scopes:
|
||||
static: str
|
||||
dynamic: str
|
||||
static: Optional[str] = None
|
||||
dynamic: Optional[str] = None
|
||||
|
||||
def __contains__(self, scope: Union[Scope, str]) -> bool:
|
||||
assert isinstance(scope, (Scope, str))
|
||||
return (scope == self.static) or (scope == self.dynamic)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.static and self.dynamic:
|
||||
return f"static-scope: {self.static}, dyanamic-scope: {self.dynamic}"
|
||||
elif self.static:
|
||||
return f"static-scope: {self.static}"
|
||||
elif self.dynamic:
|
||||
return f"dynamic-scope: {self.dynamic}"
|
||||
else:
|
||||
raise ValueError("invalid rules class. at least one scope must be specified")
|
||||
|
||||
@classmethod
|
||||
def from_dict(self, scopes: dict) -> "Scopes":
|
||||
assert isinstance(scopes, dict)
|
||||
# mark non-specified scopes as invalid
|
||||
if "static" not in scopes:
|
||||
scopes["static"] = None
|
||||
if "dynamic" not in scopes:
|
||||
scopes["dynamic"] = None
|
||||
|
||||
# check the syntax of the meta `scopes` field
|
||||
if sorted(scopes) != ["dynamic", "static"]:
|
||||
raise InvalidRule("scope flavors can be either static or dynamic")
|
||||
if scopes["static"] not in STATIC_SCOPES:
|
||||
if (not scopes["static"]) and (not scopes["dynamic"]):
|
||||
raise InvalidRule("invalid scopes value. At least one scope must be specified")
|
||||
|
||||
# check that all the specified scopes are valid
|
||||
if scopes["static"] not in (
|
||||
*STATIC_SCOPES,
|
||||
None,
|
||||
):
|
||||
raise InvalidRule(f"{scopes['static']} is not a valid static scope")
|
||||
if scopes["dynamic"] not in DYNAMIC_SCOPES:
|
||||
raise InvalidRule(f"{scopes['dynamic']} is not a valid dynamicscope")
|
||||
return Scopes(scopes["static"], scopes["dynamic"])
|
||||
if scopes["dynamic"] not in (
|
||||
*DYNAMIC_SCOPES,
|
||||
None,
|
||||
):
|
||||
raise InvalidRule(f"{scopes['dynamic']} is not a valid dynamic scope")
|
||||
|
||||
return Scopes(static=scopes["static"], dynamic=scopes["dynamic"])
|
||||
|
||||
|
||||
SUPPORTED_FEATURES: Dict[str, Set] = {
|
||||
@@ -205,12 +231,6 @@ SUPPORTED_FEATURES: Dict[str, Set] = {
|
||||
capa.features.common.Class,
|
||||
capa.features.common.Namespace,
|
||||
},
|
||||
DEV_SCOPE: {
|
||||
# TODO(yelhamer): this is a temporary scope. remove it after support
|
||||
# for the legacy scope keyword has been added (to rendering).
|
||||
# https://github.com/mandiant/capa/pull/1580
|
||||
capa.features.insn.API,
|
||||
},
|
||||
}
|
||||
|
||||
# global scope features are available in all other scopes
|
||||
@@ -227,10 +247,6 @@ SUPPORTED_FEATURES[PROCESS_SCOPE].update(SUPPORTED_FEATURES[THREAD_SCOPE])
|
||||
SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE].update(SUPPORTED_FEATURES[INSTRUCTION_SCOPE])
|
||||
# all basic block scope features are also function scope features
|
||||
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE])
|
||||
# dynamic-dev scope contains all features
|
||||
SUPPORTED_FEATURES[DEV_SCOPE].update(SUPPORTED_FEATURES[FILE_SCOPE])
|
||||
SUPPORTED_FEATURES[DEV_SCOPE].update(SUPPORTED_FEATURES[FUNCTION_SCOPE])
|
||||
SUPPORTED_FEATURES[DEV_SCOPE].update(SUPPORTED_FEATURES[PROCESS_SCOPE])
|
||||
|
||||
|
||||
class InvalidRule(ValueError):
|
||||
@@ -268,22 +284,29 @@ class InvalidRuleSet(ValueError):
|
||||
return str(self)
|
||||
|
||||
|
||||
def ensure_feature_valid_for_scope(scope: str, feature: Union[Feature, Statement]):
|
||||
def ensure_feature_valid_for_scopes(scopes: Scopes, feature: Union[Feature, Statement]):
|
||||
# construct a dict of all supported features
|
||||
supported_features: Set = set()
|
||||
if scopes.static:
|
||||
supported_features.update(SUPPORTED_FEATURES[scopes.static])
|
||||
if scopes.dynamic:
|
||||
supported_features.update(SUPPORTED_FEATURES[scopes.dynamic])
|
||||
|
||||
# if the given feature is a characteristic,
|
||||
# check that is a valid characteristic for the given scope.
|
||||
if (
|
||||
isinstance(feature, capa.features.common.Characteristic)
|
||||
and isinstance(feature.value, str)
|
||||
and capa.features.common.Characteristic(feature.value) not in SUPPORTED_FEATURES[scope]
|
||||
and capa.features.common.Characteristic(feature.value) not in supported_features
|
||||
):
|
||||
raise InvalidRule(f"feature {feature} not supported for scope {scope}")
|
||||
raise InvalidRule(f"feature {feature} not supported for scopes {scopes}")
|
||||
|
||||
if not isinstance(feature, capa.features.common.Characteristic):
|
||||
# features of this scope that are not Characteristics will be Type instances.
|
||||
# check that the given feature is one of these types.
|
||||
types_for_scope = filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope])
|
||||
types_for_scope = filter(lambda t: isinstance(t, type), supported_features)
|
||||
if not isinstance(feature, tuple(types_for_scope)): # type: ignore
|
||||
raise InvalidRule(f"feature {feature} not supported for scope {scope}")
|
||||
raise InvalidRule(f"feature {feature} not supported for scopes {scopes}")
|
||||
|
||||
|
||||
def parse_int(s: str) -> int:
|
||||
@@ -491,71 +514,79 @@ def pop_statement_description_entry(d):
|
||||
return description["description"]
|
||||
|
||||
|
||||
def build_statements(d, scope: str):
|
||||
def build_statements(d, scopes: Scopes):
|
||||
if len(d.keys()) > 2:
|
||||
raise InvalidRule("too many statements")
|
||||
|
||||
key = list(d.keys())[0]
|
||||
description = pop_statement_description_entry(d[key])
|
||||
if key == "and":
|
||||
return ceng.And([build_statements(dd, scope) for dd in d[key]], description=description)
|
||||
return ceng.And([build_statements(dd, scopes) for dd in d[key]], description=description)
|
||||
elif key == "or":
|
||||
return ceng.Or([build_statements(dd, scope) for dd in d[key]], description=description)
|
||||
return ceng.Or([build_statements(dd, scopes) for dd in d[key]], description=description)
|
||||
elif key == "not":
|
||||
if len(d[key]) != 1:
|
||||
raise InvalidRule("not statement must have exactly one child statement")
|
||||
return ceng.Not(build_statements(d[key][0], scope), description=description)
|
||||
return ceng.Not(build_statements(d[key][0], scopes), description=description)
|
||||
elif key.endswith(" or more"):
|
||||
count = int(key[: -len("or more")])
|
||||
return ceng.Some(count, [build_statements(dd, scope) for dd in d[key]], description=description)
|
||||
return ceng.Some(count, [build_statements(dd, scopes) for dd in d[key]], description=description)
|
||||
elif key == "optional":
|
||||
# `optional` is an alias for `0 or more`
|
||||
# which is useful for documenting behaviors,
|
||||
# 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, scopes) for dd in d[key]], description=description)
|
||||
|
||||
elif key == "process":
|
||||
if scope != FILE_SCOPE:
|
||||
if FILE_SCOPE not in scopes:
|
||||
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)
|
||||
return ceng.Subscope(
|
||||
PROCESS_SCOPE, build_statements(d[key][0], Scopes(dynamic=PROCESS_SCOPE)), description=description
|
||||
)
|
||||
|
||||
elif key == "thread":
|
||||
if scope not in (PROCESS_SCOPE, FILE_SCOPE):
|
||||
if (PROCESS_SCOPE not in scopes) and (FILE_SCOPE not in scopes):
|
||||
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)
|
||||
return ceng.Subscope(
|
||||
THREAD_SCOPE, build_statements(d[key][0], Scopes(dynamic=THREAD_SCOPE)), description=description
|
||||
)
|
||||
|
||||
elif key == "function":
|
||||
if scope not in (FILE_SCOPE, DEV_SCOPE):
|
||||
if FILE_SCOPE not in scopes:
|
||||
raise InvalidRule("function subscope supported only for file scope")
|
||||
|
||||
if len(d[key]) != 1:
|
||||
raise InvalidRule("subscope must have exactly one child statement")
|
||||
|
||||
return ceng.Subscope(FUNCTION_SCOPE, build_statements(d[key][0], FUNCTION_SCOPE), description=description)
|
||||
return ceng.Subscope(
|
||||
FUNCTION_SCOPE, build_statements(d[key][0], Scopes(static=FUNCTION_SCOPE)), description=description
|
||||
)
|
||||
|
||||
elif key == "basic block":
|
||||
if scope not in (FUNCTION_SCOPE, DEV_SCOPE):
|
||||
if FUNCTION_SCOPE not in scopes:
|
||||
raise InvalidRule("basic block subscope supported only for function scope")
|
||||
|
||||
if len(d[key]) != 1:
|
||||
raise InvalidRule("subscope must have exactly one child statement")
|
||||
|
||||
return ceng.Subscope(BASIC_BLOCK_SCOPE, build_statements(d[key][0], BASIC_BLOCK_SCOPE), description=description)
|
||||
return ceng.Subscope(
|
||||
BASIC_BLOCK_SCOPE, build_statements(d[key][0], Scopes(static=BASIC_BLOCK_SCOPE)), description=description
|
||||
)
|
||||
|
||||
elif key == "instruction":
|
||||
if scope not in (FUNCTION_SCOPE, BASIC_BLOCK_SCOPE, DEV_SCOPE):
|
||||
if all(s not in scopes for s in (FUNCTION_SCOPE, BASIC_BLOCK_SCOPE)):
|
||||
raise InvalidRule("instruction subscope supported only for function and basic block scope")
|
||||
|
||||
if len(d[key]) == 1:
|
||||
statements = build_statements(d[key][0], INSTRUCTION_SCOPE)
|
||||
statements = build_statements(d[key][0], Scopes(static=INSTRUCTION_SCOPE))
|
||||
else:
|
||||
# for instruction subscopes, we support a shorthand in which the top level AND is implied.
|
||||
# the following are equivalent:
|
||||
@@ -569,7 +600,7 @@ def build_statements(d, scope: str):
|
||||
# - arch: i386
|
||||
# - mnemonic: cmp
|
||||
#
|
||||
statements = ceng.And([build_statements(dd, INSTRUCTION_SCOPE) for dd in d[key]])
|
||||
statements = ceng.And([build_statements(dd, Scopes(static=INSTRUCTION_SCOPE)) for dd in d[key]])
|
||||
|
||||
return ceng.Subscope(INSTRUCTION_SCOPE, statements, description=description)
|
||||
|
||||
@@ -610,7 +641,7 @@ def build_statements(d, scope: str):
|
||||
feature = Feature(arg)
|
||||
else:
|
||||
feature = Feature()
|
||||
ensure_feature_valid_for_scope(scope, feature)
|
||||
ensure_feature_valid_for_scopes(scopes, feature)
|
||||
|
||||
count = d[key]
|
||||
if isinstance(count, int):
|
||||
@@ -644,7 +675,7 @@ def build_statements(d, scope: str):
|
||||
feature = capa.features.insn.OperandNumber(index, value, description=description)
|
||||
except ValueError as e:
|
||||
raise InvalidRule(str(e)) from e
|
||||
ensure_feature_valid_for_scope(scope, feature)
|
||||
ensure_feature_valid_for_scopes(scopes, feature)
|
||||
return feature
|
||||
|
||||
elif key.startswith("operand[") and key.endswith("].offset"):
|
||||
@@ -660,7 +691,7 @@ def build_statements(d, scope: str):
|
||||
feature = capa.features.insn.OperandOffset(index, value, description=description)
|
||||
except ValueError as e:
|
||||
raise InvalidRule(str(e)) from e
|
||||
ensure_feature_valid_for_scope(scope, feature)
|
||||
ensure_feature_valid_for_scopes(scopes, feature)
|
||||
return feature
|
||||
|
||||
elif (
|
||||
@@ -680,7 +711,7 @@ def build_statements(d, scope: str):
|
||||
feature = capa.features.insn.Property(value, access=access, description=description)
|
||||
except ValueError as e:
|
||||
raise InvalidRule(str(e)) from e
|
||||
ensure_feature_valid_for_scope(scope, feature)
|
||||
ensure_feature_valid_for_scopes(scopes, feature)
|
||||
return feature
|
||||
|
||||
else:
|
||||
@@ -690,7 +721,7 @@ def build_statements(d, scope: str):
|
||||
feature = Feature(value, description=description)
|
||||
except ValueError as e:
|
||||
raise InvalidRule(str(e)) from e
|
||||
ensure_feature_valid_for_scope(scope, feature)
|
||||
ensure_feature_valid_for_scopes(scopes, feature)
|
||||
return feature
|
||||
|
||||
|
||||
@@ -773,13 +804,19 @@ class Rule:
|
||||
# the name is a randomly generated, hopefully unique value.
|
||||
# ideally, this won't every be rendered to a user.
|
||||
name = self.name + "/" + uuid.uuid4().hex
|
||||
if subscope.scope in STATIC_SCOPES:
|
||||
scopes = Scopes(static=subscope.scope)
|
||||
elif subscope.scope in DYNAMIC_SCOPES:
|
||||
scopes = Scopes(dynamic=subscope.scope)
|
||||
else:
|
||||
raise InvalidRule(f"scope {subscope.scope} is not a valid subscope")
|
||||
new_rule = Rule(
|
||||
name,
|
||||
Scopes(subscope.scope, DEV_SCOPE),
|
||||
scopes,
|
||||
subscope.child,
|
||||
{
|
||||
"name": name,
|
||||
"scopes": asdict(Scopes(subscope.scope, DEV_SCOPE)),
|
||||
"scopes": asdict(scopes),
|
||||
# these derived rules are never meant to be inspected separately,
|
||||
# they are dependencies for the parent rule,
|
||||
# so mark it as such.
|
||||
@@ -843,7 +880,16 @@ class Rule:
|
||||
# this is probably the mode that rule authors will start with.
|
||||
# each rule has two scopes, a static-flavor scope, and a
|
||||
# dynamic-flavor one. which one is used depends on the analysis type.
|
||||
scopes: Scopes = Scopes.from_dict(meta.get("scopes", {"static": "function", "dynamic": "dev"}))
|
||||
if "scope" in meta:
|
||||
raise InvalidRule("rule is in legacy mode (has scope meta field). please update to the new syntax.")
|
||||
elif "scopes" in meta:
|
||||
scopes_ = meta.get("scopes")
|
||||
else:
|
||||
raise InvalidRule("please specify at least one of this rule's (static/dynamic) scopes")
|
||||
if not isinstance(scopes_, dict):
|
||||
raise InvalidRule("the scopes field must contain a dictionary specifying the scopes")
|
||||
|
||||
scopes: Scopes = Scopes.from_dict(scopes_)
|
||||
statements = d["rule"]["features"]
|
||||
|
||||
# the rule must start with a single logic node.
|
||||
@@ -860,14 +906,7 @@ class Rule:
|
||||
if not isinstance(meta.get("mbc", []), list):
|
||||
raise InvalidRule("MBC mapping must be a list")
|
||||
|
||||
# TODO(yelhamer): once we've decided on the desired format for mixed-scope statements,
|
||||
# we should go back and update this accordingly to either:
|
||||
# - generate one englobing statement.
|
||||
# - generate two respective statements and store them approriately
|
||||
# https://github.com/mandiant/capa/pull/1580
|
||||
statement = build_statements(statements[0], scopes.static)
|
||||
_ = build_statements(statements[0], scopes.dynamic)
|
||||
return cls(name, scopes, statement, meta, definition)
|
||||
return cls(name, scopes, build_statements(statements[0], scopes), meta, definition)
|
||||
|
||||
@staticmethod
|
||||
@lru_cache()
|
||||
@@ -1173,7 +1212,11 @@ class RuleSet:
|
||||
capa.engine.match(ruleset.file_rules, ...)
|
||||
"""
|
||||
|
||||
def __init__(self, rules: List[Rule]):
|
||||
def __init__(
|
||||
self,
|
||||
rules: List[Rule],
|
||||
rules_filter_func=None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
ensure_rules_are_unique(rules)
|
||||
@@ -1190,6 +1233,11 @@ class RuleSet:
|
||||
|
||||
ensure_rule_dependencies_are_met(rules)
|
||||
|
||||
if rules_filter_func:
|
||||
# this allows for filtering the ruleset based on
|
||||
# the execution context (static or dynamic)
|
||||
rules = list(filter(rules_filter_func, rules))
|
||||
|
||||
if len(rules) == 0:
|
||||
raise InvalidRuleSet("no rules selected")
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ EXPECTED = textwrap.dedent(
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
@@ -45,7 +45,7 @@ def test_rule_reformat_top_level_elements():
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
@@ -65,7 +65,7 @@ def test_rule_reformat_indentation():
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
@@ -91,7 +91,7 @@ def test_rule_reformat_order():
|
||||
- bar5678
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
name: test rule
|
||||
features:
|
||||
- and:
|
||||
@@ -117,7 +117,7 @@ def test_rule_reformat_meta_update():
|
||||
- bar5678
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
name: AAAA
|
||||
features:
|
||||
- and:
|
||||
@@ -143,7 +143,7 @@ def test_rule_reformat_string_description():
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: foo
|
||||
|
||||
@@ -38,7 +38,7 @@ def test_main_single_rule(z9324d_extractor, tmpdir):
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
authors:
|
||||
- test
|
||||
features:
|
||||
@@ -103,7 +103,7 @@ def test_ruleset():
|
||||
name: file rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: embedded pe
|
||||
"""
|
||||
@@ -117,7 +117,7 @@ def test_ruleset():
|
||||
name: function rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: tight loop
|
||||
"""
|
||||
@@ -131,7 +131,7 @@ def test_ruleset():
|
||||
name: basic block rule
|
||||
scopes:
|
||||
static: basic block
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: nzxor
|
||||
"""
|
||||
@@ -170,7 +170,7 @@ def test_ruleset():
|
||||
assert len(rules.file_rules) == 2
|
||||
assert len(rules.function_rules) == 2
|
||||
assert len(rules.basic_block_rules) == 1
|
||||
assert len(rules.process_rules) == 1
|
||||
assert len(rules.process_rules) == 4
|
||||
assert len(rules.thread_rules) == 1
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ def test_match_across_scopes_file_function(z9324d_extractor):
|
||||
name: install service
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a:0x4073F0
|
||||
features:
|
||||
@@ -206,7 +206,7 @@ def test_match_across_scopes_file_function(z9324d_extractor):
|
||||
name: .text section
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a
|
||||
features:
|
||||
@@ -225,7 +225,7 @@ def test_match_across_scopes_file_function(z9324d_extractor):
|
||||
name: .text section and install service
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a
|
||||
features:
|
||||
@@ -255,7 +255,7 @@ def test_match_across_scopes(z9324d_extractor):
|
||||
name: tight loop
|
||||
scopes:
|
||||
static: basic block
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a:0x403685
|
||||
features:
|
||||
@@ -273,7 +273,7 @@ def test_match_across_scopes(z9324d_extractor):
|
||||
name: kill thread loop
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a:0x403660
|
||||
features:
|
||||
@@ -293,7 +293,7 @@ def test_match_across_scopes(z9324d_extractor):
|
||||
name: kill thread program
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- 9324d1a8ae37a36ae560c37448c9705a
|
||||
features:
|
||||
@@ -322,7 +322,7 @@ def test_subscope_bb_rules(z9324d_extractor):
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- basic block:
|
||||
@@ -348,7 +348,7 @@ def test_byte_matching(z9324d_extractor):
|
||||
name: byte match test
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- bytes: ED 24 9E F4 52 A9 07 47 55 8E E1 AB 30 8E 23 61
|
||||
@@ -373,7 +373,7 @@ def test_count_bb(z9324d_extractor):
|
||||
namespace: test
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- count(basic blocks): 1 or more
|
||||
@@ -399,7 +399,7 @@ def test_instruction_scope(z9324d_extractor):
|
||||
namespace: test
|
||||
scopes:
|
||||
static: instruction
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- mnemonic: push
|
||||
@@ -429,7 +429,7 @@ def test_instruction_subscope(z9324d_extractor):
|
||||
namespace: test
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- arch: i386
|
||||
|
||||
@@ -43,6 +43,9 @@ def test_match_simple():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: testns1/testns2
|
||||
features:
|
||||
- number: 100
|
||||
@@ -63,6 +66,9 @@ def test_match_range_exact():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): 2
|
||||
"""
|
||||
@@ -87,7 +93,10 @@ def test_match_range_range():
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): (2, 3)
|
||||
"""
|
||||
@@ -117,6 +126,9 @@ def test_match_range_exact_zero():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): 0
|
||||
"""
|
||||
@@ -142,7 +154,10 @@ def test_match_range_with_zero():
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): (0, 1)
|
||||
"""
|
||||
@@ -169,6 +184,9 @@ def test_match_adds_matched_rule_feature():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- number: 100
|
||||
"""
|
||||
@@ -187,6 +205,9 @@ def test_match_matched_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- number: 100
|
||||
"""
|
||||
@@ -198,6 +219,9 @@ def test_match_matched_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule2
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: test rule1
|
||||
"""
|
||||
@@ -232,6 +256,9 @@ def test_match_namespace():
|
||||
rule:
|
||||
meta:
|
||||
name: CreateFile API
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: file/create/CreateFile
|
||||
features:
|
||||
- api: CreateFile
|
||||
@@ -244,6 +271,9 @@ def test_match_namespace():
|
||||
rule:
|
||||
meta:
|
||||
name: WriteFile API
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: file/write
|
||||
features:
|
||||
- api: WriteFile
|
||||
@@ -256,6 +286,9 @@ def test_match_namespace():
|
||||
rule:
|
||||
meta:
|
||||
name: file-create
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: file/create
|
||||
"""
|
||||
@@ -267,6 +300,9 @@ def test_match_namespace():
|
||||
rule:
|
||||
meta:
|
||||
name: filesystem-any
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: file
|
||||
"""
|
||||
@@ -304,6 +340,9 @@ def test_match_substring():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- substring: abc
|
||||
@@ -355,6 +394,9 @@ def test_match_regex():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: /.*bbbb.*/
|
||||
@@ -367,6 +409,9 @@ def test_match_regex():
|
||||
rule:
|
||||
meta:
|
||||
name: rule with implied wildcards
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: /bbbb/
|
||||
@@ -379,6 +424,9 @@ def test_match_regex():
|
||||
rule:
|
||||
meta:
|
||||
name: rule with anchor
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: /^bbbb/
|
||||
@@ -425,6 +473,9 @@ def test_match_regex_ignorecase():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: /.*bbbb.*/i
|
||||
@@ -448,6 +499,9 @@ def test_match_regex_complex():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- string: /.*HARDWARE\\Key\\key with spaces\\.*/i
|
||||
@@ -471,6 +525,9 @@ def test_match_regex_values_always_string():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- string: /123/
|
||||
@@ -500,6 +557,9 @@ def test_match_not():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: testns1/testns2
|
||||
features:
|
||||
- not:
|
||||
@@ -518,6 +578,9 @@ def test_match_not_not():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: testns1/testns2
|
||||
features:
|
||||
- not:
|
||||
@@ -537,6 +600,9 @@ def test_match_operand_number():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- operand[0].number: 0x10
|
||||
@@ -564,6 +630,9 @@ def test_match_operand_offset():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- operand[0].offset: 0x10
|
||||
@@ -591,6 +660,9 @@ def test_match_property_access():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- property/read: System.IO.FileInfo::Length
|
||||
@@ -632,6 +704,9 @@ def test_match_os_any():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- and:
|
||||
|
||||
@@ -25,7 +25,7 @@ def test_optimizer_order():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- substring: "foo"
|
||||
|
||||
@@ -52,7 +52,7 @@ def test_render_meta_attack():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
authors:
|
||||
- foo
|
||||
att&ck:
|
||||
@@ -90,7 +90,7 @@ def test_render_meta_mbc():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
authors:
|
||||
- foo
|
||||
mbc:
|
||||
|
||||
@@ -22,7 +22,7 @@ R1 = capa.rules.Rule.from_yaml(
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
@@ -44,7 +44,7 @@ R2 = capa.rules.Rule.from_yaml(
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
|
||||
@@ -56,7 +56,7 @@ def test_rule_yaml():
|
||||
- user@domain.com
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
examples:
|
||||
- foo1234
|
||||
- bar5678
|
||||
@@ -79,6 +79,9 @@ def test_rule_yaml_complex():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- and:
|
||||
@@ -103,6 +106,9 @@ def test_rule_descriptions():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- description: and description
|
||||
@@ -147,6 +153,9 @@ def test_invalid_rule_statement_descriptions():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- number: 1 = This is the number 1
|
||||
@@ -163,6 +172,9 @@ def test_rule_yaml_not():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- number: 1
|
||||
@@ -181,6 +193,9 @@ def test_rule_yaml_count():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): 1
|
||||
"""
|
||||
@@ -197,6 +212,9 @@ def test_rule_yaml_count_range():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(number(100)): (1, 2)
|
||||
"""
|
||||
@@ -214,6 +232,9 @@ def test_rule_yaml_count_string():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- count(string(foo)): 2
|
||||
"""
|
||||
@@ -233,6 +254,9 @@ def test_invalid_rule_feature():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- foo: true
|
||||
"""
|
||||
@@ -248,7 +272,7 @@ def test_invalid_rule_feature():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: nzxor
|
||||
"""
|
||||
@@ -264,7 +288,7 @@ def test_invalid_rule_feature():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: thread
|
||||
features:
|
||||
- characteristic: embedded pe
|
||||
"""
|
||||
@@ -280,28 +304,93 @@ def test_invalid_rule_feature():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: basic block
|
||||
dynamic: dev
|
||||
dynamic: thread
|
||||
features:
|
||||
- characteristic: embedded pe
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(capa.rules.InvalidRule):
|
||||
|
||||
def test_multi_scope_rules_features():
|
||||
_ = capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- api: write
|
||||
- and:
|
||||
- os: linux
|
||||
- mnemonic: syscall
|
||||
- number: 1 = write
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
_ = capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- api: read
|
||||
- and:
|
||||
- os: linux
|
||||
- mnemonic: syscall
|
||||
- number: 0 = read
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_rules_flavor_filtering():
|
||||
rules = [
|
||||
capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
name: static rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- mnemonic: xor
|
||||
- api: CreateFileA
|
||||
"""
|
||||
)
|
||||
)
|
||||
),
|
||||
capa.rules.Rule.from_yaml(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
rule:
|
||||
meta:
|
||||
name: dynamic rule
|
||||
scopes:
|
||||
dynamic: thread
|
||||
features:
|
||||
- api: CreateFileA
|
||||
"""
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
static_rules = capa.rules.RuleSet(rules.copy(), rules_filter_func=lambda rule: rule.scopes.static)
|
||||
dynamic_rules = capa.rules.RuleSet(rules, rules_filter_func=lambda rule: rule.scopes.dynamic)
|
||||
|
||||
# only static rule
|
||||
assert len(static_rules) == 1
|
||||
# only dynamic rule
|
||||
assert len(dynamic_rules) == 1
|
||||
|
||||
|
||||
def test_lib_rules():
|
||||
@@ -313,6 +402,9 @@ def test_lib_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: a lib rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
lib: true
|
||||
features:
|
||||
- api: CreateFileA
|
||||
@@ -325,6 +417,9 @@ def test_lib_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: a standard rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
lib: false
|
||||
features:
|
||||
- api: CreateFileW
|
||||
@@ -348,7 +443,7 @@ def test_subscope_rules():
|
||||
name: test function subscope
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- characteristic: embedded pe
|
||||
@@ -407,7 +502,7 @@ def test_subscope_rules():
|
||||
|
||||
# the process rule scope has three rules:
|
||||
# - the rule on which `test process subscope` depends,
|
||||
assert len(rules.process_rules) == 2
|
||||
assert len(rules.process_rules) == 3
|
||||
|
||||
# the thread rule scope has one rule:
|
||||
# - the rule on which `test thread subscope` depends
|
||||
@@ -424,6 +519,9 @@ def test_duplicate_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: rule-name
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- api: CreateFileA
|
||||
"""
|
||||
@@ -435,6 +533,9 @@ def test_duplicate_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: rule-name
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- api: CreateFileW
|
||||
"""
|
||||
@@ -454,6 +555,9 @@ def test_missing_dependency():
|
||||
rule:
|
||||
meta:
|
||||
name: dependent rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: missing rule
|
||||
"""
|
||||
@@ -471,6 +575,9 @@ def test_invalid_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: number(1)
|
||||
"""
|
||||
@@ -484,6 +591,9 @@ def test_invalid_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- characteristic: count(number(100))
|
||||
"""
|
||||
@@ -498,6 +608,9 @@ def test_invalid_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
att&ck: Tactic::Technique::Subtechnique [Identifier]
|
||||
features:
|
||||
- number: 1
|
||||
@@ -511,6 +624,9 @@ def test_invalid_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
mbc: Objective::Behavior::Method [Identifier]
|
||||
features:
|
||||
- number: 1
|
||||
@@ -585,6 +701,9 @@ def test_number_symbol():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- number: 1
|
||||
@@ -612,6 +731,9 @@ def test_count_number_symbol():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- count(number(2 = symbol name)): 1
|
||||
@@ -635,6 +757,9 @@ def test_invalid_number():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- number: "this is a string"
|
||||
"""
|
||||
@@ -648,6 +773,9 @@ def test_invalid_number():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- number: 2=
|
||||
"""
|
||||
@@ -661,6 +789,9 @@ def test_invalid_number():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- number: symbol name = 2
|
||||
"""
|
||||
@@ -674,6 +805,9 @@ def test_offset_symbol():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- offset: 1
|
||||
@@ -698,6 +832,9 @@ def test_count_offset_symbol():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- count(offset(2 = symbol name)): 1
|
||||
@@ -721,6 +858,9 @@ def test_invalid_offset():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- offset: "this is a string"
|
||||
"""
|
||||
@@ -734,6 +874,9 @@ def test_invalid_offset():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- offset: 2=
|
||||
"""
|
||||
@@ -747,6 +890,9 @@ def test_invalid_offset():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- offset: symbol name = 2
|
||||
"""
|
||||
@@ -762,6 +908,9 @@ def test_invalid_string_values_int():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- string: 123
|
||||
"""
|
||||
@@ -775,6 +924,9 @@ def test_invalid_string_values_int():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- string: 0x123
|
||||
"""
|
||||
@@ -788,6 +940,9 @@ def test_explicit_string_values_int():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- string: "123"
|
||||
@@ -806,6 +961,9 @@ def test_string_values_special_characters():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- string: "hello\\r\\nworld"
|
||||
@@ -825,6 +983,9 @@ def test_substring_feature():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- substring: abc
|
||||
@@ -845,6 +1006,9 @@ def test_substring_description():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- substring: abc
|
||||
@@ -865,6 +1029,9 @@ def test_filter_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
authors:
|
||||
- joe
|
||||
features:
|
||||
@@ -878,6 +1045,9 @@ def test_filter_rules():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 2
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- string: joe
|
||||
"""
|
||||
@@ -899,6 +1069,9 @@ def test_filter_rules_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: rule 2
|
||||
"""
|
||||
@@ -910,6 +1083,9 @@ def test_filter_rules_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 2
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: rule 3
|
||||
"""
|
||||
@@ -921,6 +1097,9 @@ def test_filter_rules_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 3
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- api: CreateFile
|
||||
"""
|
||||
@@ -945,6 +1124,9 @@ def test_filter_rules_missing_dependency():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
authors:
|
||||
- joe
|
||||
features:
|
||||
@@ -964,6 +1146,9 @@ def test_rules_namespace_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: ns1/nsA
|
||||
features:
|
||||
- api: CreateFile
|
||||
@@ -976,6 +1161,9 @@ def test_rules_namespace_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 2
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
namespace: ns1/nsB
|
||||
features:
|
||||
- api: CreateFile
|
||||
@@ -988,6 +1176,9 @@ def test_rules_namespace_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 3
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: ns1/nsA
|
||||
"""
|
||||
@@ -999,6 +1190,9 @@ def test_rules_namespace_dependencies():
|
||||
rule:
|
||||
meta:
|
||||
name: rule 4
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- match: ns1
|
||||
"""
|
||||
@@ -1025,7 +1219,7 @@ def test_function_name_features():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- function-name: strcpy
|
||||
@@ -1049,7 +1243,7 @@ def test_os_features():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- os: windows
|
||||
@@ -1069,7 +1263,7 @@ def test_format_features():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- format: pe
|
||||
@@ -1089,7 +1283,7 @@ def test_arch_features():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: file
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- arch: amd64
|
||||
@@ -1108,6 +1302,9 @@ def test_property_access():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- property/read: System.IO.FileInfo::Length
|
||||
"""
|
||||
@@ -1126,6 +1323,9 @@ def test_property_access_symbol():
|
||||
rule:
|
||||
meta:
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- property/read: System.IO.FileInfo::Length = some property
|
||||
"""
|
||||
|
||||
@@ -22,9 +22,8 @@ def test_rule_scope_instruction():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: instruction
|
||||
dynamic: dev
|
||||
features:
|
||||
- and:
|
||||
- and:
|
||||
- mnemonic: mov
|
||||
- arch: i386
|
||||
- os: windows
|
||||
@@ -41,7 +40,6 @@ def test_rule_scope_instruction():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: instruction
|
||||
dynamic: dev
|
||||
features:
|
||||
- characteristic: embedded pe
|
||||
"""
|
||||
@@ -60,7 +58,7 @@ def test_rule_subscope_instruction():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- instruction:
|
||||
@@ -91,7 +89,7 @@ def test_scope_instruction_implied_and():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- instruction:
|
||||
@@ -112,7 +110,7 @@ def test_scope_instruction_description():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- instruction:
|
||||
@@ -132,7 +130,7 @@ def test_scope_instruction_description():
|
||||
name: test rule
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- instruction:
|
||||
|
||||
@@ -109,7 +109,7 @@ def test_detect_duplicate_features(tmpdir):
|
||||
name: Test Rule 0
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- number: 1
|
||||
@@ -124,6 +124,9 @@ def test_detect_duplicate_features(tmpdir):
|
||||
rule:
|
||||
meta:
|
||||
name: Test Rule 1
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- string: unique
|
||||
@@ -143,6 +146,9 @@ def test_detect_duplicate_features(tmpdir):
|
||||
rule:
|
||||
meta:
|
||||
name: Test Rule 2
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- string: "sites.ini"
|
||||
@@ -157,6 +163,9 @@ def test_detect_duplicate_features(tmpdir):
|
||||
rule:
|
||||
meta:
|
||||
name: Test Rule 3
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- or:
|
||||
- not:
|
||||
@@ -172,6 +181,9 @@ def test_detect_duplicate_features(tmpdir):
|
||||
rule:
|
||||
meta:
|
||||
name: Test Rule 4
|
||||
scopes:
|
||||
static: function
|
||||
dynamic: process
|
||||
features:
|
||||
- not:
|
||||
- string: "expa"
|
||||
|
||||
@@ -90,7 +90,7 @@ def test_null_feature_extractor():
|
||||
name: xor loop
|
||||
scopes:
|
||||
static: basic block
|
||||
dynamic: dev
|
||||
dynamic: process
|
||||
features:
|
||||
- and:
|
||||
- characteristic: tight loop
|
||||
|
||||
Reference in New Issue
Block a user