"""
name: Async RAT / DCRAT / VenomRAT
category: config extractors
author: malcat

Decrypt config from a (unpacked) Async RAT, DCRAT or VenomRAT sample
"""
import base64
from pprint import pp
import malcat
import hashlib
malcat.setup()  # Add Malcat's data directories to sys.path when called in headless mode
from transforms.block import AesDecrypt
from Cryptodome.Protocol.KDF import PBKDF2

############################ config extraction

def async_rat_decrypt_config(a):
    fn_settings = a.symbols["Client.Settings.cctor"]
    config = {}
    config_encrypted = {}
    # find SALT
    salt = None
    for bb in a.fns["Client.Algorithm.Aes256.cctor"]:
        for instr in bb:
            if instr.mnemonic == "ldstr":   #dcrat and venomrat
                str_address = a.v2a(instr[0].value)
                salt = a.strings[str_address].text.encode("ascii")
                config["Encryption_Salt"] = a.strings[str_address].text
                break
            elif instr.mnemonic == "ldtoken":   #asyncrat
                fieldref = a.v2a(instr[0].value)
                # locate the corresponding static array: malcat automatically adds a cross-ref from the array to the corresponding fielddef
                for inref in a.xref[fieldref]:
                    if inref.address in a.symbols:
                        for sym in a.symbols[inref.address]:
                            if sym.name.endswith(".Data"):
                                # this is our static array, there is also a (Bytes) structure object defined there by the .NET parser
                                salt = a.struct[sym.address]
    if not salt:
        raise ValueError("Cannot locate salt")
    # extract config values
    for bb in a.fns[fn_settings]:
        prev_str = ""
        for instr in bb:
            if instr.mnemonic == "ldstr":
                prev_str = ""
                str_address = a.v2a(instr[0].value)
                if not str_address in a.strings:
                    continue
                prev_str = a.strings[str_address].text
                try:
                    prev_str = base64.b64decode(prev_str)
                except:
                    try:
                        prev_str = int(prev_str)
                    except:
                        pass
            elif instr.mnemonic == "stsfld" and prev_str:
                config_encrypted[instr[0].symbol.replace("_", "")] = prev_str
    if not "Key" in config_encrypted:
        raise ValueError("Config key not found")
    key = config_encrypted["Key"]
    del config_encrypted["Key"]
    # decrypt config
    keydata = PBKDF2(key, salt, 96, count=50000)
    aeskey = keydata[:32]
    authkey = keydata[32:]
    iv  = hashlib.sha256(authkey[32:]).digest()[:16]
    for entry, encrypted in config_encrypted.items():
        if type(encrypted) != bytes:
            config[entry] = encrypted
        else:
            decrypt = AesDecrypt().run(encrypted, mode="cbc", key=aeskey, iv=iv, unpad=True)
            decrypt = decrypt[48:]
            try:
                decrypt = decrypt.decode("ascii")
            except: pass
            config[entry] = decrypt
    return config

################################ MAIN
    
if __name__ == "__main__":

    configs = []
    if "analysis" in globals():
        # called from the gui, analysis object is already instanciated with the current file
        configs.append(async_rat_decrypt_config(analysis))
    else:
        # called in headless mode, we need to analyse a file first
        import optparse
        usage = "usage: %prog <file1> [file2] ... [fileN]"
        parser = optparse.OptionParser(usage=usage, description="""Extract config for (unpacked) AsyncRAT samples""")
        options, args = parser.parse_args()
        if len(args) < 1:
            parser.error("Please give path to a file")

        for fname in args:
            a = malcat.analyse(fname)
            configs.append(async_rat_decrypt_config(a))

    for config in configs:
        for k, v in config.items():
            print(f"{k}: {v}")
