@ -9,19 +9,23 @@ import time
import requests
from app . config import MachineConfig
from app . exceptions import ServerError , ConfigurationError
from app . exceptions import ServerError , ConfigurationError , PluginError
from app . pluginmanager import PluginManager
from app . calderacontrol import CalderaControl
from app . interface_sfx import CommandlineColors
from app . attack_log import AttackLog
from plugins . base . machinery import MachineryPlugin
from plugins . base . sensor import SensorPlugin
from plugins . base . vulnerability_plugin import VulnerabilityPlugin
from app . config_verifier import Attacker , Target
from typing import Any , Optional , Union
class Machine ( ) :
""" A virtual machine. Attacker or target. Abstracting stuff away. """
def __init__ ( self , config , attack_logger , calderakey = " ADMIN123 " , ) :
def __init__ ( self , config : Union [ dict , MachineConfig , Attacker , Target ] , attack_logger : AttackLog , calderakey : str = " ADMIN123 " , ) - > None :
"""
: param config : The machine configuration as dict
@ -29,14 +33,22 @@ class Machine():
: param calderakey : Key to the caldera controller
"""
self . vm_manager = None
self . attack_logger = None
self . set_attack_logger ( attack_logger )
self . vm_manager : Optional [ MachineryPlugin ] = None
self . attack_logger : AttackLog = attack_logger
self . calderakey : str = calderakey
self . sensors : list [ SensorPlugin ] = [ ] # Sensor plugins
self . vulnerabilities : list [ VulnerabilityPlugin ] = [ ] # Vulnerability plugins
self . caldera_server : str = " "
if isinstance ( config , MachineConfig ) :
self . config = config
el se:
el if i sinstanc e( config , Attacker ) :
self . config = MachineConfig ( config )
elif isinstance ( config , Target ) :
self . config = MachineConfig ( config )
else :
print ( type ( config ) )
raise ConfigurationError ( " unknown type " )
self . plugin_manager = PluginManager ( self . attack_logger )
@ -46,8 +58,6 @@ class Machine():
if self . config . vmcontroller ( ) == " running_vm " :
self . __parse_running_vm_config__ ( )
self . caldera_server = None
self . abs_machinepath_external = None
self . abs_machinepath_external = os . path . join ( self . vagrantfilepath , self . config . machinepath ( ) )
@ -58,13 +68,14 @@ class Machine():
raise ConfigurationError ( f " machinepath does not exist: { self . abs_machinepath_external } " )
self . load_machine_plugin ( )
self . caldera_basedir = self . vm_manager . get_playground ( )
self . calderakey = calderakey
self . sensors = [ ] # Sensor plugins
self . vulnerabilities = [ ] # Vulnerability plugins
def __parse_vagrant_config__ ( self ) :
if self . vm_manager is None :
raise ConfigurationError ( " VM manager required " )
playground = self . vm_manager . get_playground ( )
if playground is None :
playground = " "
self . caldera_basedir : str = playground
def __parse_vagrant_config__ ( self ) - > None :
""" Check if a file configured in the config is present """
self . vagrantfilepath = os . path . abspath ( self . config . vagrantfilepath ( ) )
@ -72,113 +83,152 @@ class Machine():
if not os . path . isfile ( self . vagrantfile ) :
raise ConfigurationError ( f " Vagrantfile not existing: { self . vagrantfile } " )
def __parse_running_vm_config__ ( self ) :
def __parse_running_vm_config__ ( self ) - > None :
""" Check if a file configured in the config is present """
self . vagrantfilepath = os . path . abspath ( self . config . vagrantfilepath ( ) )
self . vagrantfile = os . path . join ( self . vagrantfilepath , " Vagrantfile " )
def get_paw ( self ) :
def get_paw ( self ) - > Optional [ str ] :
""" Returns the paw of the current machine """
return self . config . caldera_paw ( )
def get_group ( self ) :
def get_group ( self ) - > Optional [ str ] :
""" Returns the group of the current machine """
return self . config . caldera_group ( )
def destroy ( self ) :
def destroy ( self ) - > None :
""" Destroys the current machine """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
self . vm_manager . __call_destroy__ ( )
def create ( self , reboot = True ) :
def create ( self , reboot : bool = True ) - > None :
""" Create a VM
: param reboot : Reboot the VM during installation . Required if you want to install software
"""
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
self . vm_manager . __call_create__ ( reboot )
def reboot ( self ) :
def reboot ( self ) - > None :
""" Reboot a machine """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
if self . get_os ( ) == " windows " :
self . remote_run ( " shutdown /r " )
self . vm_manager . __call_disconnect__ ( )
time . sleep ( 60 ) # Shutdown can be slow....
if self . get_os ( ) == " linux " :
self . remote_run ( " reboot" )
self . remote_run ( " sudo reboot" , must_succeed = False )
self . vm_manager . __call_disconnect__ ( )
res = None
while not res :
time . sleep ( 5 )
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
res = self . vm_manager . __call_connect__ ( )
self . attack_logger . vprint ( " Re-connecting.... " , 3 )
if self . attack_logger is not None :
self . attack_logger . vprint ( " Re-connecting.... " , 3 )
self . attack_logger . vprint ( f " The machine { self . vm_manager . get_vm_name ( ) } is back { res . is_connected } " , 3 )
def up ( self ) : # pylint: disable=invalid-name
def up ( self ) - > None : # pylint: disable=invalid-name
""" Starts a VM. Creates it if not already created """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
self . vm_manager . __call_up__ ( )
def halt ( self ) :
def halt ( self ) - > None :
""" Halts a VM """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
self . vm_manager . __call_halt__ ( )
def getuser ( self ) :
def getuser ( self ) - > str :
""" Gets the user of the current VM """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
return " Result " + str ( self . vm_manager . __call_remote_run__ ( " echo $USER " ) )
def connect ( self ) :
def connect ( self ) - > Any :
""" command connection. establish it """
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
return self . vm_manager . __call_connect__ ( )
def disconnect ( self , connection ) :
def disconnect ( self , connection : Any ) - > None :
""" Command connection dis-connect """
self . vm_manager . __call_disconnect__ ( connection )
if self . vm_manager is None :
raise ConfigurationError ( " VM Manager is missing " )
self . vm_manager . __call_disconnect__ ( )
def remote_run ( self , cmd , disown = False ) :
def remote_run ( self , cmd : str , disown : bool = False , must_succeed : bool = False ) - > str :
""" Simplifies connect and run
: param cmd : Command to run as shell command
: param disown : run in background
: param must_succeed : Throw an exception if the command being run fails .
"""
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . __call_remote_run__ ( cmd , disown , must_succeed )
return self . vm_manager . __call_remote_run__ ( cmd , disown )
def load_machine_plugin ( self ) :
def load_machine_plugin ( self ) - > None :
""" Loads the matching machine plugin """
for plugin in self . plugin_manager . get_plugins ( MachineryPlugin , [ self . config . vmcontroller ( ) ] ) :
if not isinstance ( plugin , MachineryPlugin ) :
raise PluginError ( " Expected Machinery Plugin " )
name = plugin . get_name ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing machinery: { name } { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing machinery: { name } { CommandlineColors . ENDC } " , 1 )
syscon = { " abs_machinepath_internal " : self . abs_machinepath_internal ,
" abs_machinepath_external " : self . abs_machinepath_external }
plugin . set_sysconf ( syscon )
plugin . __call_process_config__ ( self . config )
self . vm_manager = plugin
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed machinery: { name } { CommandlineColors . ENDC } " ,
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed machinery: { name } { CommandlineColors . ENDC } " ,
1 )
break
def prime_sensors ( self ) :
def prime_sensors ( self ) - > bool :
""" Prime sensors from plugins (hard core installs that could require a reboot)
A machine can have several sensors running . Those are defined in a list in the config . This primes the sensors
: result : true if a reboot is required
"""
reboot = False
for plugin in self . plugin_manager . get_plugins ( SensorPlugin , self . config . sensors ( ) ) :
if not isinstance ( plugin , SensorPlugin ) :
raise PluginError ( " Expected sensor plugin " )
name = plugin . get_name ( )
# if name in self.config.sensors():
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Priming sensor: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Priming sensor: { name } { CommandlineColors . ENDC } " , 2 )
syscon = { " abs_machinepath_internal " : self . abs_machinepath_internal ,
" abs_machinepath_external " : self . abs_machinepath_external ,
}
@ -189,10 +239,11 @@ class Machine():
plugin . setup ( )
reboot | = plugin . prime ( )
self . sensors . append ( plugin )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Primed sensor: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Primed sensor: { name } { CommandlineColors . ENDC } " , 2 )
return reboot
def install_sensors ( self ) :
def install_sensors ( self ) - > None :
""" Install sensors from plugins
A machine can have several sensors running . Those are defined in a list in the config . This installs the sensors
@ -202,7 +253,8 @@ class Machine():
for plugin in self . get_sensors ( ) :
name = plugin . get_name ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing sensor: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing sensor: { name } { CommandlineColors . ENDC } " , 2 )
syscon = { " abs_machinepath_internal " : self . abs_machinepath_internal ,
" abs_machinepath_external " : self . abs_machinepath_external ,
}
@ -211,25 +263,28 @@ class Machine():
plugin . process_config ( self . config . raw_config . get ( name , { } ) ) # plugin specific configuration
plugin . setup ( )
plugin . install ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed sensor: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed sensor: { name } { CommandlineColors . ENDC } " , 2 )
def get_sensors ( self ) - > list [ SensorPlugin ] :
""" Returns a list of running sensors """
return self . sensors
def start_sensors ( self ) :
def start_sensors ( self ) - > None :
""" Start sensors
A machine can have several sensors running . Those are defined in a list in the config . This starts the sensors
"""
for plugin in self . get_sensors ( ) :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
plugin . set_machine_plugin ( self . vm_manager )
plugin . start ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Started sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Started sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
def stop_sensors ( self ) :
def stop_sensors ( self ) - > None :
""" Stop sensors
A machine can have several sensors running . Those are defined in a list in the config . This stops the sensors
@ -237,17 +292,20 @@ class Machine():
"""
for plugin in self . get_sensors ( ) :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Stopping sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Stopping sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
plugin . set_machine_plugin ( self . vm_manager )
plugin . stop ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Stopped sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Stopped sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
def collect_sensors ( self , lootdir ):
def collect_sensors ( self , lootdir : str ) - > list [ str ] :
""" Collect data from sensors
A machine can have several sensors running . Those are defined in a list in the config . This collects the data from the sensors
: param lootdir : Fresh created directory for loot
: returns : a list of file names to put into the loot zip
"""
machine_specific_path = os . path . join ( lootdir , self . config . vmname ( ) )
@ -255,27 +313,32 @@ class Machine():
loot_files = [ ]
for plugin in self . get_sensors ( ) :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Collecting sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Collecting sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
plugin . set_machine_plugin ( self . vm_manager )
loot_files + = plugin . __call_collect__ ( machine_specific_path )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Collected sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Collected sensor: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
return loot_files
############
def prime_vulnerabilities ( self ) :
def prime_vulnerabilities ( self ) - > bool :
""" Prime vulnerabilities from plugins (hard core installs that could require a reboot)
A machine can have several vulnerabilities . Those are defined in a list in the config .
: returns : True if a reboot is requires
"""
reboot = False
for plugin in self . plugin_manager . get_plugins ( VulnerabilityPlugin , self . config . vulnerabilities ( ) ) :
if not isinstance ( plugin , VulnerabilityPlugin ) :
raise PluginError ( " Plugin manager returned wrong plugin type " )
name = plugin . get_name ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Priming vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Priming vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
syscon = { " abs_machinepath_internal " : self . abs_machinepath_internal ,
" abs_machinepath_external " : self . abs_machinepath_external ,
}
@ -285,10 +348,11 @@ class Machine():
plugin . setup ( )
reboot | = plugin . prime ( )
self . vulnerabilities . append ( plugin )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Primed vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Primed vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
return reboot
def install_vulnerabilities ( self ) :
def install_vulnerabilities ( self ) - > None :
""" Install vulnerabilities from plugins: The machine is not yet modified ! For that call start_vulnerabilities next
A machine can have several vulnerabilities . Those are defined in a list in the config . This installs the vulnerabilities
@ -296,9 +360,12 @@ class Machine():
"""
for plugin in self . plugin_manager . get_plugins ( VulnerabilityPlugin , self . config . vulnerabilities ( ) ) :
if not isinstance ( plugin , VulnerabilityPlugin ) :
raise PluginError ( " Plugin manager returned wrong plugin type " )
name = plugin . get_name ( )
self . attack_logger . vprint ( f " Configured vulnerabilities: { self . config . vulnerabilities ( ) } " , 3 )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " Configured vulnerabilities: { self . config . vulnerabilities ( ) } " , 3 )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing vulnerability: { name } { CommandlineColors . ENDC } " , 2 )
syscon = { " abs_machinepath_internal " : self . abs_machinepath_internal ,
" abs_machinepath_external " : self . abs_machinepath_external }
plugin . set_sysconf ( syscon )
@ -312,25 +379,27 @@ class Machine():
""" Returns a list of installed vulnerabilities """
return self . vulnerabilities
def start_vulnerabilities ( self ) :
def start_vulnerabilities ( self ) - > None :
""" Really install the vulnerabilities on the machine
A machine can have vulnerabilities installed . Those are defined in a list in the config . This starts the vulnerabilities
"""
for plugin in self . get_vulnerabilities ( ) :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Activating vulnerability: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Activating vulnerability: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
plugin . set_machine_plugin ( self . vm_manager )
plugin . start ( )
def stop_vulnerabilities ( self ) :
def stop_vulnerabilities ( self ) - > None :
""" Un-install the vulnerabilities on the machine
A machine can have vulnerabilities installed . Those are defined in a list in the config . This stops the vulnerabilities
"""
for plugin in self . get_vulnerabilities ( ) :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Uninstalling vulnerability: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Uninstalling vulnerability: { plugin . get_name ( ) } { CommandlineColors . ENDC } " , 2 )
plugin . set_machine_plugin ( self . vm_manager )
plugin . stop ( )
@ -340,6 +409,8 @@ class Machine():
""" Returns the IP of the main ethernet interface of this machine """
# TODO: Find a smarter way to get the ip
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . get_ip ( )
@ -353,23 +424,31 @@ class Machine():
return self . config . get_nicknames ( )
def get_playground ( self ) - > str :
def get_playground ( self ) - > Optional [ str ] :
""" Return this machine ' s playground """
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . get_playground ( )
def get_machine_path_external ( self ) - > str :
""" Returns the external path for this machine """
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . get_machine_path_external ( )
def put ( self , src : str , dst : str ) :
def put ( self , src : str , dst : str ) - > Any :
""" Send a file to the machine """
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . put ( src , dst )
def get ( self , src : str , dst : str ) :
def get ( self , src : str , dst : str ) - > Any :
""" Get a file from a machine """
if self . vm_manager is None :
raise ConfigurationError ( " Missing VM Manager " )
return self . vm_manager . get ( src , dst )
@ -391,14 +470,18 @@ class Machine():
# TODO: Metasploit implant
# options for version: "4.0.0-alpha.2" and "2.8.1"
def install_caldera_server ( self , cleanup = False , version = " 4.0.0-alpha.2 " ) :
def install_caldera_server ( self , cleanup : bool = False , version : str = " 4.0.0-alpha.2 " ) - > str :
""" Installs the caldera server on the VM
: param cleanup : Remove the old caldera version . Slow but reduces side effects
: param version : Caldera version to use . Check Caldera git for potential branches to use
"""
# https://github.com/mitre/caldera.git
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing Caldera server { CommandlineColors . ENDC } " , 1 )
if self . vm_manager is None :
raise ConfigurationError ( " VM manager missing " )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing Caldera server { CommandlineColors . ENDC } " , 1 )
if cleanup :
cleanupcmd = " rm -rf caldera; "
@ -406,20 +489,25 @@ class Machine():
cleanupcmd = " "
cmd = f " cd { self . caldera_basedir } ; { cleanupcmd } git clone https://github.com/mitre/caldera.git --recursive --branch { version } ; cd caldera; git checkout { version } ; pip3 install -r requirements.txt "
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera server installed { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera server installed { CommandlineColors . ENDC } " , 1 )
res = self . vm_manager . __call_remote_run__ ( cmd )
return " Result installing caldera server " + str ( res )
def wait_for_caldera_server ( self , timeout = 6 ) :
def wait_for_caldera_server ( self , timeout : int = 6 ) - > bool :
""" Ping caldera server. return as soon as it is responding
: param timeout : timeout in seconds
"""
if self . attack_logger is None :
raise ConfigurationError ( " Attack logger required " )
for i in range ( timeout ) :
time . sleep ( 10 )
caldera_url = " http:// " + self . get_ip ( ) + " :8888 "
caldera_control = CalderaControl ( caldera_url , self . attack_logger , apikey = self . calderakey )
self . attack_logger . vprint ( f " { i } Trying to connect to { caldera_url } Caldera API " , 3 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { i } Trying to connect to { caldera_url } Caldera API " , 3 )
try :
caldera_control . list_adversaries ( )
except requests . exceptions . ConnectionError :
@ -429,20 +517,22 @@ class Machine():
return True
raise ServerError
def start_caldera_server ( self ) :
def start_caldera_server ( self ) - > None :
""" Start the caldera server on the VM. Required for an attacker VM """
# https://github.com/mitre/caldera.git
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting Caldera server { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting Caldera server { CommandlineColors . ENDC } " , 1 )
# The pkill was added because the server sometimes gets stuck. And we can not re-create the attacking machines in all cases
self . remote_run ( " pkill -f server.py ;" , disown = False )
self . remote_run ( " pkill -f server.py || true ;" , disown = False )
cmd = f " cd { self . caldera_basedir } ; cd caldera ; nohup python3 server.py --insecure & "
self . remote_run ( cmd , disown = True )
self . wait_for_caldera_server ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera server started. Confirmed it is running. { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera server started. Confirmed it is running. { CommandlineColors . ENDC } " , 1 )
def create_start_caldera_client_cmd ( self ) :
def create_start_caldera_client_cmd ( self ) - > str :
""" Creates a command to start the caldera client """
playground = self . get_playground ( )
@ -462,20 +552,32 @@ class Machine():
return cmd
def start_caldera_client ( self ) :
def start_caldera_client ( self ) - > None :
""" Install caldera client. Required on targets """
if self . vm_manager is None :
raise PluginError ( " Vm manager not available " )
name = self . vm_manager . get_vm_name ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting Caldera client { name } { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Starting Caldera client { name } { CommandlineColors . ENDC } " , 1 )
if self . get_os ( ) == " windows " :
if self . caldera_server is None :
raise ConfigurationError ( " Caldera server not set " )
url = " http:// " + self . caldera_server + " :8888 "
if not isinstance ( self . attack_logger , AttackLog ) :
raise ConfigurationError ( " attack_logger is not of type AttackLog " )
if self . abs_machinepath_external is None :
raise ConfigurationError ( " External machine path not set " )
caldera_control = CalderaControl ( url , self . attack_logger , apikey = self . calderakey )
caldera_control . fetch_client ( platform = " windows " ,
file = " sandcat.go " ,
target_dir = self . abs_machinepath_external ,
extension = " .go " )
dst = self . get_playground ( )
if self . abs_machinepath_external is None :
raise ConfigurationError ( " External machine path not set " )
src = os . path . join ( self . abs_machinepath_external , " caldera_agent.bat " )
self . vm_manager . put ( src , dst )
src = os . path . join ( self . abs_machinepath_external , " splunkd.go " ) # sandcat.go local name
@ -488,6 +590,10 @@ class Machine():
if self . get_os ( ) == " linux " :
dst = self . get_playground ( )
if self . abs_machinepath_external is None :
raise ConfigurationError ( " machine_path external not set " )
if dst is None :
raise ConfigurationError ( " Missing playground " )
src = os . path . join ( self . abs_machinepath_external , " caldera_agent.sh " )
self . vm_manager . put ( src , dst )
@ -496,14 +602,15 @@ class Machine():
print ( cmd )
self . remote_run ( cmd , disown = True )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera client started { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Caldera client started { CommandlineColors . ENDC } " , 1 )
def get_os ( self ) :
def get_os ( self ) - > str :
""" Returns the OS of the machine """
return self . config . os ( )
def __wmi_cmd_for_caldera_implant ( self ) :
def __wmi_cmd_for_caldera_implant ( self ) - > str :
""" Creates a windows specific command to start the caldera implant in background using wmi """
playground = self . get_playground ( )
@ -511,15 +618,23 @@ class Machine():
playground = playground + " \\ "
else :
playground = " %u serprofile % \\ "
if self . caldera_server is None :
raise ConfigurationError ( " Caldera server not configured " )
url = " http:// " + self . caldera_server + " :8888 "
res = f ' wmic process call create " { playground } splunkd.go -server { url } -group { self . config . caldera_group ( ) } -paw { self . config . caldera_paw ( ) } " '
return res
def __install_caldera_service_cmd ( self ) :
def __install_caldera_service_cmd ( self ) - > str :
playground = self . get_playground ( )
if self . abs_machinepath_external is None :
raise ConfigurationError ( " machine path external is not set " )
if self . attack_logger is None :
raise
if self . get_os ( ) == " linux " :
return f """
#!/bin/bash
@ -551,14 +666,18 @@ START {playground}{filename} -server {url} -group {self.config.caldera_group()}
raise Exception # System type unknown
def install_caldera_service ( self ) :
def install_caldera_service ( self ) - > None :
""" Install the caldera client as a service. For linux targets """
# print("DELETEME ! " + sys._getframe().f_code.co_name)
content = self . __install_caldera_service_cmd ( )
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing Caldera service { CommandlineColors . ENDC } " , 1 )
if self . abs_machinepath_external is None :
raise ConfigurationError ( " machine path external is not set " )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKBLUE } Installing Caldera service { CommandlineColors . ENDC } " , 1 )
if self . get_os ( ) == " linux " :
filename = os . path . join ( self . abs_machinepath_external , " caldera_agent.sh " )
@ -566,16 +685,10 @@ START {playground}{filename} -server {url} -group {self.config.caldera_group()}
filename = os . path . join ( self . abs_machinepath_external , " caldera_agent.bat " )
with open ( filename , " wt " ) as fh :
fh . write ( content )
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed Caldera service { CommandlineColors . ENDC } " , 1 )
if self . attack_logger is not None :
self . attack_logger . vprint ( f " { CommandlineColors . OKGREEN } Installed Caldera service { CommandlineColors . ENDC } " , 1 )
def set_caldera_server ( self , server ):
def set_caldera_server ( self , server : str ) - > None :
""" Set the local caldera server config """
self . caldera_server = server
def set_attack_logger ( self , attack_logger ) :
""" Configure the attack logger for this server
: param attack_logger : The attack logger to set
"""
self . attack_logger = attack_logger