diff options
Diffstat (limited to 'tests/analysis')
-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) |