mirror of
https://github.com/mandiant/capa.git
synced 2025-12-12 15:49:46 -08:00
addressing Willi's feedback
This commit is contained in:
@@ -104,26 +104,10 @@ class Result(object):
|
||||
return self.success
|
||||
|
||||
|
||||
class Description(object):
|
||||
def __init__(self, value=None):
|
||||
super(Description, self).__init__()
|
||||
self.name = self.__class__.__name__
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % (self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class And(Statement):
|
||||
"""match if all of the children evaluate to True."""
|
||||
|
||||
def __init__(self, children):
|
||||
description = get_statement_description(children)
|
||||
if description:
|
||||
children.remove(description)
|
||||
def __init__(self, children, description=None):
|
||||
super(And, self).__init__(description=description)
|
||||
self.children = children
|
||||
|
||||
@@ -136,10 +120,7 @@ class And(Statement):
|
||||
class Or(Statement):
|
||||
"""match if any of the children evaluate to True."""
|
||||
|
||||
def __init__(self, children):
|
||||
description = get_statement_description(children)
|
||||
if description:
|
||||
children.remove(description)
|
||||
def __init__(self, children, description=None):
|
||||
super(Or, self).__init__(description=description)
|
||||
self.children = children
|
||||
|
||||
@@ -152,8 +133,8 @@ class Or(Statement):
|
||||
class Not(Statement):
|
||||
"""match only if the child evaluates to False."""
|
||||
|
||||
def __init__(self, child):
|
||||
super(Not, self).__init__()
|
||||
def __init__(self, child, description=None):
|
||||
super(Not, self).__init__(description=description)
|
||||
self.child = child
|
||||
|
||||
def evaluate(self, ctx):
|
||||
@@ -165,10 +146,7 @@ class Not(Statement):
|
||||
class Some(Statement):
|
||||
"""match if at least N of the children evaluate to True."""
|
||||
|
||||
def __init__(self, count, children):
|
||||
description = get_statement_description(children)
|
||||
if description:
|
||||
children.remove(description)
|
||||
def __init__(self, count, children, description=None):
|
||||
super(Some, self).__init__(description=description)
|
||||
self.count = count
|
||||
self.children = children
|
||||
@@ -186,8 +164,8 @@ class Some(Statement):
|
||||
class Range(Statement):
|
||||
"""match if the child is contained in the ctx set with a count in the given range."""
|
||||
|
||||
def __init__(self, child, min=None, max=None):
|
||||
super(Range, self).__init__()
|
||||
def __init__(self, child, min=None, max=None, description=None):
|
||||
super(Range, self).__init__(description=description)
|
||||
self.child = child
|
||||
self.min = min if min is not None else 0
|
||||
self.max = max if max is not None else (1 << 64 - 1)
|
||||
@@ -221,16 +199,6 @@ class Subscope(Statement):
|
||||
raise ValueError("cannot evaluate a subscope directly!")
|
||||
|
||||
|
||||
def get_statement_description(children):
|
||||
description = list(filter(lambda c: isinstance(c, Description), children))
|
||||
if len(description) == 1:
|
||||
return description[0]
|
||||
elif len(description) > 1:
|
||||
raise ValueError("statements can only have one description")
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def topologically_order_rules(rules):
|
||||
"""
|
||||
order the given rules such that dependencies show up before dependents.
|
||||
|
||||
@@ -276,27 +276,66 @@ def parse_description(s, value_type, description=None):
|
||||
return value, description
|
||||
|
||||
|
||||
def get_statement_description_entry(d):
|
||||
"""
|
||||
extracts the description for statements
|
||||
a statement must only have one description
|
||||
|
||||
example:
|
||||
the features definition
|
||||
- or:
|
||||
- description: statement description
|
||||
- number: 1
|
||||
description: feature description
|
||||
|
||||
becomes
|
||||
<statement>: [
|
||||
{ "description": "statement description" }, <-- extracted here
|
||||
{ "number": 1, "description": "feature description" }
|
||||
]
|
||||
"""
|
||||
if not isinstance(d, list):
|
||||
return None
|
||||
|
||||
# identify child of form '{ "description": <description> }'
|
||||
descriptions = list(filter(lambda c: isinstance(c, dict) and len(c) == 1 and "description" in c, d))
|
||||
if len(descriptions) > 1:
|
||||
raise InvalidRule("statements can only have one description")
|
||||
|
||||
if not descriptions:
|
||||
return None
|
||||
|
||||
return descriptions[0]
|
||||
|
||||
|
||||
def build_statements(d, scope):
|
||||
if len(d.keys()) > 2:
|
||||
raise InvalidRule("too many statements")
|
||||
|
||||
key = list(d.keys())[0]
|
||||
|
||||
description = get_statement_description_entry(d[key])
|
||||
if description:
|
||||
# remove description entry from the document and use statement description
|
||||
d[key].remove(description)
|
||||
description = description["description"]
|
||||
|
||||
if key == "and":
|
||||
return And([build_statements(dd, scope) for dd in d[key]])
|
||||
return And([build_statements(dd, scope) for dd in d[key]], description=description)
|
||||
elif key == "or":
|
||||
return Or([build_statements(dd, scope) for dd in d[key]])
|
||||
return Or([build_statements(dd, scope) 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 Not(build_statements(d[key][0], scope))
|
||||
return Not(build_statements(d[key][0], scope), description=description)
|
||||
elif key.endswith(" or more"):
|
||||
count = int(key[: -len("or more")])
|
||||
return Some(count, [build_statements(dd, scope) for dd in d[key]])
|
||||
return Some(count, [build_statements(dd, scope) 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 Some(0, [build_statements(dd, scope) for dd in d[key]])
|
||||
return Some(0, [build_statements(dd, scope) for dd in d[key]], description=description)
|
||||
|
||||
elif key == "function":
|
||||
if scope != FILE_SCOPE:
|
||||
@@ -355,24 +394,22 @@ def build_statements(d, scope):
|
||||
|
||||
count = d[key]
|
||||
if isinstance(count, int):
|
||||
return Range(feature, min=count, max=count)
|
||||
return Range(feature, min=count, max=count, description=description)
|
||||
elif count.endswith(" or more"):
|
||||
min = parse_int(count[: -len(" or more")])
|
||||
max = None
|
||||
return Range(feature, min=min, max=max)
|
||||
return Range(feature, min=min, max=max, description=description)
|
||||
elif count.endswith(" or fewer"):
|
||||
min = None
|
||||
max = parse_int(count[: -len(" or fewer")])
|
||||
return Range(feature, min=min, max=max)
|
||||
return Range(feature, min=min, max=max, description=description)
|
||||
elif count.startswith("("):
|
||||
min, max = parse_range(count)
|
||||
return Range(feature, min=min, max=max)
|
||||
return Range(feature, min=min, max=max, description=description)
|
||||
else:
|
||||
raise InvalidRule("unexpected range: %s" % (count))
|
||||
elif key == "string" and not isinstance(d[key], six.string_types):
|
||||
raise InvalidRule("ambiguous string value %s, must be defined as explicit string" % d[key])
|
||||
elif key == "description":
|
||||
return Description(d[key])
|
||||
else:
|
||||
Feature = parse_feature(key)
|
||||
value, description = parse_description(d[key], key, d.get("description"))
|
||||
@@ -538,10 +575,7 @@ class Rule(object):
|
||||
if scope not in SUPPORTED_FEATURES.keys():
|
||||
raise InvalidRule("{:s} is not a supported scope".format(scope))
|
||||
|
||||
try:
|
||||
return cls(name, scope, build_statements(statements[0], scope), d["rule"]["meta"], definition)
|
||||
except ValueError as e:
|
||||
raise InvalidRule(e)
|
||||
return cls(name, scope, build_statements(statements[0], scope), d["rule"]["meta"], definition)
|
||||
|
||||
@staticmethod
|
||||
@lru_cache()
|
||||
|
||||
Reference in New Issue
Block a user