diff options
| author | Cyrille Bagard <nocbos@gmail.com> | 2023-08-06 16:54:57 (GMT) | 
|---|---|---|
| committer | Cyrille Bagard <nocbos@gmail.com> | 2023-08-06 16:54:57 (GMT) | 
| commit | 4fcc35a52ccb025b6d803d85e017931cd2452960 (patch) | |
| tree | e95920f16c273e41f9cae1ea2f02571c221a514e /tests | |
| parent | 74d062d4ec55d7ac3914bbf64b8b6c5ab52227df (diff) | |
Extend the ROST grammar with a first batch of new features.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/analysis/scan/booleans.py | 98 | ||||
| -rw-r--r-- | tests/analysis/scan/common.py | 52 | ||||
| -rw-r--r-- | tests/analysis/scan/examples.py | 70 | ||||
| -rw-r--r-- | tests/analysis/scan/expr.py | 169 | ||||
| -rw-r--r-- | tests/analysis/scan/exprs.py | 122 | ||||
| -rw-r--r-- | tests/analysis/scan/func.py | 16 | ||||
| -rw-r--r-- | tests/analysis/scan/functions.py | 104 | ||||
| -rw-r--r-- | tests/analysis/scan/grammar.py | 327 | ||||
| -rw-r--r-- | tests/analysis/scan/matches.py | 27 | ||||
| -rw-r--r-- | tests/analysis/scan/options.py | 16 | ||||
| -rw-r--r-- | tests/analysis/scan/pyapi.py | 58 | ||||
| -rw-r--r-- | tests/analysis/scan/sets.py | 118 | 
12 files changed, 643 insertions, 534 deletions
diff --git a/tests/analysis/scan/booleans.py b/tests/analysis/scan/booleans.py new file mode 100644 index 0000000..aa3c1a3 --- /dev/null +++ b/tests/analysis/scan/booleans.py @@ -0,0 +1,98 @@ + +from common import RostTestClass + + +class TestRostBooleans(RostTestClass): +    """TestCases for booleans and ROST.""" + +    def testFinalCondition(self): +        """Validate the final condition.""" + +        rule = ''' +rule test { + +   condition: +      false + +} +''' + +        self.check_rule_failure(rule) + + +        rule = ''' +rule test { + +   condition: +      true + +} +''' + +        self.check_rule_success(rule) + + +    def testBasicBooleanOperations(self): +        """Evaluate basic boolean operations.""" + +        rule = ''' +rule test { + +   condition: +      true and false + +} +''' + +        self.check_rule_failure(rule) + + +        rule = ''' +rule test { + +   condition: +      true or false + +} +''' + +        self.check_rule_success(rule) + + +    def testImplicitCast(self): +        """Imply implicit casts to booleans.""" + +        rule = ''' +rule test { + +   condition: +      true and 0 + +} +''' + +        self.check_rule_failure(rule) + + +        rule = ''' +rule test { + +   condition: +      1 or false + +} +''' + +        self.check_rule_success(rule) + + +        rule = ''' +rule test { + +   condition: +      1 or () + +} +''' + +        self.check_rule_success(rule) diff --git a/tests/analysis/scan/common.py b/tests/analysis/scan/common.py new file mode 100644 index 0000000..3b52e38 --- /dev/null +++ b/tests/analysis/scan/common.py @@ -0,0 +1,52 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.analysis.contents import MemoryContent +from pychrysalide.analysis.scan import ContentScanner +from pychrysalide.analysis.scan import ScanOptions +from pychrysalide.analysis.scan.patterns.backends import AcismBackend + + +class RostTestClass(ChrysalideTestCase): +    """TestCase for analysis.scan.ScanExpression.""" + +    @classmethod +    def setUpClass(cls): + +        super(RostTestClass, cls).setUpClass() + +        cls._options = ScanOptions() +        cls._options.backend_for_data = AcismBackend + +        cls._empty_content = MemoryContent(b'') + + +    def _validate_rule_result(self, rule, content, expected): +        """Check for scan success or failure.""" + +        scanner = ContentScanner(rule) +        ctx = scanner.analyze(self._options, content) + +        self.assertIsNotNone(ctx) + +        if expected: +            self.assertTrue(ctx.has_match_for_rule('test')) +        else: +            self.assertFalse(ctx.has_match_for_rule('test')) + + +    def check_rule_success(self, rule, content = None): +        """Check for scan success.""" + +        if content is None: +            content = self._empty_content + +        self._validate_rule_result(rule, content, True) + + +    def check_rule_failure(self, rule, content = None): +        """Check for scan failure.""" + +        if content is None: +            content = self._empty_content + +        self._validate_rule_result(rule, content, False) diff --git a/tests/analysis/scan/examples.py b/tests/analysis/scan/examples.py new file mode 100644 index 0000000..74b5094 --- /dev/null +++ b/tests/analysis/scan/examples.py @@ -0,0 +1,70 @@ + +from common import RostTestClass +from pychrysalide.analysis.contents import MemoryContent + + +class TestRostExamples(RostTestClass): +    """TestCases for the examples provides in the ROST documentation.""" + +    def testComments(self): +        """Ensure comments do not bother rule definitions.""" + +        rule = ''' +/* +    Multi-line header... +*/ + +rule test {    // comment + +   /* +    * Some context +    */ + +   condition:  /* List of condition(s) */ +      true     // Dummy condition + +} +''' + +        self.check_rule_success(rule) + + +    def testArithmeticPrecedence(self): +        """Take care of arithmetic operators precedence.""" + +        rule = ''' +rule test {  // MulFirst + +   condition: +      1 + 4 * (3 + 2) == 21 +      and +      (1 + 4) * (3 + 2) == 25 + +} +''' + +        self.check_rule_success(rule) + + +    def testUintCast(self): +        """Process nested integer values from binary content.""" + +        cnt = MemoryContent(b'\x4d\x5a\x00\x00' + b'\x50\x45\x00\x00' + 52 * b'\x00' + b'\x04\x00\x00\x00') + +        rule = ''' +rule test {  // IsPE + +    condition: + +        // MZ signature at offset 0 and ... + +        uint16(0) == 0x5a4d and + +        // ... PE signature at offset stored in the MZ header at offset 0x3c + +        uint32(uint32(0x3c)) == 0x00004550 + +} +''' + +        self.check_rule_success(rule, cnt) diff --git a/tests/analysis/scan/expr.py b/tests/analysis/scan/expr.py deleted file mode 100644 index dbe8c55..0000000 --- a/tests/analysis/scan/expr.py +++ /dev/null @@ -1,169 +0,0 @@ - - -from chrysacase import ChrysalideTestCase -from pychrysalide.analysis.scan import ScanExpression -from pychrysalide.glibext import ComparableItem - - -class TestScanExpression(ChrysalideTestCase): -    """TestCase for analysis.scan.ScanExpression.""" - - -    def testDirectInstances(self): -        """Reject direct instances.""" - -        with self.assertRaisesRegex(RuntimeError, 'pychrysalide.analysis.scan.ScanExpression is an abstract class'): - -            e = ScanExpression() - - -    def testBooleanComparison(self): -        """Compare custom scan expressions.""" - -        class StrLenExpr(ScanExpression): - -            def __init__(self, value): -                super().__init__(ScanExpression.ExprValueType.STRING) -                self._value = value - -            def _cmp_rich(self, other, op): - -                if op == ComparableItem.RichCmpOperation.EQ: -                    return len(self._value) == len(other._value) - - -        e0 = StrLenExpr('00000000000') - -        e1 = StrLenExpr('00000000000') - -        e2 = StrLenExpr('000000000000000000000000000') - -        self.assertTrue(e0 == e1) - -        # !? -        # Python teste e0 != e1 (non implémenté), puis e1 != e0 (pareil) et en déduit une différence ! -        # self.assertFalse(e0 != e1) - -        self.assertFalse(e0 == e2) - -        # TypeError: '<' not supported between instances of 'StrLenExpr' and 'StrLenExpr' -        with self.assertRaisesRegex(TypeError, '\'<\' not supported between instances'): -            self.assertTrue(e0 < e1) - - - - - - -    # def testTypeSubclassing(self): -    #     """Verify the data type subclassing is working.""" - -    #     class MyType(DataType): - -    #         def __init__(self, num): -    #             super(MyType, self).__init__() -    #             self._num = num - -    #         def _to_string(self, include): -    #             return '%x' % self._num - -    #         def _dup(self): -    #             return MyType(self._num) - -    #     tp = MyType(0x123) - -    #     self.assertEqual(str(tp), '123') - -    #     tp2 = tp.dup() - -    #     self.assertEqual(str(tp), str(tp2)) - - -    # def testTypeDefaultProperties(self): -    #     """Check for default values of some type properties.""" - -    #     class MyPropType(DataType): -    #         pass - -    #     tp = MyPropType() - -    #     self.assertTrue(tp.handle_namespaces) - -    #     self.assertFalse(tp.is_pointer) - -    #     self.assertFalse(tp.is_reference) - -    #     class MyPropType2(DataType): - -    #         def _handle_namespaces(self): -    #             return True - -    #         def _is_pointer(self): -    #             return 123 < 1234 - -    #         def _is_reference(self): -    #             return False - -    #     tp2 = MyPropType2() - -    #     self.assertTrue(tp.handle_namespaces) - -    #     self.assertTrue(tp2.is_pointer) - -    #     self.assertFalse(tp2.is_reference) - - -    # def testTypeNamespaces(self): -    #     """Test the type namespace property.""" - -    #     class MyNSType(DataType): - -    #         def __init__(self, name): -    #             super(MyNSType, self).__init__() -    #             self._name = name - -    #         def _to_string(self, include): -    #             return self._name - -    #     tp = MyNSType('TP') -    #     ns = MyNSType('NS') - -    #     self.assertIsNone(tp.namespace) - -    #     tp.namespace = (ns, '.') - -    #     self.assertEqual(str(tp), 'NS.TP') - -    #     self.assertEqual(tp.namespace, (ns, '.')) - - -    # def testTypeHash(self): -    #     """Hash a user-defined type.""" - -    #     class MyUserType(DataType): - -    #         def __init__(self, name): -    #             super(MyUserType, self).__init__() -    #             self._name = name - -    #         def _hash(self): -    #             return hash(self._name) - -    #     tp = MyUserType('random') - -    #     self.assertEqual(tp.hash, hash('random') & ((1 << 32) - 1)) - -    #     class MyOutOfRangeUserType(DataType): - -    #         hard_coded_hash = -8752470794866657507 - -    #         def __init__(self, name): -    #             super(MyOutOfRangeUserType, self).__init__() -    #             self._name = name - -    #         def _hash(self): -    #             return self.hard_coded_hash - -    #     tp = MyOutOfRangeUserType('out-of-range') - -    #     self.assertEqual(tp.hash, MyOutOfRangeUserType.hard_coded_hash & ((1 << 32) - 1)) diff --git a/tests/analysis/scan/exprs.py b/tests/analysis/scan/exprs.py deleted file mode 100644 index c89dc59..0000000 --- a/tests/analysis/scan/exprs.py +++ /dev/null @@ -1,122 +0,0 @@ - -from chrysacase import ChrysalideTestCase -from pychrysalide.analysis.contents import MemoryContent -from pychrysalide.analysis.scan import ContentScanner -from pychrysalide.analysis.scan import ScanOptions -from pychrysalide.analysis.scan.patterns.backends import AcismBackend - - -class TestScanExpressions(ChrysalideTestCase): -    """TestCase for analysis.scan.exprs.*.""" - -    @classmethod -    def setUpClass(cls): - -        super(TestScanExpressions, cls).setUpClass() - -        cls._options = ScanOptions() -        cls._options.backend_for_data = AcismBackend - - -    def testBasicStringOperations(self): -        """Evaluate basic string operations.""" - -        cnt = MemoryContent(b'empty') - -        rule = ''' -rule test { - -   condition: -      "123abc456" contains "abc" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "123\t456" contains "\t" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "123-456" startswith "1" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "123-456" startswith "1234" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertFalse(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "123-456" endswith "6" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "123-456" endswith "3456" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertFalse(ctx.has_match_for_rule('test')) - -        rule = ''' -rule test { - -   condition: -      "ABCD" iequals "AbCd" - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(ctx.has_match_for_rule('test')) diff --git a/tests/analysis/scan/func.py b/tests/analysis/scan/func.py deleted file mode 100644 index bd7d0ce..0000000 --- a/tests/analysis/scan/func.py +++ /dev/null @@ -1,16 +0,0 @@ - - -from chrysacase import ChrysalideTestCase -from pychrysalide.analysis.scan import ScanFunction - - -class TestScanFunction(ChrysalideTestCase): -    """TestCase for analysis.scan.ScanFunction.""" - - -    def testDirectInstances(self): -        """Reject direct instances.""" - -        with self.assertRaisesRegex(RuntimeError, 'pychrysalide.analysis.scan.ScanFunction is an abstract class'): - -            f = ScanFunction('name') diff --git a/tests/analysis/scan/functions.py b/tests/analysis/scan/functions.py new file mode 100644 index 0000000..8553018 --- /dev/null +++ b/tests/analysis/scan/functions.py @@ -0,0 +1,104 @@ + +from common import RostTestClass +from pychrysalide.analysis.contents import MemoryContent + + +class TestRostFunctions(RostTestClass): +    """TestCases for the core functions of ROST.""" + +    # Core +    # ==== + +    def testDatasize(self): +        """Handle the size of the provided data.""" + +        cnt = MemoryContent(b'\x01\x02\x03\x04') + +        cases = [ +            'datasize == 4', +            'uint16(0) == 0x201 and uint16(datasize - 2) == 0x0403', +        ] + +        for c in cases: + +            rule = ''' +rule test { + +   condition: +      %s + +} +''' % c + +            self.check_rule_success(rule, cnt) + + +    # Modules +    # ======= + +    def testConsole(self): +        """Ensure logging always returns true.""" + +        rule = ''' +rule test { + +   condition: +      console.log() + +} +''' + +        self.check_rule_success(rule) + + +    def testMagic(self): +        """Scan text content with the Magic module.""" + +        cnt = MemoryContent(b'aaaa') + +        cases = [ +            [ 'type', 'ASCII text, with no line terminators' ], +            [ 'mime_encoding', 'us-ascii' ], +            [ 'mime_type', 'text/plain' ], +        ] + +        for target, expected in cases: + +            rule = ''' +rule test { + +   condition: +      magic.%s() == "%s" + +} +''' % (target, expected) + +            self.check_rule_success(rule, cnt) + + +    def testTime(self): +        """Check current time.""" + +        # Cf. https://www.epochconverter.com/ + +        rule = ''' +rule test { + +   condition: +      time.make(2023, 8, 5, 22, 8, 41) == 0x64cec869 + +} +''' + +        self.check_rule_success(rule) + +        rule = ''' +rule test { + +   condition: +      time.now() >= 0x64cec874 and time.now() <= time.now() + +} +''' + +        self.check_rule_success(rule) diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py index 5a2e1d5..8b18f81 100644 --- a/tests/analysis/scan/grammar.py +++ b/tests/analysis/scan/grammar.py @@ -1,286 +1,191 @@ -from chrysacase import ChrysalideTestCase -from pychrysalide.analysis.contents import MemoryContent -from pychrysalide.analysis.scan import ContentScanner -from pychrysalide.analysis.scan import ScanOptions -from pychrysalide.analysis.scan.patterns.backends import AcismBackend +from common import RostTestClass -class TestRostGrammar(ChrysalideTestCase): -    """TestCase for analysis.scan.ScanExpression.""" +class TestRostGrammar(RostTestClass): +    """TestCases for the ROST grammar.""" -    @classmethod -    def setUpClass(cls): +    def testRelationalExpressions(self): +        """Build expressions with relational comparisons.""" -        super(TestRostGrammar, cls).setUpClass() +        cases = [ -        cls._options = ScanOptions() -        cls._options.backend_for_data = AcismBackend +            # 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 ], -    def testComments(self): -        """Ensure comments do not bother rule definitions.""" +        ] -        cnt = MemoryContent(b'no_real_content') +        for op1, kwd, op2, expected in cases: -        rule = ''' -/* -    Multi-line header... -*/ - -rule test {    // comment - -   /* -    * Some context -    */ - -   condition:  /* List of condition(s) */ -      true     // Dummy condition - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) - - -    def testUintCast(self): -        """Process nested integer values from binary content.""" - -        cnt = MemoryContent(b'\x4d\x5a\x00\x00' + b'\x50\x45\x00\x00' + 52 * b'\x00' + b'\x04\x00\x00\x00') - -        rule = ''' -rule IsPE { - -    condition: - -        // MZ signature at offset 0 and ... - -        uint16(0) == 0x5a4d and - -        // ... PE signature at offset stored in the MZ header at offset 0x3c - -        uint32(uint32(0x3c)) == 0x00004550 - -} -''' - -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('IsPE')) - - -    def testBasicBooleanConditions(self): -        """Evaluate basic boolean conditions.""" - -        cnt = MemoryContent(b'0123') - -        rule = ''' -rule test { - -   condition: -      true and false - -} -''' - -        scanner = ContentScanner(rule) - -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None)) -        self.assertFalse(ctx.has_match_for_rule('test')) - -        rule = ''' +            rule = '''  rule test {     condition: -      true or false +      %s %s %s  } -''' +''' % (op1, kwd, op2) -        scanner = ContentScanner(rule) +            if expected: +                self.check_rule_success(rule) +            else: +                self.check_rule_failure(rule) -        ctx = scanner.analyze(self._options, cnt) -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) +    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 ], +        ] -    def testArithmeticOpeations(self): -        """Compute some arithmetic operations.""" +        for cond, expected in cases: -        cnt = MemoryContent(b'0123') - -        rule = ''' +            rule = '''  rule test {     condition: -      1 + 4 * 3 + 2 == 15 +      %s  } -''' - -        scanner = ContentScanner(rule) - -        ctx = scanner.analyze(self._options, cnt) +''' % (cond) -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) +            if expected: +                self.check_rule_success(rule) +            else: +                self.check_rule_failure(rule) -        rule = ''' -rule test { +    def testArithmeticOperations(self): +        """Evaluate some arithmetic operations.""" -   condition: -      (1 + 4) * 3 + 2 == 17 +        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', -        scanner = ContentScanner(rule) +            # Legacy +            '1 + 4 * 3 + 2 == 15', +            '(1 + 4) * 3 + 2 == 17', +            '1 + 4 * (3 + 2) == 21', +            '(1 + 4) * (3 + 2) == 25', -        ctx = scanner.analyze(self._options, cnt) +        ] -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) +        for c in cases: - -        rule = ''' +            rule = '''  rule test {     condition: -      1 + 4 * (3 + 2) == 21 +      %s  } -''' +''' % (c) -        scanner = ContentScanner(rule) +            self.check_rule_success(rule) -        ctx = scanner.analyze(self._options, cnt) -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) +    def testBasicStringsOperations(self): +        """Build expressions with basic strings operations.""" +        cases = [ -        rule = ''' -rule test { +            # 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 ], -   condition: -      (1 + 4) * (3 + 2) == 25 - -} -''' +            # 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 ], -        scanner = ContentScanner(rule) +        ] -        ctx = scanner.analyze(self._options, cnt) +        for op1, kwd, op2, expected in cases: -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) - - -    def testSizeUnits(self): -        """Evaluate size units.""" - -        cnt = MemoryContent(b'0123') - - -        rule = ''' -rule test { - -   condition: -      1KB == 1024 - -} -''' - -        scanner = ContentScanner(rule) - -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) - - -        rule = ''' +            rule = '''  rule test {     condition: -      2MB == 2 * 1024 * 1024 +      "%s" %s "%s"  } -''' +''' % (op1, kwd, op2) -        scanner = ContentScanner(rule) +            if expected: +                self.check_rule_success(rule) +            else: +                self.check_rule_failure(rule) -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) - - -        rule = ''' -rule test { - -   condition: -      4Kb == (4 * 1024) - -} -''' - -        scanner = ContentScanner(rule) - -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) - - -        rule = ''' -rule test { - -   condition: -      1KB <= 1024 and 1024 < 1MB - -} -''' - -        scanner = ContentScanner(rule) - -        ctx = scanner.analyze(self._options, cnt) - -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) +    def testSizeUnits(self): +        """Evaluate size units.""" -    def testNamespace(self): -        """Resolve main functions with the root scan namesapce.""" +        cases = [ +            '1KB == 1024', +            '2MB == 2 * 1024 * 1024', +            '4Kb == (4 * 1024)', +            '1KB <= 1024 and 1024 < 1MB', +        ] -        cnt = MemoryContent(b'\x01\x02\x03\x04') +        for c in cases: -        rule = ''' +            rule = '''  rule test {     condition: -      datasize == 4 +      %s  } -''' +''' % (c) -        scanner = ContentScanner(rule) +            self.check_rule_success(rule) -        ctx = scanner.analyze(self._options, cnt) -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) -        rule = ''' -rule test { -   condition: -      uint16(0) == 0x201 and uint16(datasize - 2) == 0x0403 -} -''' +# TODO : test     <haystack> matches <regex> -        scanner = ContentScanner(rule) -        ctx = scanner.analyze(self._options, cnt) -        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) diff --git a/tests/analysis/scan/matches.py b/tests/analysis/scan/matches.py new file mode 100644 index 0000000..0d7556e --- /dev/null +++ b/tests/analysis/scan/matches.py @@ -0,0 +1,27 @@ + +from common import RostTestClass +from pychrysalide.analysis.contents import MemoryContent + + +class TestRostMatchs(RostTestClass): +    """TestCases for the ROST pattern matching engine.""" + +    def testCountMatches(self): +        """Count matches patterns.""" + +        cnt = MemoryContent(b'aaa aaa bbb aaa') + +        rule = ''' +rule test { + +   strings: +      $a = "aaa" +      $b = "bbb" + +   condition: +      #a == 3 and #b < 2 + +} +''' + +        self.check_rule_success(rule, cnt) diff --git a/tests/analysis/scan/options.py b/tests/analysis/scan/options.py deleted file mode 100644 index 7617b3a..0000000 --- a/tests/analysis/scan/options.py +++ /dev/null @@ -1,16 +0,0 @@ - - -from chrysacase import ChrysalideTestCase -from gi._constants import TYPE_INVALID -from pychrysalide.analysis.scan import ScanOptions - - -class TestScanOptions(ChrysalideTestCase): -    """TestCase for analysis.scan.ScanOptions.""" - -    def testEmptyOptions(self): -        """Check default scan options.""" - -        ops = ScanOptions() - -        self.assertEqual(ops.backend_for_data, TYPE_INVALID) diff --git a/tests/analysis/scan/pyapi.py b/tests/analysis/scan/pyapi.py new file mode 100644 index 0000000..1bba44e --- /dev/null +++ b/tests/analysis/scan/pyapi.py @@ -0,0 +1,58 @@ + +from chrysacase import ChrysalideTestCase +from gi._constants import TYPE_INVALID +from pychrysalide.analysis.scan import ScanExpression +from pychrysalide.analysis.scan import ScanOptions +from pychrysalide.glibext import ComparableItem + + +class TestRostPythonAPI(ChrysalideTestCase): +    """TestCase for the ROST Python API.""" + +    def testEmptyOptions(self): +        """Check default scan options.""" + +        ops = ScanOptions() + +        self.assertEqual(ops.backend_for_data, TYPE_INVALID) + + +    def testDirectInstancesOfExpression(self): +        """Reject direct instances of ROST expressions.""" + +        with self.assertRaisesRegex(RuntimeError, 'pychrysalide.analysis.scan.ScanExpression is an abstract class'): + +            e = ScanExpression() + +    def testBooleanComparison(self): +        """Compare custom scan expressions.""" + +        class StrLenExpr(ScanExpression): + +            def __init__(self, value): +                super().__init__(ScanExpression.ExprValueType.STRING) +                self._value = value + +            def _cmp_rich(self, other, op): + +                if op == ComparableItem.RichCmpOperation.EQ: +                    return len(self._value) == len(other._value) + + +        e0 = StrLenExpr('00000000000') + +        e1 = StrLenExpr('00000000000') + +        e2 = StrLenExpr('000000000000000000000000000') + +        self.assertTrue(e0 == e1) + +        # !? +        # Python teste e0 != e1 (non implémenté), puis e1 != e0 (pareil) et en déduit une différence ! +        # self.assertFalse(e0 != e1) + +        self.assertFalse(e0 == e2) + +        # TypeError: '<' not supported between instances of 'StrLenExpr' and 'StrLenExpr' +        with self.assertRaisesRegex(TypeError, '\'<\' not supported between instances'): +            self.assertTrue(e0 < e1) diff --git a/tests/analysis/scan/sets.py b/tests/analysis/scan/sets.py new file mode 100644 index 0000000..1d10fbf --- /dev/null +++ b/tests/analysis/scan/sets.py @@ -0,0 +1,118 @@ + +from common import RostTestClass + + +class TestRostSets(RostTestClass): +    """TestCases for sets support in ROST.""" + +    def testSetsAsBooleans(self): +        """Convert sets to boolean.""" + +        rule = ''' +rule test { + +   condition: +      () + +} +''' + +        self.check_rule_failure(rule) + + +        rule = ''' +rule test { + +   condition: +      (1, ) + +} +''' + +        self.check_rule_success(rule) + + +        rule = ''' +rule test { + +   condition: +      ("aaa", true, 123) + +} +''' + +        self.check_rule_success(rule) + + +    def testStringAsArray(self): +        """Handle strings as arrays.""" + +        rule = ''' +rule test { + +   condition: +      count("aaa") + +} +''' + +        self.check_rule_success(rule) + + +        rule = ''' +rule test { + +   condition: +      count("aaa") == 3 + +} +''' + +        self.check_rule_success(rule) + + +    def testSetsIntersections(self): +        """Perform sets intersections.""" + +        rule = ''' +rule test { + +   condition: +      ("aaa", "bbb") in ("AAA", "BBB", "aaa") + +} +''' + +        self.check_rule_success(rule) + + +        rule = ''' +rule test { + +   condition: +      ("aaa", "bbb") in ("123", ) + +} +''' + +        self.check_rule_failure(rule) + + + + + + + + + + + + + +        # TODO : + +        # test : intersection(a, a) == a + +        # test : "123" in "0123456789" +        # test : "123" in "012987" +  | 
