diff options
Diffstat (limited to 'tests')
| -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/grammar.py | 286 | ||||
| -rw-r--r-- | tests/analysis/scan/options.py | 16 | 
5 files changed, 609 insertions, 0 deletions
diff --git a/tests/analysis/scan/expr.py b/tests/analysis/scan/expr.py new file mode 100644 index 0000000..dbe8c55 --- /dev/null +++ b/tests/analysis/scan/expr.py @@ -0,0 +1,169 @@ + + +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 new file mode 100644 index 0000000..c89dc59 --- /dev/null +++ b/tests/analysis/scan/exprs.py @@ -0,0 +1,122 @@ + +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 new file mode 100644 index 0000000..bd7d0ce --- /dev/null +++ b/tests/analysis/scan/func.py @@ -0,0 +1,16 @@ + + +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/grammar.py b/tests/analysis/scan/grammar.py new file mode 100644 index 0000000..5a2e1d5 --- /dev/null +++ b/tests/analysis/scan/grammar.py @@ -0,0 +1,286 @@ + +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 TestRostGrammar(ChrysalideTestCase): +    """TestCase for analysis.scan.ScanExpression.""" + +    @classmethod +    def setUpClass(cls): + +        super(TestRostGrammar, cls).setUpClass() + +        cls._options = ScanOptions() +        cls._options.backend_for_data = AcismBackend + + +    def testComments(self): +        """Ensure comments do not bother rule definitions.""" + +        cnt = MemoryContent(b'no_real_content') + +        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 test { + +   condition: +      true or false + +} +''' + +        scanner = ContentScanner(rule) + +        ctx = scanner.analyze(self._options, cnt) + +        self.assertTrue(not(ctx is None) and ctx.has_match_for_rule('test')) + + +    def testArithmeticOpeations(self): +        """Compute some arithmetic operations.""" + +        cnt = MemoryContent(b'0123') + +        rule = ''' +rule test { + +   condition: +      1 + 4 * 3 + 2 == 15 + +} +''' + +        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: +      (1 + 4) * 3 + 2 == 17 + +} +''' + +        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: +      1 + 4 * (3 + 2) == 21 + +} +''' + +        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: +      (1 + 4) * (3 + 2) == 25 + +} +''' + +        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.""" + +        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 test { + +   condition: +      2MB == 2 * 1024 * 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: +      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 testNamespace(self): +        """Resolve main functions with the root scan namesapce.""" + +        cnt = MemoryContent(b'\x01\x02\x03\x04') + +        rule = ''' +rule test { + +   condition: +      datasize == 4 + +} +''' + +        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: +      uint16(0) == 0x201 and uint16(datasize - 2) == 0x0403 + +} +''' + +        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/options.py b/tests/analysis/scan/options.py new file mode 100644 index 0000000..7617b3a --- /dev/null +++ b/tests/analysis/scan/options.py @@ -0,0 +1,16 @@ + + +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)  | 
