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

Feat/warn for dynamic dotnet #2568

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
## master (unreleased)

### New Features
- add warning for dynamic .NET samples #1864
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved

### Breaking Changes

### New Rules (2)

- data-manipulation/encryption/rsa/encrypt-data-using-rsa-via-embedded-library Ana06
- data-manipulation/encryption/use-bigint-function Ana06
- data-manipulation/encryption/rsa/encrypt-data-using-rsa-via-embedded-library @Ana06
- data-manipulation/encryption/use-bigint-function @Ana06
-

### Bug Fixes
Expand Down
26 changes: 18 additions & 8 deletions capa/capabilities/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,38 @@ def find_file_capabilities(ruleset: RuleSet, extractor: FeatureExtractor, functi
return matches, len(file_features)


def has_file_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool:
file_limitation_rules = list(filter(lambda r: r.is_file_limitation_rule(), rules.rules.values()))
def has_limitation(rules: list, capabilities: MatchResults, is_standalone: bool) -> bool:
# given list of rules and capabilities, finds the limitation if it exists, logs warning and returns True
# else logs nothing and returns False

for file_limitation_rule in file_limitation_rules:
if file_limitation_rule.name not in capabilities:
for rule in rules:
if rule.name not in capabilities:
continue

logger.warning("-" * 80)
for line in file_limitation_rule.meta.get("description", "").split("\n"):
for line in rule.meta.get("description", "").split("\n"):
logger.warning(" %s", line)
logger.warning(" Identified via rule: %s", file_limitation_rule.name)
logger.warning(" Identified via rule: %s", rule.name)
if is_standalone:
logger.warning(" ")
logger.warning(" Use -v or -vv if you really want to see the capabilities identified by capa.")
logger.warning("-" * 80)

# bail on first file limitation
return True

return False


def has_file_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool:
file_limitation_rules = list(filter(lambda r: r.is_file_limitation_rule(), rules.rules.values()))

return has_limitation(file_limitation_rules, capabilities, is_standalone)


def has_dynamic_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool:
dynamic_limitation_rules = list(filter(lambda r: r.is_dynamic_limitation_rule(), rules.rules.values()))
return has_limitation(dynamic_limitation_rules, capabilities, is_standalone)


v1bh475u marked this conversation as resolved.
Show resolved Hide resolved
def find_capabilities(
ruleset: RuleSet, extractor: FeatureExtractor, disable_progress=None, **kwargs
) -> tuple[MatchResults, Any]:
Expand Down
46 changes: 41 additions & 5 deletions capa/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
)
from capa.capabilities.common import find_capabilities, has_file_limitation, find_file_capabilities
from capa.capabilities.common import (
find_capabilities,
has_file_limitation,
find_file_capabilities,
has_dynamic_limitation,
)
from capa.features.extractors.base_extractor import (
ProcessFilter,
FunctionFilter,
Expand Down Expand Up @@ -762,6 +767,7 @@ def find_file_limitations_from_cli(args, rules: RuleSet, file_extractors: list[F
for file_extractor in file_extractors:
try:
pure_file_capabilities, _ = find_file_capabilities(rules, file_extractor, {})
# logger.info("file capabilities: ", pure_file_capabilities)
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved
except PEFormatError as e:
logger.error("Input file '%s' is not a valid PE file: %s", args.input_file, str(e))
raise ShouldExitError(E_CORRUPT_FILE) from e
Expand All @@ -781,6 +787,32 @@ def find_file_limitations_from_cli(args, rules: RuleSet, file_extractors: list[F
return found_file_limitation


def find_dynamic_limitations_from_cli(args, rules: RuleSet, file_extractors: list[FeatureExtractor]) -> bool:
"""
args:
args: The parsed command line arguments from `install_common_args`.

Handles dynamic dotnet samples.
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved

raises:
ShouldExitError: if the program is invoked incorrectly and should exit.
"""
found_dynamic_limitation = False
for file_extractor in file_extractors:
pure_dynamic_capabilities, _ = find_file_capabilities(rules, file_extractor, {})
found_dynamic_limitation = has_dynamic_limitation(rules, pure_dynamic_capabilities)

# file limitations that rely on non-file scope won't be detected here.
# nor on FunctionName features, because pefile doesn't support this.
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved
if found_dynamic_limitation:
# bail if capa encountered file limitation e.g. a dotnet sample is detected
# do show the output in verbose mode, though.
if not (args.verbose or args.vverbose or args.json):
logger.debug("file limitation short circuit, won't analyze fully.")
raise ShouldExitError(E_FILE_LIMITATION)
return found_dynamic_limitation


def get_signatures_from_cli(args, input_format: str, backend: str) -> list[Path]:
if backend != BACKEND_VIV:
logger.debug("skipping library code matching: only supported by the vivisect backend")
Expand Down Expand Up @@ -965,11 +997,14 @@ def main(argv: Optional[list[str]] = None):
ensure_input_exists_from_cli(args)
input_format = get_input_format_from_cli(args)
rules = get_rules_from_cli(args)
found_file_limitation = False
# logger.info(rules.rules)
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved
found_limitation = False
file_extractors = get_file_extractors_from_cli(args, input_format)
if input_format in STATIC_FORMATS:
# only static extractors have file limitations
file_extractors = get_file_extractors_from_cli(args, input_format)
found_file_limitation = find_file_limitations_from_cli(args, rules, file_extractors)
found_limitation = find_file_limitations_from_cli(args, rules, file_extractors)
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved
if input_format in DYNAMIC_FORMATS:
found_limitation = find_dynamic_limitations_from_cli(args, rules, file_extractors)
except ShouldExitError as e:
return e.status_code

Expand Down Expand Up @@ -1002,8 +1037,9 @@ def main(argv: Optional[list[str]] = None):
meta = capa.loader.collect_metadata(argv, args.input_file, input_format, os_, args.rules, extractor, counts)
meta.analysis.layout = capa.loader.compute_layout(rules, extractor, capabilities)

if isinstance(extractor, StaticFeatureExtractor) and found_file_limitation:
if found_limitation:
# bail if capa's static feature extractor encountered file limitation e.g. a packed binary
# or capa's dynamic feature extractor encountered some limitation e.g. a dotnet sample
# do show the output in verbose mode, though.
if not (args.verbose or args.vverbose or args.json):
return E_FILE_LIMITATION
Expand Down
3 changes: 3 additions & 0 deletions capa/rules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,9 @@ def _extract_subscope_rules_rec(self, statement):
def is_file_limitation_rule(self) -> bool:
return self.meta.get("namespace", "") == "internal/limitation/file"
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved

def is_dynamic_limitation_rule(self) -> bool:
return self.meta.get("namespace", "") == "internal/limitation/dynamic"
v1bh475u marked this conversation as resolved.
Show resolved Hide resolved

def is_subscope_rule(self):
return bool(self.meta.get("capa/subscope-rule", False))

Expand Down