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')