import binascii import struct from chrysacase import ChrysalideTestCase from gi._constants import TYPE_INVALID from pychrysalide.analysis.scan import ScanExpression from pychrysalide.analysis.scan import ScanOptions from pychrysalide.analysis.scan import find_token_modifiers_for_name 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.ScanReductionState.REDUCED) 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 testBytePatternModifiers(self): """Validate the bytes produced by modifiers.""" mod = find_token_modifiers_for_name('plain') self.assertIsNotNone(mod) source = b'ABC' transformed = mod.transform(source) self.assertEqual(source, transformed[0]) mod = find_token_modifiers_for_name('hex') self.assertIsNotNone(mod) source = b'ABC' transformed = mod.transform(source) self.assertEqual(binascii.hexlify(source), transformed[0]) mod = find_token_modifiers_for_name('rev') self.assertIsNotNone(mod) source = b'ABC' 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')