Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rule scoping tweaks #1714

Merged
merged 9 commits into from
Aug 15, 2023
1 change: 1 addition & 0 deletions capa/ida/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def collect_metadata(rules: List[Path]):
sha256=sha256,
path=idaapi.get_input_file_path(),
),
flavor=rdoc.Flavor.STATIC,
analysis=rdoc.StaticAnalysis(
format=idaapi.get_file_type_name(),
arch=arch,
Expand Down
41 changes: 11 additions & 30 deletions capa/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import itertools
import contextlib
import collections
from enum import Enum
from typing import Any, Dict, List, Tuple, Callable, Optional
from pathlib import Path

Expand All @@ -29,6 +28,7 @@
import colorama
import tqdm.contrib.logging
from pefile import PEFormatError
from typing_extensions import assert_never
from elftools.common.exceptions import ELFError

import capa.perf
Expand Down Expand Up @@ -79,8 +79,6 @@
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 (
Expand Down Expand Up @@ -117,15 +115,6 @@
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()
Expand Down Expand Up @@ -889,7 +878,6 @@ 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:
Expand Down Expand Up @@ -928,14 +916,7 @@ def get_rules(
rules.append(rule)
logger.debug("loaded rule: '%s' with scope: %s", rule.name, rule.scopes)

# 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)
ruleset = capa.rules.RuleSet(rules)

capa.rules.cache.cache_ruleset(cache_dir, ruleset)

Expand Down Expand Up @@ -1022,6 +1003,13 @@ def collect_metadata(
arch = get_arch(sample_path)
os_ = get_os(sample_path) if os_ == OS_AUTO else os_

if isinstance(extractor, StaticFeatureExtractor):
flavor = rdoc.Flavor.STATIC
elif isinstance(extractor, DynamicFeatureExtractor):
flavor = rdoc.Flavor.DYNAMIC
else:
assert_never(extractor)

return rdoc.Metadata(
timestamp=datetime.datetime.now(),
version=capa.version.__version__,
Expand All @@ -1032,6 +1020,7 @@ def collect_metadata(
sha256=sha256,
path=str(Path(sample_path).resolve()),
),
flavor=flavor,
analysis=get_sample_analysis(
format_,
arch,
Expand Down Expand Up @@ -1456,15 +1445,7 @@ def main(argv: Optional[List[str]] = None):
else:
cache_dir = capa.rules.cache.get_default_cache_directory()

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)
rules = get_rules(args.rules, cache_dir=cache_dir)

logger.debug(
"successfully loaded %s rules",
Expand Down
1 change: 1 addition & 0 deletions capa/render/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def render_meta(doc: rd.ResultDocument, ostream: StringIO):
(width("md5", 22), width(doc.meta.sample.md5, 82)),
("sha1", doc.meta.sample.sha1),
("sha256", doc.meta.sample.sha256),
("analysis", doc.meta.flavor),
("os", doc.meta.analysis.os),
("format", doc.meta.analysis.format),
("arch", doc.meta.analysis.arch),
Expand Down
20 changes: 20 additions & 0 deletions capa/render/proto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,23 @@ def scope_to_pb2(scope: capa.rules.Scope) -> capa_pb2.Scope.ValueType:
assert_never(scope)


def flavor_to_pb2(flavor: rd.Flavor) -> capa_pb2.Flavor.ValueType:
if flavor == rd.Flavor.STATIC:
return capa_pb2.Flavor.FLAVOR_STATIC
elif flavor == rd.Flavor.DYNAMIC:
return capa_pb2.Flavor.FLAVOR_DYNAMIC
else:
assert_never(flavor)


def metadata_to_pb2(meta: rd.Metadata) -> capa_pb2.Metadata:
assert isinstance(meta.analysis, rd.StaticAnalysis)
return capa_pb2.Metadata(
timestamp=str(meta.timestamp),
version=meta.version,
argv=meta.argv,
sample=google.protobuf.json_format.ParseDict(meta.sample.model_dump(), capa_pb2.Sample()),
flavor=flavor_to_pb2(meta.flavor),
analysis=capa_pb2.Analysis(
format=meta.analysis.format,
arch=meta.analysis.arch,
Expand Down Expand Up @@ -480,6 +490,15 @@ def scope_from_pb2(scope: capa_pb2.Scope.ValueType) -> capa.rules.Scope:
assert_never(scope)


def flavor_from_pb2(flavor: capa_pb2.Flavor.ValueType) -> rd.Flavor:
if flavor == capa_pb2.Flavor.FLAVOR_STATIC:
return rd.Flavor.STATIC
elif flavor == capa_pb2.Flavor.FLAVOR_DYNAMIC:
return rd.Flavor.DYNAMIC
else:
assert_never(flavor)


def metadata_from_pb2(meta: capa_pb2.Metadata) -> rd.Metadata:
return rd.Metadata(
timestamp=datetime.datetime.fromisoformat(meta.timestamp),
Expand All @@ -491,6 +510,7 @@ def metadata_from_pb2(meta: capa_pb2.Metadata) -> rd.Metadata:
sha256=meta.sample.sha256,
path=meta.sample.path,
),
flavor=flavor_from_pb2(meta.flavor),
analysis=rd.StaticAnalysis(
format=meta.analysis.format,
arch=meta.analysis.arch,
Expand Down
7 changes: 7 additions & 0 deletions capa/render/proto/capa.proto
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,19 @@ message MatchFeature {
optional string description = 3;
}

enum Flavor {
FLAVOR_UNSPECIFIED = 0;
FLAVOR_STATIC = 1;
FLAVOR_DYNAMIC = 2;
}

message Metadata {
string timestamp = 1; // iso8601 format, like: 2019-01-01T00:00:00Z
string version = 2;
repeated string argv = 3;
Sample sample = 4;
Analysis analysis = 5;
Flavor flavor = 6;
}

message MnemonicFeature {
Expand Down
Loading
Loading