diff --git a/app/calderacontrol.py b/app/calderacontrol.py index e956aaf..655325d 100644 --- a/app/calderacontrol.py +++ b/app/calderacontrol.py @@ -198,6 +198,20 @@ class CalderaControl(): res.append(ability) return res + def does_ability_support_platform(self, abid: str, platform: str) -> bool: + """ Checks if an ability supports a specific os + + @param abid: ability id. + @param platform: os string to match for + """ + + # caldera knows the os-es "windows", "linux" and "darwin" + + for ability in self.get_ability(abid): + if ability["platform"] == platform: + return True + return False + def get_operation_by_id(self, op_id): """ Get operation by id @@ -492,13 +506,16 @@ class CalderaControl(): # ######## All inclusive methods - def attack(self, attack_logger: AttackLog = None, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red"): + def attack(self, attack_logger: AttackLog = None, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red", target_platform=None): """ Attacks a system and returns results @param attack_logger: An attack logger class to log attacks with @param paw: Paw to attack @param group: Group to attack. Paw must be in the group @param ability_id: Ability to run against the target + @param target_platform: Platform of the target machine. Optional. Used for quick-outs + + @:return : True if the attack was executed. False if it was not. For example the target os is not supported by this attack """ # Tested obfuscators (with sandcat): @@ -514,6 +531,14 @@ class CalderaControl(): adversary_name = "generated_adv__" + str(time.time()) operation_name = "testoperation__" + str(time.time()) + if target_platform: + # Check if an ability does support the platform of the target: + if not self.does_ability_support_platform(ability_id, target_platform): + self.attack_logger.vprint( + f"{CommandlineColors.FAIL}Platform {target_platform} not supported by {ability_id}{CommandlineColors.ENDC}", + 1) + return False + self.add_adversary(adversary_name, ability_id) adid = self.get_adversary(adversary_name)["adversary_id"] @@ -595,6 +620,7 @@ class CalderaControl(): obfuscator=obfuscator, jitter=jitter ) + return True def pretty_print_ability(self, abi): """ Pretty pritns an ability @@ -603,7 +629,7 @@ class CalderaControl(): """ print(""" - ID: {technique_id} + TTP: {technique_id} Technique name: {technique_name} Tactic: {tactic} Name: {name} diff --git a/app/experimentcontrol.py b/app/experimentcontrol.py index eb376d4..c191950 100644 --- a/app/experimentcontrol.py +++ b/app/experimentcontrol.py @@ -22,11 +22,12 @@ from app.exceptions import ServerError class Experiment(): """ Class handling experiments """ - def __init__(self, configfile, verbosity=0): + def __init__(self, configfile, verbosity=0, caldera_attacks: list = None): """ @param configfile: Path to the configfile to load @param verbosity: verbosity level between 0 and 3 + @param caldera_attacks: an optional argument to override caldera attacks in the config file and run just this one caldera attack. A list of caldera ID """ self.attacker_1 = None @@ -103,8 +104,9 @@ class Experiment(): # Attack them self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Running Caldera attacks{CommandlineColors.ENDC}", 1) for target_1 in self.targets: - # Run caldera attacks - caldera_attacks = self.experiment_config.get_caldera_attacks(target_1.get_os()) + if caldera_attacks is None: + # Run caldera attacks + caldera_attacks = self.experiment_config.get_caldera_attacks(target_1.get_os()) if caldera_attacks: for attack in caldera_attacks: # TODO: Work with snapshots @@ -112,11 +114,12 @@ class Experiment(): self.attack_logger.vprint(f"Attacking machine with PAW: {target_1.get_paw()} with {attack}", 2) caldera_control = CalderaControl("http://" + self.attacker_1.getip() + ":8888", self.attack_logger, config=self.experiment_config) - caldera_control.attack(attack_logger=self.attack_logger, - paw=target_1.get_paw(), - ability_id=attack, - group=target_1.get_group(), - ) + it_worked = caldera_control.attack(attack_logger=self.attack_logger, + paw=target_1.get_paw(), + ability_id=attack, + group=target_1.get_group(), + target_platform=target_1.get_os() + ) # Moved to fix section below. If fix works: can be removed # print(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}") @@ -124,23 +127,24 @@ class Experiment(): # Fix: Caldera sometimes gets stuck. This is why we better re-start the caldera server and wait till all the implants re-connected # Reason: In some scenarios we keep the infra up for hours or days. No re-creation like intended. This can cause Caldera to hick up - self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Restarting caldera server and waiting for clients to re-connect{CommandlineColors.ENDC}", 1) - self.attacker_1.start_caldera_server() - self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 2) - time.sleep(self.experiment_config.get_nap_time()) - retries = 100 - for target_system in self.targets: - running_agents = caldera_control.list_paws_of_running_agents() - self.attack_logger.vprint(f"Agents currently connected to the server: {running_agents}", 2) - while target_system.get_paw() not in running_agents: - time.sleep(1) + if it_worked: + self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Restarting caldera server and waiting for clients to re-connect{CommandlineColors.ENDC}", 1) + self.attacker_1.start_caldera_server() + self.attack_logger.vprint(f"Pausing before next attack (config: nap_time): {self.experiment_config.get_nap_time()}", 2) + time.sleep(self.experiment_config.get_nap_time()) + retries = 100 + for target_system in self.targets: running_agents = caldera_control.list_paws_of_running_agents() - retries -= 1 - self.attack_logger.vprint(f"Waiting for clients to re-connect ({retries}, {running_agents}) ", 3) - if retries <= 0: - raise ServerError - self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}", 1) - # End of fix + self.attack_logger.vprint(f"Agents currently connected to the server: {running_agents}", 2) + while target_system.get_paw() not in running_agents: + time.sleep(1) + running_agents = caldera_control.list_paws_of_running_agents() + retries -= 1 + self.attack_logger.vprint(f"Waiting for clients to re-connect ({retries}, {running_agents}) ", 3) + if retries <= 0: + raise ServerError + self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Restarted caldera server clients re-connected{CommandlineColors.ENDC}", 1) + # End of fix self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Finished Caldera attacks{CommandlineColors.ENDC}", 1) diff --git a/caldera_control.py b/caldera_control.py index 2d8a326..8506fcc 100644 --- a/caldera_control.py +++ b/caldera_control.py @@ -104,7 +104,7 @@ def create_parser(): parser_delete_agents.set_defaults(func=delete_agents) # For all parsers - main_parser.add_argument("--caldera_url", help="caldera url, including port", default="http://192.168.178.118:8888/") + main_parser.add_argument("--caldera_url", help="caldera url, including port", default="http://192.168.178.125:8888/") main_parser.add_argument("--apikey", help="caldera api key", default="ADMIN123") return main_parser diff --git a/caldera_subset_classic.txt b/caldera_subset_classic.txt new file mode 100644 index 0000000..9bd6f44 --- /dev/null +++ b/caldera_subset_classic.txt @@ -0,0 +1,13 @@ +bd527b63-9f9e-46e0-9816-b8434d2b8989 +c0da588f-79f0-4263-8998-7496b1a40596 +c1cd6388-3ced-48c7-a511-0434c6ba8f48 +feaced8f-f43f-452a-9500-a5219488abb8 +b6f545ef-f802-4537-b59d-2cb19831c8ed +3b5db901-2cb8-4df7-8043-c4628a6a5d5a +530e47c6-8592-42bf-91df-c59ffbd8541b +b22b3b47-6219-4504-a2e6-ae8263e49fc3 +2dece965-37a0-4f70-a391-0f30e3331aba +5c4dd985-89e3-4590-9b57-71fed66ff4e2 +8c06ebf8-bacf-486b-bd77-21ba8c5a5777 +ce485320-41a4-42e8-a510-f5a8fe96a644 +b007fc38-9eb7-4320-92b3-9a3ad3e6ec25 diff --git a/experiment_control.py b/experiment_control.py index c98e6ee..f4779a4 100644 --- a/experiment_control.py +++ b/experiment_control.py @@ -20,7 +20,19 @@ def run(args): @param args: arguments from the argparse parser """ - Experiment(args.configfile, args.verbose) + + if args.caldera_attack_file: + with open(args.caldera_attack_file, "rt") as fh: + for line in fh: + line = line.strip() + print(f"Running calder attack {line}") + Experiment(args.configfile, args.verbose, [line]) + + else: + caldera_attack = None + if args.caldera_attack: + caldera_attack = [args.caldera_attack] + Experiment(args.configfile, args.verbose, caldera_attack) def create_parser(): @@ -35,6 +47,8 @@ def create_parser(): parser_run = subparsers.add_parser("run", help="run experiments") parser_run.set_defaults(func=run) parser_run.add_argument("--configfile", default="experiment.yaml", help="Config file to create from") + parser_run.add_argument("--caldera_attack", default=None, help="The id of a specific caldera attack to run") + parser_run.add_argument("--caldera_attack_file", default=None, help="The file name containing a list of caldera attacks to run") return parser diff --git a/tools/shipit.py b/tools/shipit.py index afe371d..78a39bf 100755 --- a/tools/shipit.py +++ b/tools/shipit.py @@ -58,7 +58,9 @@ globs = ["TODO.md", "plugins/avast_internal_plugins/*/*/hosts", "plugins/README.md", "pylint.rc", - "shipit_log.txt"] + "shipit_log.txt", + "all_caldera_attacks_unique.txt", + "caldera_subset.txt"] try: os.remove(filename)