summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/analysis/scan/expr.py169
-rw-r--r--tests/analysis/scan/exprs.py122
-rw-r--r--tests/analysis/scan/func.py16
-rw-r--r--tests/analysis/scan/grammar.py286
-rw-r--r--tests/analysis/scan/options.py16
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)