diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/analysis/scan/functions.py | 77 | ||||
| -rw-r--r-- | tests/analysis/scan/fuzzing.py | 179 | ||||
| -rw-r--r-- | tests/analysis/scan/grammar.py | 227 | ||||
| -rw-r--r-- | tests/analysis/scan/matches.py | 41 | ||||
| -rw-r--r-- | tests/analysis/scan/pyapi.py | 209 | ||||
| -rw-r--r-- | tests/analysis/scan/scanning_hex.py | 89 | ||||
| -rw-r--r-- | tests/analysis/scan/scanning_str.py | 20 | ||||
| -rw-r--r-- | tests/common/bitfield.py | 29 | ||||
| -rw-r--r-- | tests/plugins/encodings/all.py | 23 | ||||
| -rw-r--r-- | tests/plugins/kaitai/__init__.py | 0 | ||||
| -rw-r--r-- | tests/plugins/kaitai/language.py | 4 | ||||
| -rw-r--r-- | tests/plugins/kaitai/rost.py | 170 |
12 files changed, 1010 insertions, 58 deletions
diff --git a/tests/analysis/scan/functions.py b/tests/analysis/scan/functions.py index 96f029f..6aca957 100644 --- a/tests/analysis/scan/functions.py +++ b/tests/analysis/scan/functions.py @@ -9,6 +9,41 @@ class TestRostFunctions(RostTestClass): # Core # ==== + def testSetCounter(self): + """Count quantities and set sizes.""" + + rule = ''' +rule test { + + condition: + count("ABC") == 3 + and count("AB", "C") == count("ABC") + +} +''' + + self.check_rule_success(rule) + + + cnt = MemoryContent(b'\x01\x02\x02\x03\x03\x03') + + rule = ''' +rule test { + + bytes: + $int_01 = "\x01" + $int_02 = "\x02" + $int_3 = "\x03" + + condition: + count($int_0*, $int_3) == #int_* + +} +''' + + self.check_rule_success(rule, cnt) + + def testDatasize(self): """Handle the size of the provided data.""" @@ -33,6 +68,32 @@ rule test { self.check_rule_success(rule, cnt) + def testMaxCommon(self): + """Count the largest quantity of same items in a set.""" + + cnt = MemoryContent(b'') + + cases = [ + [ '1', 1 ], + [ '1, 2, 3', 1 ], + [ '1, 2, 1, 3, 1', 3 ], + [ '1, "a", 2, 3, "a"', 2 ], + ] + + for c, q in cases: + + rule = ''' +rule test { + + condition: + maxcommon(%s) == %u + +} +''' % (c, q) + + self.check_rule_success(rule, cnt) + + # Modules # ======= @@ -108,6 +169,7 @@ rule test { self.check_rule_success(rule) + rule = ''' rule test { @@ -119,6 +181,7 @@ rule test { self.check_rule_success(rule) + rule = ''' rule test { @@ -134,6 +197,19 @@ rule test { self.check_rule_success(rule) + rule = r''' +rule test { + + condition: + "A\x00B\x00C\x00D\x00" endswith string.wide("CD") + and "A\x00B\x00C\x00D\x00" contains string.wide("BC") + +} +''' + + self.check_rule_success(rule) + + def testTime(self): """Check current time.""" @@ -150,6 +226,7 @@ rule test { self.check_rule_success(rule) + rule = ''' rule test { diff --git a/tests/analysis/scan/fuzzing.py b/tests/analysis/scan/fuzzing.py index 53227af..1b9b25b 100644 --- a/tests/analysis/scan/fuzzing.py +++ b/tests/analysis/scan/fuzzing.py @@ -108,3 +108,182 @@ rule test { ''' self.check_rule_failure(rule) + + + def testSelfReferencingRule(self): + """Expect only one argument for the not operator, even in debug mode.""" + + rule = ''' +rule test { + + condition: + not(0) + +} +''' + + self.check_rule_success(rule) + + + def testNoCommon(self): + """Handle the case where no common item is found from an empty set.""" + + rule = ''' +rule test { + + bytes: + $a = "a" + + condition: + maxcommon($a) == 0 + +} +''' + + self.check_rule_success(rule) + + + def testAAsAcharacter(self): + """Consider the 'a' character as a valid lowercase character.""" + + rule = ''' +rule test { + + bytes: + $a = "0000a0I0" nocase + + condition: + $a + +} +''' + + self.check_rule_failure(rule) + + + def testAAsAcharacter(self): + """Do not expect initialized trackers when there is no real defined search pattern.""" + + rule = ''' +rule test { + + bytes: + $a = {[0]} + + condition: + $a + +} +''' + + with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'): + + scanner = ContentScanner(rule) + + + def testAllocations(self): + """Handle big alloctions for strings in conditions with regular expressions.""" + + rule = ''' +rule test { + + condition: + "%s" == "%s" + +} +''' % ("0" * (256 * 2 + 8), "0" * (256 * 2 + 8)) + + self.check_rule_success(rule) + + + def testFileFinalAccess(self): + """Ensure patterns found at the edges of scanned content do not crash the scanner.""" + + cnt = MemoryContent(bytes([ 0 for i in range(16) ])) + + rule = ''' +rule test { + + bytes: + $a = { 00 00 00 00 00 00 00 00 } + + condition: + $a + +} +''' + + self.check_rule_success(rule, cnt) + + + def testValidHexRangeMerge(self): + """Merge valid hexadecimal ranges.""" + + rule = ''' +rule test { + + bytes: + $a = { [0] ?? } + + condition: + $a + +} +''' + + with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'): + + scanner = ContentScanner(rule) + + + rule = ''' +rule test { + + bytes: + $a = { [2] ?? } + + condition: + $a + +} +''' + + self.check_rule_failure(rule) + + + def testSmallBase64(self): + """Handle small base64 encodings which may produce few patterns.""" + + rule = ''' +rule test { + + bytes: + $a = "0" base64 + + condition: + $a + +} +''' + + self.check_rule_failure(rule) + + + def testCountIndex(self): + """Ban pattern count indexes from the grammer.""" + + rule = ''' +rule test { + + bytes: + $a = "1" + + condition: + #*[0] + +} +''' + + with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'): + + scanner = ContentScanner(rule) diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py index 13a255b..14f67fa 100644 --- a/tests/analysis/scan/grammar.py +++ b/tests/analysis/scan/grammar.py @@ -2,6 +2,7 @@ import json from common import RostTestClass +from pychrysalide.analysis.contents import MemoryContent class TestRostGrammar(RostTestClass): @@ -250,6 +251,232 @@ rule test { 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> diff --git a/tests/analysis/scan/matches.py b/tests/analysis/scan/matches.py index 0d7556e..efcae4f 100644 --- a/tests/analysis/scan/matches.py +++ b/tests/analysis/scan/matches.py @@ -7,14 +7,14 @@ class TestRostMatchs(RostTestClass): """TestCases for the ROST pattern matching engine.""" def testCountMatches(self): - """Count matches patterns.""" + """Count matched patterns.""" cnt = MemoryContent(b'aaa aaa bbb aaa') rule = ''' rule test { - strings: + bytes: $a = "aaa" $b = "bbb" @@ -25,3 +25,40 @@ rule test { ''' self.check_rule_success(rule, cnt) + + + def testCountSameMatches(self): + """Count matches of similar patterns.""" + + cnt = MemoryContent(b'ABCDabcdABCDabcd') + + rule = ''' +rule test { + + bytes: + $a = "\x61\x62\x63\x64" + $b = "\x61\x62\x63\x64" + + condition: + #a == 2 and #b == 2 + +} +''' + + self.check_rule_success(rule, cnt) + + + rule = ''' +rule test { + + bytes: + $a = "\x61\x62\x63\x64" + $b = "\x61\x62\x63" + + condition: + #a == 2 and #b == 2 + +} +''' + + self.check_rule_success(rule, cnt) diff --git a/tests/analysis/scan/pyapi.py b/tests/analysis/scan/pyapi.py index 4b066f3..7a697b3 100644 --- a/tests/analysis/scan/pyapi.py +++ b/tests/analysis/scan/pyapi.py @@ -1,5 +1,6 @@ import binascii +import struct from chrysacase import ChrysalideTestCase from gi._constants import TYPE_INVALID @@ -34,7 +35,7 @@ class TestRostPythonAPI(ChrysalideTestCase): class StrLenExpr(ScanExpression): def __init__(self, value): - super().__init__(ScanExpression.ExprValueType.STRING) + super().__init__(ScanExpression.ScanReductionState.REDUCED) self._value = value def _cmp_rich(self, other, op): @@ -73,6 +74,7 @@ class TestRostPythonAPI(ChrysalideTestCase): self.assertEqual(source, transformed[0]) + mod = find_token_modifiers_for_name('hex') self.assertIsNotNone(mod) @@ -81,6 +83,7 @@ class TestRostPythonAPI(ChrysalideTestCase): self.assertEqual(binascii.hexlify(source), transformed[0]) + mod = find_token_modifiers_for_name('rev') self.assertIsNotNone(mod) @@ -88,3 +91,207 @@ class TestRostPythonAPI(ChrysalideTestCase): transformed = mod.transform(source) self.assertEqual(source[::-1], transformed[0]) + + + mod = find_token_modifiers_for_name('lower') + self.assertIsNotNone(mod) + + source = b'AbC' + transformed = mod.transform(source) + + self.assertEqual(source.lower(), transformed[0]) + + + mod = find_token_modifiers_for_name('upper') + self.assertIsNotNone(mod) + + source = b'AbC' + transformed = mod.transform(source) + + self.assertEqual(source.upper(), transformed[0]) + + + mod = find_token_modifiers_for_name('wide') + self.assertIsNotNone(mod) + + source = b'ABC' + transformed = mod.transform(source) + + self.assertEqual(source.decode('ascii'), transformed[0].decode('utf-16-le')) + + + mod = find_token_modifiers_for_name('base64') + self.assertIsNotNone(mod) + + source = b'ABC' + transformed = mod.transform(source) + + self.assertEqual(len(transformed), 3) + self.assertEqual(transformed[0], b'QUJD') + self.assertEqual(transformed[1], b'FCQ') + self.assertEqual(transformed[2], b'BQk') + + + def testClassicalAPIHashing(self): + """Reproduce classical API Hashing results.""" + + def b2i(t): + return struct.unpack('<I', t)[0] + + + # Example: + # - PlugX (2020) - https://vms.drweb.fr/virus/?i=21512304 + + mod = find_token_modifiers_for_name('crc32') + self.assertIsNotNone(mod) + + source = b'GetCurrentProcess\x00' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x3690e66) + + + # Example: + # - GuLoader (2020) - https://www.crowdstrike.com/blog/guloader-malware-analysis/ + + mod = find_token_modifiers_for_name('djb2') + self.assertIsNotNone(mod) + + source = b'GetProcAddress' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0xcf31bb1f) + + + def testCustomAPIHashing(self): + """Reproduce custom API Hashing results.""" + + def b2i(t): + return struct.unpack('<I', t)[0] + + + # Example: + # Underminer Exploit Kit (2019) - https://jsac.jpcert.or.jp/archive/2019/pdf/JSAC2019_1_koike-nakajima_jp.pdf + + mod = find_token_modifiers_for_name('add1505-shl5') + self.assertIsNotNone(mod) + + source = b'LoadLibraryA' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x5fbff0fb) + + + # Example: + # Enigma Stealer (2023) https://www.trendmicro.com/es_mx/research/23/b/enigma-stealer-targets-cryptocurrency-industry-with-fake-jobs.html + + mod = find_token_modifiers_for_name('enigma-murmur') + self.assertIsNotNone(mod) + + source = b'CreateMutexW' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0xfd43765a) + + + # Examples: + # - ShadowHammer (2019) - https://blog.f-secure.com/analysis-shadowhammer-asus-attack-first-stage-payload/ + # - ShadowHammer (2019) - https://securelist.com/operation-shadowhammer-a-high-profile-supply-chain-attack/90380/ + + mod = find_token_modifiers_for_name('imul21-add') + self.assertIsNotNone(mod) + + source = b'VirtualAlloc' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0xdf894b12) + + + # Examples: + # - Bottle Exploit Kit (2019) - https://nao-sec.org/2019/12/say-hello-to-bottle-exploit-kit.html + # - ShadowHammer (2019) - https://securelist.com/operation-shadowhammer-a-high-profile-supply-chain-attack/90380/ + + mod = find_token_modifiers_for_name('imul83-add') + self.assertIsNotNone(mod) + + source = b'GetProcAddress' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x9ab9b854) + + + # Examples: + # - ?? (2021) - https://www.threatspike.com/blogs/reflective-dll-injection + # - Mustang Panda (2022) - https://blog.talosintelligence.com/mustang-panda-targets-europe/ + + mod = find_token_modifiers_for_name('ror13') + self.assertIsNotNone(mod) + + source = b'GetProcAddress' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x7c0dfcaa) + + source = b'VirtualAlloc' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x91afca54) + + + # Example: + # - Energetic Bear (2019) - https://insights.sei.cmu.edu/blog/api-hashing-tool-imagine-that/ + + mod = find_token_modifiers_for_name('sll1-add-hash32') + self.assertIsNotNone(mod) + + source = b'LoadLibraryA' + transformed = mod.transform(source) + + self.assertEqual(b2i(transformed[0]), 0x000d5786) + + + # Example: + # - SideWinder/WarHawk (2022) - https://www.zscaler.com/blogs/security-research/warhawk-new-backdoor-arsenal-sidewinder-apt-group + + mod = find_token_modifiers_for_name('sub42') + self.assertIsNotNone(mod) + + source = b'LoadLibraryA' + transformed = mod.transform(source) + + self.assertEqual(transformed[0], b'\x8e\xb1\xa3\xa6\x8e\xab\xa4\xb4\xa3\xb4\xbb\x83') + + + # Example: + # - TrickBot (2021) - https://medium.com/walmartglobaltech/trickbot-crews-new-cobaltstrike-loader-32c72b78e81c + + mod = find_token_modifiers_for_name('sub-index1') + self.assertIsNotNone(mod) + + source = b'raw.githubusercontent.com' + transformed = mod.transform(source) + + self.assertEqual(transformed[0], b'\x73\x63\x7a\x32\x6c\x6f\x7b\x70\x7e\x6c\x80\x7f\x72\x80\x72\x7f\x7f\x86\x78\x82\x89\x44\x7a\x87\x86') + + + def testBytePatternModifiersAPI(self): + """Validate the API for pattern modifiers.""" + + mod = find_token_modifiers_for_name('plain') + self.assertIsNotNone(mod) + + source = [ b'ABC', b'01234' ] + transformed = mod.transform(source) + + self.assertEqual(len(source), len(transformed)) + self.assertEqual(source[0], transformed[0]) + self.assertEqual(source[1], transformed[1]) + + + mod = find_token_modifiers_for_name('xor') + self.assertIsNotNone(mod) + + source = [ b'ABC' ] + transformed = mod.transform(source, 0x20) + + self.assertEqual(transformed[0], b'abc') diff --git a/tests/analysis/scan/scanning_hex.py b/tests/analysis/scan/scanning_hex.py index 32979c8..4b0fda4 100644 --- a/tests/analysis/scan/scanning_hex.py +++ b/tests/analysis/scan/scanning_hex.py @@ -14,7 +14,7 @@ class TestRostScanningBinary(RostTestClass): rule = ''' rule test { - strings: + bytes: $a = { 41 } condition: @@ -31,7 +31,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 62 } condition: @@ -48,7 +48,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 66 } condition: @@ -65,7 +65,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?1 } condition: @@ -82,7 +82,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?2 } condition: @@ -99,7 +99,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?6 } condition: @@ -111,7 +111,7 @@ rule test { self.check_rule_success(rule, content=cnt) - def testLonelyPatternsNot(self): + def ___testLonelyPatternsNot(self): """Evaluate the most simple patterns (not version).""" cnt = MemoryContent(b'Abcdef') @@ -119,7 +119,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~41 } condition: @@ -136,7 +136,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~62 } condition: @@ -153,7 +153,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~66 } condition: @@ -170,7 +170,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~?1 } condition: @@ -187,7 +187,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~?2 } condition: @@ -204,7 +204,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ~?6 } condition: @@ -224,7 +224,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 41 62 63 } condition: @@ -241,7 +241,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 2d 41 62 63 } condition: @@ -261,7 +261,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?1 6? ?3 } condition: @@ -281,7 +281,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 41 6? ?3 } condition: @@ -298,7 +298,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 4? 62 ?3 } condition: @@ -315,7 +315,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 4? ?2 63 } condition: @@ -332,7 +332,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 4? ?2 ?3 } condition: @@ -349,7 +349,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 2d 4? ?2 63 } condition: @@ -366,7 +366,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 2d 4? 62 ?3 2d } condition: @@ -383,7 +383,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 2? 41 6? 63 ?d } condition: @@ -403,7 +403,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 33 ?? 41 ?? 63 ?? 34 } condition: @@ -420,7 +420,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? 33 ?? 41 ?? 63 ?? 34 ?? } condition: @@ -437,7 +437,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? 33 [1-5] 63 ?? 34 ?? } condition: @@ -454,7 +454,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { [3-4] 41 ?? 63 ?? 34 ?? } condition: @@ -471,7 +471,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? 33 ?? 41 ?? 63 [3-] } condition: @@ -491,7 +491,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?3 ?? 4? ?? 6? ?? ?4 } condition: @@ -508,7 +508,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? ?3 ?? 4? ?? 6? ?? ?4 ?? } condition: @@ -525,7 +525,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? ?3 [1-5] ?3 ?? ?4 ?? } condition: @@ -542,7 +542,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { [3-4] ?1 ?? ?3 ?? ?4 ?? } condition: @@ -559,7 +559,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ?? 3? ?? 4? ?? 6? [3-] } condition: @@ -579,7 +579,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 41 62 ( 63 | 64 | 65 ) } condition: @@ -596,7 +596,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ( 41 | f2 | f3 ) 62 63 } condition: @@ -613,7 +613,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 41 ( 61 | 62 | 63 ) 63 } condition: @@ -630,7 +630,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ( 41 62 63 | 42 62 63 | 43 62 63 ) } condition: @@ -643,14 +643,14 @@ rule test { def testPipedMaskedHexPatterns(self): - """Look for several patterns at once with piped definition.""" + """Look for several patterns at once with piped and masked definition.""" cnt = MemoryContent(b'123-Abc-456') rule = ''' rule test { - strings: + bytes: $a = { 4? 6? ( ?3 | ?4 | ?5 ) } condition: @@ -667,11 +667,11 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { ( ?1 | ?2 | ?3 ) 6? 6? } condition: - console.log("COUNTER: ", #a) and #a == 1 and @a[0] == 4 and !a[0] == 3 + #a == 1 and @a[0] == 4 and !a[0] == 3 } ''' @@ -684,7 +684,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = { 4? ( ?1 | ?2 | ?3 ) 6? } condition: @@ -696,12 +696,15 @@ rule test { self.check_rule_success(rule, content=cnt) + def testDuplicatedResultsFiltering(self): + """Filter duplicated results.""" + cnt = MemoryContent(b'123-Abc-456') rule = ''' rule test { - strings: + bytes: $a = { ( 4? ?2 ?3 | 4? 6? 6? | ?3 6? ?3 ) } condition: diff --git a/tests/analysis/scan/scanning_str.py b/tests/analysis/scan/scanning_str.py index ff36ca3..75427a7 100644 --- a/tests/analysis/scan/scanning_str.py +++ b/tests/analysis/scan/scanning_str.py @@ -14,7 +14,7 @@ class TestRostScanningStrings(RostTestClass): rule = ''' rule test { - strings: + bytes: $a = "Abc" condition: @@ -34,7 +34,7 @@ rule test { rule = r''' rule test { - strings: + bytes: $a = "\a\b\t\n\v\f\r\e\"\\\xff" condition: @@ -51,7 +51,7 @@ rule test { rule = r''' rule test { - strings: + bytes: $a = "\a\b\t\n--123--\v\f\r\e\"\\\xff" condition: @@ -71,7 +71,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "ABC" hex condition: @@ -88,7 +88,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "ABC" plain condition: @@ -105,7 +105,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "ABC" rev condition: @@ -123,7 +123,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "DEF" fullword $b = "123" fullword @@ -141,7 +141,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "DEF" fullword $b = "123" fullword @@ -159,7 +159,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "DEF" fullword $b = "123" fullword @@ -180,7 +180,7 @@ rule test { rule = ''' rule test { - strings: + bytes: $a = "Abc" nocase $b = "ABC123DEF456GHI" nocase $z = "z0z1z2z3z4z5z6z7z8z9" nocase diff --git a/tests/common/bitfield.py b/tests/common/bitfield.py index aff5de6..75dfb6e 100644 --- a/tests/common/bitfield.py +++ b/tests/common/bitfield.py @@ -231,6 +231,35 @@ class TestBitFields(ChrysalideTestCase): self.assertNotEqual(bf_a, bf_b) + def testSearchOfSetBit(self): + """Find the next set bit in a bit field.""" + + size = 128 + bf = BitField(size, 0) + + bits = [ 0, 1, 50, 63, 64, 65, 111 ] + + for b in bits: + bf.set(b, 1) + + prev = None + found = [] + + while prev is None or prev < size: + + if prev is None: + f = bf.find_next_set() + else: + f = bf.find_next_set(prev) + + if f < size: + found.append(f) + + prev = f + + self.assertEqual(found, bits) + + def testRealCase00(self): """Test bits in bitfields against other bitfields in a real case (#02).""" diff --git a/tests/plugins/encodings/all.py b/tests/plugins/encodings/all.py new file mode 100644 index 0000000..a856ccb --- /dev/null +++ b/tests/plugins/encodings/all.py @@ -0,0 +1,23 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.plugins import encodings + +import base64 + + +class TestEncodingsModule(ChrysalideTestCase): + """TestCase for encodings plugin.""" + + def testBase64Encoding(self): + """Validate the base64 implementation.""" + + text = '0123456789abcdef' + + for i in range(len(text) + 1): + + src = text[:i].encode('ascii') + + encoded = encodings.base64_encode(src) + ref = base64.b64encode(src) + + self.assertEqual(encoded, ref) diff --git a/tests/plugins/kaitai/__init__.py b/tests/plugins/kaitai/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/plugins/kaitai/__init__.py diff --git a/tests/plugins/kaitai/language.py b/tests/plugins/kaitai/language.py index b1e8881..43b6185 100644 --- a/tests/plugins/kaitai/language.py +++ b/tests/plugins/kaitai/language.py @@ -1312,8 +1312,8 @@ instances: self.assertEqual(parsed.result_0.value.value, b'\x01\x02\x03\x04\x05\x06\x07\x08\x09') - self.assertEqual(type(parsed.result_1).__name__, 'RecordValue') # result_1 - self.assertEqual(type(parsed.result_1.value).__name__, 'RecordValue') # result_1.ref + self.assertEqual(type(parsed.result_1).__name__, 'RecordDelayed') # result_1 + self.assertEqual(type(parsed.result_1.value).__name__, 'RecordDelayed') # result_1.ref self.assertEqual(type(parsed.result_1.value.value).__name__, 'RecordList') # result_1.ref.table self.assertEqual(parsed.result_1.value.value[3].value, 0x04) diff --git a/tests/plugins/kaitai/rost.py b/tests/plugins/kaitai/rost.py new file mode 100644 index 0000000..4a29ef8 --- /dev/null +++ b/tests/plugins/kaitai/rost.py @@ -0,0 +1,170 @@ +#!/usr/bin/python3-dbg +# -*- coding: utf-8 -*- + +import locale + +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 pychrysalide import core +from pychrysalide.plugins.kaitai.parsers import KaitaiStruct +from pychrysalide.plugins.kaitai.rost import KaitaiTrigger + + +class TestScansWithKaitai(ChrysalideTestCase): + """TestCase for ROST scan with the KaitaiStruct parsing.""" + + @classmethod + def setUpClass(cls): + + super(TestScansWithKaitai, cls).setUpClass() + + cls._options = ScanOptions() + cls._options.backend_for_data = AcismBackend + + + def testSimpleKaitaiDefinitionForScanning(self): + """Rely on basic Kaitai simple definition for scanning.""" + + definitions = ''' +meta: + id: basic_test +seq: + - id: field0 + type: u1 +''' + + kstruct = KaitaiStruct(definitions) + + trigger = KaitaiTrigger(kstruct) + + root_ns = core.get_rost_root_namespace() + + ns = root_ns.resolve('kaitai') + ns.register_item(trigger) + + ns = ns.resolve('basic_test') + self.assertEqual(ns, trigger) + + cnt = MemoryContent(b'\x01\x02\x03') + + rule = ''' +rule testing { + + condition: + kaitai.basic_test.field0 == 1 + +} +''' + + scanner = ContentScanner(rule) + ctx = scanner.analyze(self._options, cnt) + + self.assertIsNotNone(ctx) + + self.assertFalse(ctx.has_match_for_rule('no_such_rule')) + + self.assertTrue(ctx.has_match_for_rule('testing')) + + + definitions = ''' +meta: + id: other_basic_test +seq: + - id: field0 + type: u1 + - id: field1 + type: u1 +''' + + kstruct = KaitaiStruct(definitions) + + trigger = KaitaiTrigger(kstruct) + + root_ns = core.get_rost_root_namespace() + + ns = root_ns.resolve('kaitai') + ns.register_item(trigger) + + ns = ns.resolve('other_basic_test') + self.assertEqual(ns, trigger) + + cnt = MemoryContent(b'\x01\x02\x03') + + rule = ''' +rule testing { + + condition: + kaitai.other_basic_test.field0 == 1 and kaitai.other_basic_test.field1 == 2 + +} +''' + + scanner = ContentScanner(rule) + ctx = scanner.analyze(self._options, cnt) + + self.assertIsNotNone(ctx) + + self.assertTrue(ctx.has_match_for_rule('testing')) + + + rule = ''' +rule testing { + + condition: + kaitai.other_basic_test.field0 == 1 and kaitai.other_basic_testXXXX.field1 == 2 + +} +''' + + scanner = ContentScanner(rule) + ctx = scanner.analyze(self._options, cnt) + + self.assertIsNotNone(ctx) + + self.assertFalse(ctx.has_match_for_rule('testing')) + + + def testKaitaiDefinitionWithListForScanning(self): + """Access list items from Kaitai definition when scanning with ROST.""" + + definitions = ''' +meta: + id: test_with_list +seq: + - id: field0 + type: u1 + repeat: eos +''' + + kstruct = KaitaiStruct(definitions) + + trigger = KaitaiTrigger(kstruct) + + root_ns = core.get_rost_root_namespace() + + ns = root_ns.resolve('kaitai') + ns.register_item(trigger) + + ns = ns.resolve('test_with_list') + self.assertEqual(ns, trigger) + + cnt = MemoryContent(b'\x01\x02\x03') + + rule = ''' +rule testing { + + condition: + kaitai.test_with_list.field0[0] == 1 and kaitai.test_with_list.field0[1] == 2 and kaitai.test_with_list.field0[2] == 3 + +} +''' + + scanner = ContentScanner(rule) + ctx = scanner.analyze(self._options, cnt) + + self.assertIsNotNone(ctx) + + self.assertTrue(ctx.has_match_for_rule('testing')) |
