summaryrefslogtreecommitdiff
path: root/tests/analysis/scan/grammar.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/analysis/scan/grammar.py')
-rw-r--r--tests/analysis/scan/grammar.py484
1 files changed, 484 insertions, 0 deletions
diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py
new file mode 100644
index 0000000..14f67fa
--- /dev/null
+++ b/tests/analysis/scan/grammar.py
@@ -0,0 +1,484 @@
+
+import json
+
+from common import RostTestClass
+from pychrysalide.analysis.contents import MemoryContent
+
+
+class TestRostGrammar(RostTestClass):
+ """TestCases for the ROST grammar."""
+
+ def testRelationalExpressions(self):
+ """Build expressions with relational comparisons."""
+
+ cases = [
+
+ # Regular
+ [ '-1', '<=', '2', True ],
+ [ '-1', '<=', '2', True ],
+ [ '"aaa"', '==', '"aaa"', True ],
+ [ '"aaa"', '<', '"aaaa"', True ],
+ [ '""', '<', '"aaaa"', True ],
+
+ # Cast
+ [ 'false', '==', '0', True ],
+ [ 'false', '==', '1', False ],
+ [ 'true', '!=', '0', True ],
+ [ '1', '==', 'true', True ],
+ [ 'false', '==', '()', True ],
+ [ 'true', '==', '(0,)', True ],
+
+ ]
+
+ for op1, kwd, op2, expected in cases:
+
+ rule = '''
+rule test {
+
+ condition:
+ %s %s %s
+
+}
+''' % (op1, kwd, op2)
+
+ if expected:
+ self.check_rule_success(rule)
+ else:
+ self.check_rule_failure(rule)
+
+
+ def testLogicalOperations(self):
+ """Evaluate some logical operations."""
+
+ cases = [
+ [ 'true and false', False ],
+ [ 'false or false', False ],
+ [ 'true and true or false', True ],
+ [ 'false or true and false', False ],
+ [ '1 or false', True ],
+ ]
+
+ for cond, expected in cases:
+
+ rule = '''
+rule test {
+
+ condition:
+ %s
+
+}
+''' % (cond)
+
+ if expected:
+ self.check_rule_success(rule)
+ else:
+ self.check_rule_failure(rule)
+
+
+ def testArithmeticOperations(self):
+ """Evaluate some arithmetic operations."""
+
+ cases = [
+
+ # Clever
+ '1 + 2 == 3',
+ '10 + -3 == 7',
+ '-3 + 10 == 7',
+ '-10 - 1 < 0',
+ '-10 - 1 == -11',
+ '(-10 - 1) == -11',
+ '(-1 - -10) == 9',
+ '-2 * -3 == 6',
+ '-2 * 3 == -6',
+
+ # Legacy
+ '1 + 4 * 3 + 2 == 15',
+ '(1 + 4) * 3 + 2 == 17',
+ '1 + 4 * (3 + 2) == 21',
+ '(1 + 4) * (3 + 2) == 25',
+
+ ]
+
+ for c in cases:
+
+ rule = '''
+rule test {
+
+ condition:
+ %s
+
+}
+''' % (c)
+
+ self.check_rule_success(rule)
+
+
+ def testBasicStringsOperations(self):
+ """Build expressions with basic strings operations."""
+
+ cases = [
+
+ # Clever
+ [ '123---456', 'contains', '---', True ],
+ [ '123---456', 'contains', 'xxx', False ],
+ [ '---123---456', 'startswith', '---', True ],
+ [ '---123---456', 'startswith', 'xxx', False ],
+ [ '123---456---', 'endswith', '---', True ],
+ [ '123---456---', 'endswith', 'xxx', False ],
+ [ 'AAA---BBB', 'icontains', 'aaa', True ],
+ [ 'AAA---BBB', 'icontains', 'xxx', False ],
+ [ 'AAA---BBB', 'istartswith', 'aAa', True ],
+ [ 'AAA---BBB', 'istartswith', 'xxx', False ],
+ [ 'AAA---BBB', 'iendswith', 'bBb', True ],
+ [ 'AAA---BBB', 'iendswith', 'xxx', False ],
+ [ 'AzertY', 'iequals', 'AZERTY', True ],
+ [ 'AzertY', 'iequals', 'AZERTY-', False ],
+
+ # Legacy
+ [ '123\t456', 'contains', '\t', True ],
+ [ '123-456', 'startswith', '1', True ],
+ [ '123-456', 'startswith', '1234', False ],
+ [ '123-456', 'endswith', '6', True ],
+ [ '123-456', 'endswith', '3456', False ],
+
+ ]
+
+ for op1, kwd, op2, expected in cases:
+
+ rule = '''
+rule test {
+
+ condition:
+ "%s" %s "%s"
+
+}
+''' % (op1, kwd, op2)
+
+ if expected:
+ self.check_rule_success(rule)
+ else:
+ self.check_rule_failure(rule)
+
+
+ def testSizeUnits(self):
+ """Evaluate size units."""
+
+ cases = [
+ '1KB == 1024',
+ '2MB == 2 * 1024 * 1024',
+ '4Kb == (4 * 1024)',
+ '1KB <= 1024 and 1024 < 1MB',
+ ]
+
+ for c in cases:
+
+ rule = '''
+rule test {
+
+ condition:
+ %s
+
+}
+''' % (c)
+
+ self.check_rule_success(rule)
+
+
+ def testPrivateRules(self):
+ """Ensure private rules remain silent."""
+
+ for private in [ True, False ]:
+ for state in [ True, False ]:
+
+ rule = '''
+%srule silent {
+
+ condition:
+ %s
+
+}
+
+rule test {
+
+ condition:
+ silent
+
+}
+''' % ('private ' if private else '', 'true' if state else 'false')
+
+ scanner, ctx = self._validate_rule_result(rule, self._empty_content, state)
+
+ data = scanner.convert_to_json(ctx)
+ jdata = json.loads(data)
+
+ # Exemple :
+ #
+ # [{'bytes_patterns': [], 'matched': True, 'name': 'test'},
+ # {'bytes_patterns': [], 'matched': True, 'name': 'silent'}]
+
+ found = len([ j['name'] for j in jdata if j['name'] == 'silent' ]) > 0
+
+ self.assertTrue(private ^ found)
+
+
+ def testGlobalRules(self):
+ """Take global rules into account."""
+
+ for glob_state in [ True, False ]:
+ for state in [ True, False ]:
+
+ rule = '''
+%srule silent {
+
+ condition:
+ %s
+
+}
+
+rule test {
+
+ condition:
+ true
+
+}
+''' % ('global ' if glob_state else '', 'true' if state else 'false')
+
+ expected = not(glob_state) or state
+
+ if expected:
+ self.check_rule_success(rule)
+ else:
+ self.check_rule_failure(rule)
+
+
+ def testMatchCount(self):
+ """Ensure match count provides expected values."""
+
+ cnt = MemoryContent(b'\x01\x02\x02\x03\x03\x03')
+
+ rule = '''
+rule test {
+
+ bytes:
+ $int_01 = "\x01"
+ $int_02 = "\x02"
+ $int_03 = "\x03"
+
+ condition:
+ #int_01 == count($int_01) and #int_01 == 1
+ and #int_02 == count($int_02) and #int_02 == 2
+ and #int_03 == count($int_03) and #int_03 == 3
+ and #int_0* == count($int_0*) and #int_0* == 6
+
+}
+'''
+
+ self.check_rule_success(rule, cnt)
+
+
+ def testBackingUpHandlers(self):
+ """Ensure handlers for backing up removals do not limit the grammar."""
+
+ cnt = MemoryContent(b'AB12')
+
+ # Uncompleted token in rule definition: '?? ?? '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?? ?? }
+
+ condition:
+ #a == 3
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?? '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?? 4? }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?? ?'
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?? ?2 }
+
+ condition:
+ #a == 2
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?? '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?? 42 }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+
+ # Uncompleted token in rule definition: '?1 ?'
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?1 ?? }
+
+ condition:
+ #a == 2
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?1 4? '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?1 4? }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?1 ?2 '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?1 ?2 }
+
+ condition:
+ #a == 2
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '?1 4'
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { ?1 42 }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+
+ # Uncompleted token in rule definition: '41 '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { 41 ?? }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '41 4'
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { 41 4? }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '41 '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { 41 ?2 }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+ # Uncompleted token in rule definition: '41 42 '
+
+ rule = '''
+rule test {
+
+ bytes:
+ $a = { 41 42 }
+
+ condition:
+ #a == 1
+
+}
+'''
+
+ self.check_rule_success(rule, content=cnt)
+
+
+
+
+# TODO : test <haystack> matches <regex>
+
+
+