mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 15:16:22 -08:00
Get rid of the Element class
The `Element` class is just used for testing. By using `Element` we are not testing the actual code. Also, every time we implement a new feature for the `Feature` class, we need to implement it for `Element` as well. Replace `Element` by `Integer`.
This commit is contained in:
@@ -145,22 +145,6 @@ class Some(Statement):
|
|||||||
return Result(success, self, results)
|
return Result(success, self, results)
|
||||||
|
|
||||||
|
|
||||||
class Element(Statement):
|
|
||||||
'''match if the child is contained in the ctx set.'''
|
|
||||||
def __init__(self, child):
|
|
||||||
super(Element, self).__init__()
|
|
||||||
self.child = child
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name, self.child))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.name == other.name and self.child == other.child
|
|
||||||
|
|
||||||
def evaluate(self, ctx):
|
|
||||||
return Result(self.child in ctx, self, [])
|
|
||||||
|
|
||||||
|
|
||||||
class Range(Statement):
|
class Range(Statement):
|
||||||
'''match if the child is contained in the ctx set with a count in the given range.'''
|
'''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):
|
def __init__(self, child, min=None, max=None):
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
|||||||
parent2 = parent
|
parent2 = parent
|
||||||
else:
|
else:
|
||||||
parent2 = CapaExplorerDefaultItem(parent, '%d or more' % result.statement.count)
|
parent2 = CapaExplorerDefaultItem(parent, '%d or more' % result.statement.count)
|
||||||
elif not isinstance(result.statement, (capa.features.Feature, capa.engine.Element, capa.engine.Range, capa.engine.Regex)):
|
elif not isinstance(result.statement, (capa.features.Feature, capa.engine.Range, capa.engine.Regex)):
|
||||||
# when rending a structural node (and/or/not) then we only care about the node name.
|
# when rending a structural node (and/or/not) then we only care about the node name.
|
||||||
'''
|
'''
|
||||||
succs = list(filter(lambda c: bool(c), result.children))
|
succs = list(filter(lambda c: bool(c), result.children))
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ def render_result(res, indent=''):
|
|||||||
print('%soptional:' % indent)
|
print('%soptional:' % indent)
|
||||||
else:
|
else:
|
||||||
print('%s%d or more' % (indent, res.statement.count))
|
print('%s%d or more' % (indent, res.statement.count))
|
||||||
elif not isinstance(res.statement, (capa.features.Feature, capa.engine.Element, capa.engine.Range, capa.engine.Regex)):
|
elif not isinstance(res.statement, (capa.features.Feature, capa.engine.Range, capa.engine.Regex)):
|
||||||
# when rending a structural node (and/or/not),
|
# when rending a structural node (and/or/not),
|
||||||
# then we only care about the node name.
|
# then we only care about the node name.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ BASIC_BLOCK_SCOPE = 'basic block'
|
|||||||
|
|
||||||
SUPPORTED_FEATURES = {
|
SUPPORTED_FEATURES = {
|
||||||
FILE_SCOPE: set([
|
FILE_SCOPE: set([
|
||||||
capa.engine.Element,
|
|
||||||
capa.features.MatchedRule,
|
capa.features.MatchedRule,
|
||||||
capa.features.file.Export,
|
capa.features.file.Export,
|
||||||
capa.features.file.Import,
|
capa.features.file.Import,
|
||||||
@@ -33,7 +32,6 @@ SUPPORTED_FEATURES = {
|
|||||||
capa.features.String,
|
capa.features.String,
|
||||||
]),
|
]),
|
||||||
FUNCTION_SCOPE: set([
|
FUNCTION_SCOPE: set([
|
||||||
capa.engine.Element,
|
|
||||||
capa.features.MatchedRule,
|
capa.features.MatchedRule,
|
||||||
capa.features.insn.API,
|
capa.features.insn.API,
|
||||||
capa.features.insn.Number,
|
capa.features.insn.Number,
|
||||||
@@ -56,7 +54,6 @@ SUPPORTED_FEATURES = {
|
|||||||
capa.features.Characteristic('recursive call')
|
capa.features.Characteristic('recursive call')
|
||||||
]),
|
]),
|
||||||
BASIC_BLOCK_SCOPE: set([
|
BASIC_BLOCK_SCOPE: set([
|
||||||
capa.engine.Element,
|
|
||||||
capa.features.MatchedRule,
|
capa.features.MatchedRule,
|
||||||
capa.features.insn.API,
|
capa.features.insn.API,
|
||||||
capa.features.insn.Number,
|
capa.features.insn.Number,
|
||||||
@@ -180,8 +177,6 @@ def parse_feature(key):
|
|||||||
return capa.features.insn.Mnemonic
|
return capa.features.insn.Mnemonic
|
||||||
elif key == 'basic blocks':
|
elif key == 'basic blocks':
|
||||||
return capa.features.basicblock.BasicBlock
|
return capa.features.basicblock.BasicBlock
|
||||||
elif key == 'element':
|
|
||||||
return Element
|
|
||||||
elif key.startswith('characteristic(') and key.endswith(')'):
|
elif key.startswith('characteristic(') and key.endswith(')'):
|
||||||
characteristic = key[len('characteristic('):-len(')')]
|
characteristic = key[len('characteristic('):-len(')')]
|
||||||
return lambda v: capa.features.Characteristic(characteristic, v)
|
return lambda v: capa.features.Characteristic(characteristic, v)
|
||||||
@@ -311,9 +306,6 @@ def build_statements(d, scope):
|
|||||||
if term in ('number', 'offset', 'bytes'):
|
if term in ('number', 'offset', 'bytes'):
|
||||||
value, symbol = parse_symbol(arg, term)
|
value, symbol = parse_symbol(arg, term)
|
||||||
feature = Feature(value, symbol)
|
feature = Feature(value, symbol)
|
||||||
elif term in ('element'):
|
|
||||||
arg = parse_int(arg)
|
|
||||||
feature = Feature(arg)
|
|
||||||
else:
|
else:
|
||||||
# arg is string, like:
|
# arg is string, like:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -3,96 +3,95 @@ import textwrap
|
|||||||
import capa.rules
|
import capa.rules
|
||||||
import capa.engine
|
import capa.engine
|
||||||
from capa.engine import *
|
from capa.engine import *
|
||||||
import capa.features
|
from capa.features import *
|
||||||
|
from capa.features.insn import *
|
||||||
|
|
||||||
|
|
||||||
def test_element():
|
def test_number():
|
||||||
assert Element(1).evaluate(set([0])) == False
|
assert Number(1).evaluate({Number(0): {1}}) == False
|
||||||
assert Element(1).evaluate(set([1])) == True
|
assert Number(1).evaluate({Number(1): {1}}) == True
|
||||||
assert Element(1).evaluate(set([None])) == False
|
assert Number(1).evaluate({Number(2): {1, 2}}) == False
|
||||||
assert Element(1).evaluate(set([''])) == False
|
|
||||||
assert Element(1).evaluate(set([False])) == False
|
|
||||||
|
|
||||||
|
|
||||||
def test_and():
|
def test_and():
|
||||||
assert And(Element(1)).evaluate(set([0])) == False
|
assert And(Number(1)).evaluate({Number(0): {1}}) == False
|
||||||
assert And(Element(1)).evaluate(set([1])) == True
|
assert And(Number(1)).evaluate({Number(1): {1}}) == True
|
||||||
assert And(Element(1), Element(2)).evaluate(set([0])) == False
|
assert And(Number(1), Number(2)).evaluate({Number(0): {1}}) == False
|
||||||
assert And(Element(1), Element(2)).evaluate(set([1])) == False
|
assert And(Number(1), Number(2)).evaluate({Number(1): {1}}) == False
|
||||||
assert And(Element(1), Element(2)).evaluate(set([2])) == False
|
assert And(Number(1), Number(2)).evaluate({Number(2): {1}}) == False
|
||||||
assert And(Element(1), Element(2)).evaluate(set([1, 2])) == True
|
assert And(Number(1), Number(2)).evaluate({Number(1): {1}, Number(2): {2}}) == True
|
||||||
|
|
||||||
|
|
||||||
def test_or():
|
def test_or():
|
||||||
assert Or(Element(1)).evaluate(set([0])) == False
|
assert Or(Number(1)).evaluate({Number(0): {1}}) == False
|
||||||
assert Or(Element(1)).evaluate(set([1])) == True
|
assert Or(Number(1)).evaluate({Number(1): {1}}) == True
|
||||||
assert Or(Element(1), Element(2)).evaluate(set([0])) == False
|
assert Or(Number(1), Number(2)).evaluate({Number(0): {1}}) == False
|
||||||
assert Or(Element(1), Element(2)).evaluate(set([1])) == True
|
assert Or(Number(1), Number(2)).evaluate({Number(1): {1}}) == True
|
||||||
assert Or(Element(1), Element(2)).evaluate(set([2])) == True
|
assert Or(Number(1), Number(2)).evaluate({Number(2): {1}}) == True
|
||||||
assert Or(Element(1), Element(2)).evaluate(set([1, 2])) == True
|
assert Or(Number(1), Number(2)).evaluate({Number(1): {1}, Number(2): {2}}) == True
|
||||||
|
|
||||||
|
|
||||||
def test_not():
|
def test_not():
|
||||||
assert Not(Element(1)).evaluate(set([0])) == True
|
assert Not(Number(1)).evaluate({Number(0): {1}}) == True
|
||||||
assert Not(Element(1)).evaluate(set([1])) == False
|
assert Not(Number(1)).evaluate({Number(1): {1}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_some():
|
def test_some():
|
||||||
assert Some(0, Element(1)).evaluate(set([0])) == True
|
assert Some(0, Number(1)).evaluate({Number(0): {1}}) == True
|
||||||
assert Some(1, Element(1)).evaluate(set([0])) == False
|
assert Some(1, Number(1)).evaluate({Number(0): {1}}) == False
|
||||||
|
|
||||||
assert Some(2, Element(1), Element(2), Element(3)).evaluate(set([0])) == False
|
assert Some(2, Number(1), Number(2), Number(3)).evaluate({Number(0): {1}}) == False
|
||||||
assert Some(2, Element(1), Element(2), Element(3)).evaluate(set([0, 1])) == False
|
assert Some(2, Number(1), Number(2), Number(3)).evaluate({Number(0): {1}, Number(1): {1}}) == False
|
||||||
assert Some(2, Element(1), Element(2), Element(3)).evaluate(set([0, 1, 2])) == True
|
assert Some(2, Number(1), Number(2), Number(3)).evaluate({Number(0): {1}, Number(1): {1}, Number(2): {1}}) == True
|
||||||
assert Some(2, Element(1), Element(2), Element(3)).evaluate(set([0, 1, 2, 3])) == True
|
assert Some(2, Number(1), Number(2), Number(3)).evaluate({Number(0): {1}, Number(1): {1}, Number(2): {1}, Number(3): {1}}) == True
|
||||||
assert Some(2, Element(1), Element(2), Element(3)).evaluate(set([0, 1, 2, 3, 4])) == True
|
assert Some(2, Number(1), Number(2), Number(3)).evaluate({Number(0): {1}, Number(1): {1}, Number(2): {1}, Number(3): {1}, Number(4): {1}}) == True
|
||||||
|
|
||||||
|
|
||||||
def test_complex():
|
def test_complex():
|
||||||
assert True == Or(
|
assert True == Or(
|
||||||
And(Element(1), Element(2)),
|
And(Number(1), Number(2)),
|
||||||
Or(Element(3),
|
Or(Number(3),
|
||||||
Some(2, Element(4), Element(5), Element(6)))
|
Some(2, Number(4), Number(5), Number(6)))
|
||||||
).evaluate(set([5, 6, 7, 8]))
|
).evaluate({Number(5): {1}, Number(6): {1}, Number(7): {1}, Number(8): {1}})
|
||||||
|
|
||||||
assert False == Or(
|
assert False == Or(
|
||||||
And(Element(1), Element(2)),
|
And(Number(1), Number(2)),
|
||||||
Or(Element(3),
|
Or(Number(3),
|
||||||
Some(2, Element(4), Element(5)))
|
Some(2, Number(4), Number(5)))
|
||||||
).evaluate(set([5, 6, 7, 8]))
|
).evaluate({Number(5): {1}, Number(6): {1}, Number(7): {1}, Number(8): {1}})
|
||||||
|
|
||||||
|
|
||||||
def test_range():
|
def test_range():
|
||||||
# unbounded range, but no matching feature
|
# unbounded range, but no matching feature
|
||||||
assert Range(Element(1)).evaluate({Element(2): {}}) == False
|
assert Range(Number(1)).evaluate({Number(2): {}}) == False
|
||||||
|
|
||||||
# unbounded range with matching feature should always match
|
# unbounded range with matching feature should always match
|
||||||
assert Range(Element(1)).evaluate({Element(1): {}}) == True
|
assert Range(Number(1)).evaluate({Number(1): {}}) == True
|
||||||
assert Range(Element(1)).evaluate({Element(1): {0}}) == True
|
assert Range(Number(1)).evaluate({Number(1): {0}}) == True
|
||||||
|
|
||||||
# unbounded max
|
# unbounded max
|
||||||
assert Range(Element(1), min=1).evaluate({Element(1): {0}}) == True
|
assert Range(Number(1), min=1).evaluate({Number(1): {0}}) == True
|
||||||
assert Range(Element(1), min=2).evaluate({Element(1): {0}}) == False
|
assert Range(Number(1), min=2).evaluate({Number(1): {0}}) == False
|
||||||
assert Range(Element(1), min=2).evaluate({Element(1): {0, 1}}) == True
|
assert Range(Number(1), min=2).evaluate({Number(1): {0, 1}}) == True
|
||||||
|
|
||||||
# unbounded min
|
# unbounded min
|
||||||
assert Range(Element(1), max=0).evaluate({Element(1): {0}}) == False
|
assert Range(Number(1), max=0).evaluate({Number(1): {0}}) == False
|
||||||
assert Range(Element(1), max=1).evaluate({Element(1): {0}}) == True
|
assert Range(Number(1), max=1).evaluate({Number(1): {0}}) == True
|
||||||
assert Range(Element(1), max=2).evaluate({Element(1): {0}}) == True
|
assert Range(Number(1), max=2).evaluate({Number(1): {0}}) == True
|
||||||
assert Range(Element(1), max=2).evaluate({Element(1): {0, 1}}) == True
|
assert Range(Number(1), max=2).evaluate({Number(1): {0, 1}}) == True
|
||||||
assert Range(Element(1), max=2).evaluate({Element(1): {0, 1, 3}}) == False
|
assert Range(Number(1), max=2).evaluate({Number(1): {0, 1, 3}}) == False
|
||||||
|
|
||||||
# we can do an exact match by setting min==max
|
# we can do an exact match by setting min==max
|
||||||
assert Range(Element(1), min=1, max=1).evaluate({Element(1): {}}) == False
|
assert Range(Number(1), min=1, max=1).evaluate({Number(1): {}}) == False
|
||||||
assert Range(Element(1), min=1, max=1).evaluate({Element(1): {1}}) == True
|
assert Range(Number(1), min=1, max=1).evaluate({Number(1): {1}}) == True
|
||||||
assert Range(Element(1), min=1, max=1).evaluate({Element(1): {1, 2}}) == False
|
assert Range(Number(1), min=1, max=1).evaluate({Number(1): {1, 2}}) == False
|
||||||
|
|
||||||
# bounded range
|
# bounded range
|
||||||
assert Range(Element(1), min=1, max=3).evaluate({Element(1): {}}) == False
|
assert Range(Number(1), min=1, max=3).evaluate({Number(1): {}}) == False
|
||||||
assert Range(Element(1), min=1, max=3).evaluate({Element(1): {1}}) == True
|
assert Range(Number(1), min=1, max=3).evaluate({Number(1): {1}}) == True
|
||||||
assert Range(Element(1), min=1, max=3).evaluate({Element(1): {1, 2}}) == True
|
assert Range(Number(1), min=1, max=3).evaluate({Number(1): {1, 2}}) == True
|
||||||
assert Range(Element(1), min=1, max=3).evaluate({Element(1): {1, 2, 3}}) == True
|
assert Range(Number(1), min=1, max=3).evaluate({Number(1): {1, 2, 3}}) == True
|
||||||
assert Range(Element(1), min=1, max=3).evaluate({Element(1): {1, 2, 3, 4}}) == False
|
assert Range(Number(1), min=1, max=3).evaluate({Number(1): {1, 2, 3, 4}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_match_adds_matched_rule_feature():
|
def test_match_adds_matched_rule_feature():
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ import textwrap
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import capa.rules
|
import capa.rules
|
||||||
from capa.engine import Element
|
|
||||||
from capa.features.insn import Number, Offset
|
from capa.features.insn import Number, Offset
|
||||||
|
|
||||||
|
|
||||||
def test_rule_ctor():
|
def test_rule_ctor():
|
||||||
r = capa.rules.Rule('test rule', capa.rules.FUNCTION_SCOPE, Element(1), {})
|
r = capa.rules.Rule('test rule', capa.rules.FUNCTION_SCOPE, Number(1), {})
|
||||||
assert r.evaluate(set([0])) == False
|
assert r.evaluate({Number(0): {1}}) == False
|
||||||
assert r.evaluate(set([1])) == True
|
assert r.evaluate({Number(1): {1}}) == True
|
||||||
|
|
||||||
|
|
||||||
def test_rule_yaml():
|
def test_rule_yaml():
|
||||||
@@ -25,14 +24,14 @@ def test_rule_yaml():
|
|||||||
- bar5678
|
- bar5678
|
||||||
features:
|
features:
|
||||||
- and:
|
- and:
|
||||||
- element: 1
|
- number: 1
|
||||||
- element: 2
|
- number: 2
|
||||||
''')
|
''')
|
||||||
r = capa.rules.Rule.from_yaml(rule)
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
assert r.evaluate(set([0])) == False
|
assert r.evaluate({Number(0): {1}}) == False
|
||||||
assert r.evaluate(set([0, 1])) == False
|
assert r.evaluate({Number(0): {1}, Number(1): {1}}) == False
|
||||||
assert r.evaluate(set([0, 1, 2])) == True
|
assert r.evaluate({Number(0): {1}, Number(1): {1}, Number(2): {1}}) == True
|
||||||
assert r.evaluate(set([0, 1, 2, 3])) == True
|
assert r.evaluate({Number(0): {1}, Number(1): {1}, Number(2): {1}, Number(3): {1}}) == True
|
||||||
|
|
||||||
|
|
||||||
def test_rule_yaml_complex():
|
def test_rule_yaml_complex():
|
||||||
@@ -43,18 +42,18 @@ def test_rule_yaml_complex():
|
|||||||
features:
|
features:
|
||||||
- or:
|
- or:
|
||||||
- and:
|
- and:
|
||||||
- element: 1
|
- number: 1
|
||||||
- element: 2
|
- number: 2
|
||||||
- or:
|
- or:
|
||||||
- element: 3
|
- number: 3
|
||||||
- 2 or more:
|
- 2 or more:
|
||||||
- element: 4
|
- number: 4
|
||||||
- element: 5
|
- number: 5
|
||||||
- element: 6
|
- number: 6
|
||||||
''')
|
''')
|
||||||
r = capa.rules.Rule.from_yaml(rule)
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
assert r.evaluate(set([5, 6, 7, 8])) == True
|
assert r.evaluate({Number(5): {1}, Number(6): {1}, Number(7): {1}, Number(8): {1}}) == True
|
||||||
assert r.evaluate(set([6, 7, 8])) == False
|
assert r.evaluate({Number(6): {1}, Number(7): {1}, Number(8): {1}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_rule_yaml_not():
|
def test_rule_yaml_not():
|
||||||
@@ -64,13 +63,13 @@ def test_rule_yaml_not():
|
|||||||
name: test rule
|
name: test rule
|
||||||
features:
|
features:
|
||||||
- and:
|
- and:
|
||||||
- element: 1
|
- number: 1
|
||||||
- not:
|
- not:
|
||||||
- element: 2
|
- number: 2
|
||||||
''')
|
''')
|
||||||
r = capa.rules.Rule.from_yaml(rule)
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
assert r.evaluate(set([1])) == True
|
assert r.evaluate({Number(1): {1}}) == True
|
||||||
assert r.evaluate(set([1, 2])) == False
|
assert r.evaluate({Number(1): {1}, Number(2): {1}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_rule_yaml_count():
|
def test_rule_yaml_count():
|
||||||
@@ -79,12 +78,12 @@ def test_rule_yaml_count():
|
|||||||
meta:
|
meta:
|
||||||
name: test rule
|
name: test rule
|
||||||
features:
|
features:
|
||||||
- count(element(100)): 1
|
- count(number(100)): 1
|
||||||
''')
|
''')
|
||||||
r = capa.rules.Rule.from_yaml(rule)
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
assert r.evaluate({Element(100): {}}) == False
|
assert r.evaluate({Number(100): {}}) == False
|
||||||
assert r.evaluate({Element(100): {1}}) == True
|
assert r.evaluate({Number(100): {1}}) == True
|
||||||
assert r.evaluate({Element(100): {1, 2}}) == False
|
assert r.evaluate({Number(100): {1, 2}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_rule_yaml_count_range():
|
def test_rule_yaml_count_range():
|
||||||
@@ -93,13 +92,13 @@ def test_rule_yaml_count_range():
|
|||||||
meta:
|
meta:
|
||||||
name: test rule
|
name: test rule
|
||||||
features:
|
features:
|
||||||
- count(element(100)): (1, 2)
|
- count(number(100)): (1, 2)
|
||||||
''')
|
''')
|
||||||
r = capa.rules.Rule.from_yaml(rule)
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
assert r.evaluate({Element(100): {}}) == False
|
assert r.evaluate({Number(100): {}}) == False
|
||||||
assert r.evaluate({Element(100): {1}}) == True
|
assert r.evaluate({Number(100): {1}}) == True
|
||||||
assert r.evaluate({Element(100): {1, 2}}) == True
|
assert r.evaluate({Number(100): {1, 2}}) == True
|
||||||
assert r.evaluate({Element(100): {1, 2, 3}}) == False
|
assert r.evaluate({Number(100): {1, 2, 3}}) == False
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_rule_feature():
|
def test_invalid_rule_feature():
|
||||||
@@ -239,7 +238,7 @@ def test_invalid_rules():
|
|||||||
meta:
|
meta:
|
||||||
name: test rule
|
name: test rule
|
||||||
features:
|
features:
|
||||||
- characteristic(count(element(100))): True
|
- characteristic(count(number(100))): True
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user