forked from p34709852/monkey
Merge pull request #1631 from guardicore/1595-new-agent-setup
Implement Agent setup function
This commit is contained in:
commit
418b5ce9ec
|
@ -29,24 +29,6 @@
|
|||
" victims_max_exploit = 100"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/infection_monkey/monkey.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 220,
|
||||
"lines": [
|
||||
" if not WormConfiguration.alive:",
|
||||
" logger.info(\"Marked not alive from configuration\")",
|
||||
" ",
|
||||
"* machines = self._network.get_victim_machines(",
|
||||
"* max_find=WormConfiguration.victims_max_find,",
|
||||
"* stop_callback=ControlClient.check_for_stop,",
|
||||
"* )",
|
||||
" for machine in machines:",
|
||||
" if ControlClient.check_for_stop():",
|
||||
" break"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/internal.py",
|
||||
|
@ -79,7 +61,6 @@
|
|||
"app_version": "0.6.6-2",
|
||||
"file_blobs": {
|
||||
"monkey/infection_monkey/config.py": "8f4984ba6563564343282765ab498efca5d89ba8",
|
||||
"monkey/infection_monkey/monkey.py": "4160a36e0e624404d77526472d51dd07bba49e5a",
|
||||
"monkey/monkey_island/cc/services/config_schema/internal.py": "86318eaf19b9991a8af5de861a3eb085238e17a4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ class MonkeyDrops(object):
|
|||
"destination_path": self.opts.location,
|
||||
}
|
||||
|
||||
def initialize(self):
|
||||
logger.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
|
||||
|
||||
def start(self):
|
||||
|
|
|
@ -66,10 +66,16 @@ class HostExploiter(Plugin):
|
|||
def is_os_supported(self):
|
||||
return self.host.os.get("type") in self._TARGET_OS_TYPE
|
||||
|
||||
def send_exploit_telemetry(self, result):
|
||||
def send_exploit_telemetry(self, name: str, result: bool):
|
||||
from infection_monkey.telemetry.exploit_telem import ExploitTelem
|
||||
|
||||
ExploitTelem(self, result).send()
|
||||
ExploitTelem(
|
||||
name=name,
|
||||
host=self.host,
|
||||
result=result,
|
||||
info=self.exploit_info,
|
||||
attempts=self.exploit_attempts,
|
||||
).send()
|
||||
|
||||
def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
|
||||
self.exploit_attempts.append(
|
||||
|
|
|
@ -118,11 +118,9 @@ def main():
|
|||
logger.info(f"version: {get_version()}")
|
||||
|
||||
monkey = monkey_cls(monkey_args)
|
||||
monkey.initialize()
|
||||
|
||||
try:
|
||||
monkey.start()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.exception("Exception thrown from monkey's start function. More info: {}".format(e))
|
||||
|
|
|
@ -33,7 +33,7 @@ class MockMaster(IMaster):
|
|||
self._run_payload()
|
||||
|
||||
def _run_sys_info_collectors(self):
|
||||
logging.info("Running system info collectors")
|
||||
logger.info("Running system info collectors")
|
||||
system_info_telemetry = {}
|
||||
system_info_telemetry["ProcessListCollector"] = self._puppet.run_sys_info_collector(
|
||||
"ProcessListCollector"
|
||||
|
@ -43,10 +43,13 @@ class MockMaster(IMaster):
|
|||
)
|
||||
system_info = self._puppet.run_sys_info_collector("LinuxInfoCollector")
|
||||
self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info))
|
||||
logging.info("Finished running system info collectors")
|
||||
logger.info("Finished running system info collectors")
|
||||
|
||||
def _run_pbas(self):
|
||||
logging.info("Running post breach actions")
|
||||
|
||||
# TODO: Create monkey_dir and revise setup in monkey.py
|
||||
|
||||
logger.info("Running post breach actions")
|
||||
name = "AccountDiscovery"
|
||||
command, result = self._puppet.run_pba(name, {})
|
||||
self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result))
|
||||
|
@ -54,10 +57,10 @@ class MockMaster(IMaster):
|
|||
name = "CommunicateAsBackdoorUser"
|
||||
command, result = self._puppet.run_pba(name, {})
|
||||
self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result))
|
||||
logging.info("Finished running post breach actions")
|
||||
logger.info("Finished running post breach actions")
|
||||
|
||||
def _scan_victims(self):
|
||||
logging.info("Scanning network for potential victims")
|
||||
logger.info("Scanning network for potential victims")
|
||||
ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||
ports = [22, 445, 3389, 8008]
|
||||
for ip in ips:
|
||||
|
@ -78,10 +81,10 @@ class MockMaster(IMaster):
|
|||
h.services[port_scan_data.service]["banner"] = port_scan_data.banner
|
||||
|
||||
self._telemetry_messenger.send_telemetry(ScanTelem(h))
|
||||
logging.info("Finished scanning network for potential victims")
|
||||
logger.info("Finished scanning network for potential victims")
|
||||
|
||||
def _fingerprint(self):
|
||||
logging.info("Running fingerprinters on potential victims")
|
||||
logger.info("Running fingerprinters on potential victims")
|
||||
machine_1 = self._hosts["10.0.0.1"]
|
||||
machine_3 = self._hosts["10.0.0.3"]
|
||||
|
||||
|
@ -93,32 +96,35 @@ class MockMaster(IMaster):
|
|||
|
||||
self._puppet.fingerprint("HTTPFinger", machine_3)
|
||||
self._telemetry_messenger.send_telemetry(ScanTelem(machine_3))
|
||||
logging.info("Finished running fingerprinters on potential victims")
|
||||
logger.info("Finished running fingerprinters on potential victims")
|
||||
|
||||
def _exploit(self):
|
||||
logging.info("Exploiting victims")
|
||||
logger.info("Exploiting victims")
|
||||
result, info, attempts = self._puppet.exploit_host(
|
||||
"PowerShellExploiter", "10.0.0.1", {}, None
|
||||
)
|
||||
logger.info(f"Attempts for exploiting {attempts}")
|
||||
self._telemetry_messenger.send_telemetry(
|
||||
ExploitTelem("PowerShellExploiter", self._hosts["10.0.0.1"], result, info, attempts)
|
||||
)
|
||||
|
||||
result, info, attempts = self._puppet.exploit_host("SSHExploiter", "10.0.0.3", {}, None)
|
||||
logger.info(f"Attempts for exploiting {attempts}")
|
||||
self._telemetry_messenger.send_telemetry(
|
||||
ExploitTelem("SSHExploiter", self._hosts["10.0.0.3"], result, info, attempts)
|
||||
)
|
||||
logging.info("Finished exploiting victims")
|
||||
logger.info("Finished exploiting victims")
|
||||
|
||||
def _run_payload(self):
|
||||
logging.info("Running payloads")
|
||||
logger.info("Running payloads")
|
||||
# TODO: modify what FileEncryptionTelem gets
|
||||
path, success, error = self._puppet.run_payload("RansomwarePayload", {}, None)
|
||||
self._telemetry_messenger.send_telemetry(FileEncryptionTelem(path, success, error))
|
||||
logging.info("Finished running payloads")
|
||||
logger.info("Finished running payloads")
|
||||
|
||||
def terminate(self) -> None:
|
||||
logger.info("Terminating MockMaster")
|
||||
|
||||
def cleanup(self) -> None:
|
||||
# TODO: Cleanup monkey_dir and send telemetry
|
||||
pass
|
||||
|
|
|
@ -4,316 +4,239 @@ import os
|
|||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
import infection_monkey.tunnel as tunnel
|
||||
from common.utils.attack_utils import ScanStatus, UsageEnum
|
||||
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||
from common.version import get_version
|
||||
from infection_monkey.config import WormConfiguration
|
||||
from infection_monkey.control import ControlClient
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.master.mock_master import MockMaster
|
||||
from infection_monkey.model import DELAY_DELETE_CMD
|
||||
from infection_monkey.network.firewall import app as firewall
|
||||
from infection_monkey.network.HostFinger import HostFinger
|
||||
from infection_monkey.network.network_scanner import NetworkScanner
|
||||
from infection_monkey.network.tools import get_interface_to_target, is_running_on_island
|
||||
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
||||
from infection_monkey.ransomware.ransomware_payload_builder import build_ransomware_payload
|
||||
from infection_monkey.system_info import SystemInfoCollector
|
||||
from infection_monkey.network.tools import is_running_on_island
|
||||
from infection_monkey.puppet.mock_puppet import MockPuppet
|
||||
from infection_monkey.system_singleton import SystemSingleton
|
||||
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
||||
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
|
||||
from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
|
||||
from infection_monkey.telemetry.scan_telem import ScanTelem
|
||||
from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import (
|
||||
LegacyTelemetryMessengerAdapter,
|
||||
)
|
||||
from infection_monkey.telemetry.state_telem import StateTelem
|
||||
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
|
||||
from infection_monkey.telemetry.trace_telem import TraceTelem
|
||||
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
|
||||
from infection_monkey.utils.monkey_dir import (
|
||||
create_monkey_dir,
|
||||
get_monkey_dir_path,
|
||||
remove_monkey_dir,
|
||||
)
|
||||
from infection_monkey.utils.monkey_dir import get_monkey_dir_path, remove_monkey_dir
|
||||
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
|
||||
from infection_monkey.utils.signal_handler import register_signal_handlers
|
||||
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||
|
||||
MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, skipping propagation phase."
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InfectionMonkey(object):
|
||||
class InfectionMonkey:
|
||||
def __init__(self, args):
|
||||
self._keep_running = False
|
||||
self._exploited_machines = set()
|
||||
self._fail_exploitation_machines = set()
|
||||
self._singleton = SystemSingleton()
|
||||
self._parent = None
|
||||
self._default_tunnel = None
|
||||
self._args = args
|
||||
self._network = None
|
||||
self._exploiters = None
|
||||
self._fingerprint = None
|
||||
self._default_server = None
|
||||
self._default_server_port = None
|
||||
self._opts = None
|
||||
self._upgrading_to_64 = False
|
||||
self._monkey_tunnel = None
|
||||
self._post_breach_phase = None
|
||||
|
||||
def initialize(self):
|
||||
logger.info("Monkey is initializing...")
|
||||
self._master = MockMaster(MockPuppet(), LegacyTelemetryMessengerAdapter())
|
||||
self._singleton = SystemSingleton()
|
||||
self._opts = self._get_arguments(args)
|
||||
# TODO Used in propagation phase to set the default server for the victim
|
||||
self._default_server_port = None
|
||||
# TODO used in propogation phase
|
||||
self._monkey_inbound_tunnel = None
|
||||
|
||||
if not self._singleton.try_lock():
|
||||
raise Exception("Another instance of the monkey is already running")
|
||||
|
||||
@staticmethod
|
||||
def _get_arguments(args):
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument("-p", "--parent")
|
||||
arg_parser.add_argument("-t", "--tunnel")
|
||||
arg_parser.add_argument("-s", "--server")
|
||||
arg_parser.add_argument("-d", "--depth", type=int)
|
||||
arg_parser.add_argument("-vp", "--vulnerable-port")
|
||||
self._opts, self._args = arg_parser.parse_known_args(self._args)
|
||||
self.log_arguments()
|
||||
opts, _ = arg_parser.parse_known_args(args)
|
||||
InfectionMonkey._log_arguments(opts)
|
||||
return opts
|
||||
|
||||
self._parent = self._opts.parent
|
||||
self._default_tunnel = self._opts.tunnel
|
||||
self._default_server = self._opts.server
|
||||
@staticmethod
|
||||
def _log_arguments(args):
|
||||
arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()])
|
||||
logger.info(f"Monkey started with arguments: {arg_string}")
|
||||
|
||||
if self._opts.depth is not None:
|
||||
def start(self):
|
||||
if self._is_another_monkey_running():
|
||||
logger.info("Another instance of the monkey is already running")
|
||||
return
|
||||
|
||||
logger.info("Monkey is starting...")
|
||||
|
||||
self._set_propagation_depth(self._opts)
|
||||
self._add_default_server_to_config(self._opts.server)
|
||||
self._connect_to_island()
|
||||
|
||||
# TODO: Reevaluate who is responsible to send this information
|
||||
if is_windows_os():
|
||||
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
|
||||
|
||||
if InfectionMonkey._is_monkey_alive_by_config():
|
||||
logger.info("Monkey marked 'not alive' from configuration.")
|
||||
return
|
||||
|
||||
if InfectionMonkey._is_upgrade_to_64_needed():
|
||||
self._upgrade_to_64()
|
||||
logger.info("32 bit Agent can't run on 64 bit system.")
|
||||
return
|
||||
|
||||
self._setup()
|
||||
self._master.start()
|
||||
|
||||
@staticmethod
|
||||
def _set_propagation_depth(options):
|
||||
if options.depth is not None:
|
||||
WormConfiguration._depth_from_commandline = True
|
||||
WormConfiguration.depth = self._opts.depth
|
||||
WormConfiguration.depth = options.depth
|
||||
logger.debug("Setting propagation depth from command line")
|
||||
logger.debug(f"Set propagation depth to {WormConfiguration.depth}")
|
||||
|
||||
self._keep_running = True
|
||||
self._network = NetworkScanner()
|
||||
|
||||
if self._default_server:
|
||||
if self._default_server not in WormConfiguration.command_servers:
|
||||
logger.debug("Added default server: %s" % self._default_server)
|
||||
WormConfiguration.command_servers.insert(0, self._default_server)
|
||||
@staticmethod
|
||||
def _add_default_server_to_config(default_server: str):
|
||||
if default_server:
|
||||
if default_server not in WormConfiguration.command_servers:
|
||||
logger.debug("Added default server: %s" % default_server)
|
||||
WormConfiguration.command_servers.insert(0, default_server)
|
||||
else:
|
||||
logger.debug(
|
||||
"Default server: %s is already in command servers list" % self._default_server
|
||||
"Default server: %s is already in command servers list" % default_server
|
||||
)
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
logger.info("Monkey is starting...")
|
||||
|
||||
logger.debug("Starting the setup phase.")
|
||||
|
||||
# Sets island's IP and port for monkey to communicate to
|
||||
self.set_default_server()
|
||||
self.set_default_port()
|
||||
|
||||
# Create a dir for monkey files if there isn't one
|
||||
create_monkey_dir()
|
||||
|
||||
self.upgrade_to_64_if_needed()
|
||||
|
||||
ControlClient.wakeup(parent=self._parent)
|
||||
ControlClient.load_control_config()
|
||||
|
||||
if is_windows_os():
|
||||
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
|
||||
|
||||
self.shutdown_by_not_alive_config()
|
||||
|
||||
if is_running_on_island():
|
||||
WormConfiguration.started_on_island = True
|
||||
ControlClient.report_start_on_island()
|
||||
|
||||
if not ControlClient.should_monkey_run(self._opts.vulnerable_port):
|
||||
raise PlannedShutdownException(
|
||||
"Monkey shouldn't run on current machine "
|
||||
"(it will be exploited later with more depth)."
|
||||
)
|
||||
|
||||
if firewall.is_enabled():
|
||||
firewall.add_firewall_rule()
|
||||
|
||||
self._monkey_tunnel = ControlClient.create_control_tunnel()
|
||||
if self._monkey_tunnel:
|
||||
self._monkey_tunnel.start()
|
||||
|
||||
StateTelem(is_done=False, version=get_version()).send()
|
||||
TunnelTelem().send()
|
||||
|
||||
logger.debug("Starting the post-breach phase asynchronously.")
|
||||
self._post_breach_phase = Thread(target=self.start_post_breach_phase)
|
||||
self._post_breach_phase.start()
|
||||
|
||||
if not InfectionMonkey.max_propagation_depth_reached():
|
||||
logger.info("Starting the propagation phase.")
|
||||
logger.debug("Running with depth: %d" % WormConfiguration.depth)
|
||||
self.propagate()
|
||||
else:
|
||||
logger.info(
|
||||
"Maximum propagation depth has been reached; monkey will not propagate."
|
||||
)
|
||||
TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send()
|
||||
|
||||
if self._keep_running and WormConfiguration.alive:
|
||||
InfectionMonkey.run_ransomware()
|
||||
|
||||
# if host was exploited, before continue to closing the tunnel ensure the exploited
|
||||
# host had its chance to
|
||||
# connect to the tunnel
|
||||
if len(self._exploited_machines) > 0:
|
||||
time_to_sleep = WormConfiguration.keep_tunnel_open_time
|
||||
logger.info(
|
||||
"Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep
|
||||
)
|
||||
time.sleep(time_to_sleep)
|
||||
|
||||
except PlannedShutdownException:
|
||||
logger.info(
|
||||
"A planned shutdown of the Monkey occurred. Logging the reason and finishing "
|
||||
"execution."
|
||||
def _connect_to_island(self):
|
||||
# Sets island's IP and port for monkey to communicate to
|
||||
if not self._is_default_server_set():
|
||||
raise Exception(
|
||||
"Monkey couldn't find server with {} default tunnel.".format(self._opts.tunnel)
|
||||
)
|
||||
logger.exception("Planned shutdown, reason:")
|
||||
self._set_default_port()
|
||||
|
||||
finally:
|
||||
if self._monkey_tunnel:
|
||||
self._monkey_tunnel.stop()
|
||||
self._monkey_tunnel.join()
|
||||
|
||||
if self._post_breach_phase:
|
||||
self._post_breach_phase.join()
|
||||
|
||||
def start_post_breach_phase(self):
|
||||
self.collect_system_info_if_configured()
|
||||
PostBreach().execute_all_configured()
|
||||
|
||||
@staticmethod
|
||||
def max_propagation_depth_reached():
|
||||
return 0 == WormConfiguration.depth
|
||||
|
||||
def collect_system_info_if_configured(self):
|
||||
logger.debug("Calling for system info collection")
|
||||
try:
|
||||
system_info_collector = SystemInfoCollector()
|
||||
system_info = system_info_collector.get_info()
|
||||
SystemInfoTelem(system_info).send()
|
||||
except Exception as e:
|
||||
logger.exception(f"Exception encountered during system info collection: {str(e)}")
|
||||
|
||||
def shutdown_by_not_alive_config(self):
|
||||
if not WormConfiguration.alive:
|
||||
raise PlannedShutdownException("Marked 'not alive' from configuration.")
|
||||
|
||||
def propagate(self):
|
||||
ControlClient.keepalive()
|
||||
ControlClient.wakeup(parent=self._opts.parent)
|
||||
ControlClient.load_control_config()
|
||||
|
||||
self._network.initialize()
|
||||
def _is_default_server_set(self) -> bool:
|
||||
"""
|
||||
Sets the default server for the Monkey to communicate back to.
|
||||
:return
|
||||
"""
|
||||
if not ControlClient.find_server(default_tunnel=self._opts.tunnel):
|
||||
return False
|
||||
self._opts.server = WormConfiguration.current_server
|
||||
logger.debug("default server set to: %s" % self._opts.server)
|
||||
return True
|
||||
|
||||
self._fingerprint = HostFinger.get_instances()
|
||||
@staticmethod
|
||||
def _is_monkey_alive_by_config():
|
||||
return not WormConfiguration.alive
|
||||
|
||||
self._exploiters = HostExploiter.get_classes()
|
||||
@staticmethod
|
||||
def _is_upgrade_to_64_needed():
|
||||
return WindowsUpgrader.should_upgrade()
|
||||
|
||||
if not WormConfiguration.alive:
|
||||
logger.info("Marked not alive from configuration")
|
||||
def _upgrade_to_64(self):
|
||||
self._singleton.unlock()
|
||||
logger.info("32bit monkey running on 64bit Windows. Upgrading.")
|
||||
WindowsUpgrader.upgrade(self._opts)
|
||||
logger.info("Finished upgrading from 32bit to 64bit.")
|
||||
|
||||
machines = self._network.get_victim_machines(
|
||||
max_find=WormConfiguration.victims_max_find,
|
||||
stop_callback=ControlClient.check_for_stop,
|
||||
)
|
||||
for machine in machines:
|
||||
if ControlClient.check_for_stop():
|
||||
break
|
||||
def _setup(self):
|
||||
logger.debug("Starting the setup phase.")
|
||||
|
||||
for finger in self._fingerprint:
|
||||
logger.info(
|
||||
"Trying to get OS fingerprint from %r with module %s",
|
||||
machine,
|
||||
finger.__class__.__name__,
|
||||
)
|
||||
try:
|
||||
finger.get_host_fingerprint(machine)
|
||||
except BaseException as exc:
|
||||
logger.error(
|
||||
"Failed to run fingerprinter %s, exception %s" % finger.__class__.__name__,
|
||||
str(exc),
|
||||
)
|
||||
|
||||
ScanTelem(machine).send()
|
||||
|
||||
# skip machines that we've already exploited
|
||||
if machine in self._exploited_machines:
|
||||
logger.debug("Skipping %r - already exploited", machine)
|
||||
continue
|
||||
|
||||
if self._monkey_tunnel:
|
||||
self._monkey_tunnel.set_tunnel_for_host(machine)
|
||||
if self._default_server:
|
||||
if self._network.on_island(self._default_server):
|
||||
machine.set_default_server(
|
||||
get_interface_to_target(machine.ip_addr)
|
||||
+ (":" + self._default_server_port if self._default_server_port else "")
|
||||
)
|
||||
else:
|
||||
machine.set_default_server(self._default_server)
|
||||
logger.debug(
|
||||
"Default server for machine: %r set to %s" % (machine, machine.default_server)
|
||||
)
|
||||
|
||||
# Order exploits according to their type
|
||||
self._exploiters = sorted(
|
||||
self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value
|
||||
if self._should_exit_for_performance():
|
||||
logger.info(
|
||||
"Monkey shouldn't run on current machine to improve perfomance"
|
||||
"(it will be exploited later with more depth)."
|
||||
)
|
||||
host_exploited = False
|
||||
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
||||
if self.try_exploiting(machine, exploiter):
|
||||
host_exploited = True
|
||||
VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send()
|
||||
if exploiter.RUNS_AGENT_ON_SUCCESS:
|
||||
break # if adding machine to exploited, won't try other exploits
|
||||
# on it
|
||||
if not host_exploited:
|
||||
self._fail_exploitation_machines.add(machine)
|
||||
VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send()
|
||||
if not self._keep_running:
|
||||
break
|
||||
return
|
||||
|
||||
if not WormConfiguration.alive:
|
||||
logger.info("Marked not alive from configuration")
|
||||
if firewall.is_enabled():
|
||||
firewall.add_firewall_rule()
|
||||
|
||||
def upgrade_to_64_if_needed(self):
|
||||
if WindowsUpgrader.should_upgrade():
|
||||
self._upgrading_to_64 = True
|
||||
self._singleton.unlock()
|
||||
logger.info("32bit monkey running on 64bit Windows. Upgrading.")
|
||||
WindowsUpgrader.upgrade(self._opts)
|
||||
raise PlannedShutdownException("Finished upgrading from 32bit to 64bit.")
|
||||
self._monkey_inbound_tunnel = ControlClient.create_control_tunnel()
|
||||
if self._monkey_inbound_tunnel:
|
||||
self._monkey_inbound_tunnel.start()
|
||||
|
||||
StateTelem(is_done=False, version=get_version()).send()
|
||||
TunnelTelem().send()
|
||||
|
||||
register_signal_handlers(self._master)
|
||||
|
||||
def _should_exit_for_performance(self):
|
||||
"""
|
||||
This method implements propagation performance enhancing algorithm that
|
||||
kicks in if the run was started from the Island.
|
||||
Should get replaced by other, better performance enhancement solutions
|
||||
"""
|
||||
if is_running_on_island():
|
||||
WormConfiguration.started_on_island = True
|
||||
ControlClient.report_start_on_island()
|
||||
|
||||
return not ControlClient.should_monkey_run(self._opts.vulnerable_port)
|
||||
|
||||
def _is_another_monkey_running(self):
|
||||
return not self._singleton.try_lock()
|
||||
|
||||
def _set_default_port(self):
|
||||
try:
|
||||
self._default_server_port = self._opts.server.split(":")[1]
|
||||
except KeyError:
|
||||
self._default_server_port = ""
|
||||
|
||||
def cleanup(self):
|
||||
logger.info("Monkey cleanup started")
|
||||
self._keep_running = False
|
||||
self._wait_for_exploited_machine_connection()
|
||||
try:
|
||||
if self._is_upgrade_to_64_needed():
|
||||
logger.debug("Cleanup not needed for 32 bit agent on 64 bit system(it didn't run)")
|
||||
return
|
||||
|
||||
if self._master:
|
||||
self._master.cleanup()
|
||||
|
||||
if self._monkey_inbound_tunnel:
|
||||
self._monkey_inbound_tunnel.stop()
|
||||
self._monkey_inbound_tunnel.join()
|
||||
|
||||
if firewall.is_enabled():
|
||||
firewall.remove_firewall_rule()
|
||||
firewall.close()
|
||||
|
||||
InfectionMonkey._self_delete()
|
||||
|
||||
InfectionMonkey._send_log()
|
||||
|
||||
if self._upgrading_to_64:
|
||||
InfectionMonkey.close_tunnel()
|
||||
firewall.close()
|
||||
else:
|
||||
StateTelem(
|
||||
is_done=True, version=get_version()
|
||||
).send() # Signal the server (before closing the tunnel)
|
||||
InfectionMonkey.close_tunnel()
|
||||
firewall.close()
|
||||
self.send_log()
|
||||
self._singleton.unlock()
|
||||
|
||||
InfectionMonkey.self_delete()
|
||||
# TODO: Determine how long between when we
|
||||
# send telemetry and the monkey actually exits
|
||||
InfectionMonkey._close_tunnel()
|
||||
self._singleton.unlock()
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while cleaning up the monkey agent: {e}")
|
||||
InfectionMonkey._self_delete()
|
||||
|
||||
logger.info("Monkey is shutting down")
|
||||
|
||||
def _wait_for_exploited_machine_connection(self):
|
||||
# TODO check for actual exploitation
|
||||
machines_exploited = False
|
||||
# if host was exploited, before continue to closing the tunnel ensure the exploited
|
||||
# host had its chance to
|
||||
# connect to the tunnel
|
||||
if machines_exploited:
|
||||
time_to_sleep = WormConfiguration.keep_tunnel_open_time
|
||||
logger.info(
|
||||
"Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep
|
||||
)
|
||||
time.sleep(time_to_sleep)
|
||||
|
||||
@staticmethod
|
||||
def close_tunnel():
|
||||
def _close_tunnel():
|
||||
tunnel_address = (
|
||||
ControlClient.proxies.get("https", "").replace("https://", "").split(":")[0]
|
||||
)
|
||||
|
@ -322,7 +245,18 @@ class InfectionMonkey(object):
|
|||
tunnel.quit_tunnel(tunnel_address)
|
||||
|
||||
@staticmethod
|
||||
def self_delete():
|
||||
def _send_log():
|
||||
monkey_log_path = get_monkey_log_path()
|
||||
if os.path.exists(monkey_log_path):
|
||||
with open(monkey_log_path, "r") as f:
|
||||
log = f.read()
|
||||
else:
|
||||
log = ""
|
||||
|
||||
ControlClient.send_log(log)
|
||||
|
||||
@staticmethod
|
||||
def _self_delete():
|
||||
status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED
|
||||
T1107Telem(status, get_monkey_dir_path()).send()
|
||||
|
||||
|
@ -351,117 +285,3 @@ class InfectionMonkey(object):
|
|||
status = ScanStatus.SCANNED
|
||||
if status:
|
||||
T1107Telem(status, sys.executable).send()
|
||||
|
||||
def send_log(self):
|
||||
monkey_log_path = get_monkey_log_path()
|
||||
if os.path.exists(monkey_log_path):
|
||||
with open(monkey_log_path, "r") as f:
|
||||
log = f.read()
|
||||
else:
|
||||
log = ""
|
||||
|
||||
ControlClient.send_log(log)
|
||||
|
||||
def try_exploiting(self, machine, exploiter):
|
||||
"""
|
||||
Workflow of exploiting one machine with one exploiter
|
||||
:param machine: Machine monkey tries to exploit
|
||||
:param exploiter: Exploiter to use on that machine
|
||||
:return: True if successfully exploited, False otherwise
|
||||
"""
|
||||
if not exploiter.is_os_supported():
|
||||
logger.info(
|
||||
"Skipping exploiter %s host:%r, os %s is not supported",
|
||||
exploiter.__class__.__name__,
|
||||
machine,
|
||||
machine.os,
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__
|
||||
)
|
||||
|
||||
result = False
|
||||
try:
|
||||
result = exploiter.exploit_host()
|
||||
if result:
|
||||
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
||||
return True
|
||||
else:
|
||||
logger.info(
|
||||
"Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__
|
||||
)
|
||||
except ExploitingVulnerableMachineError as exc:
|
||||
logger.error(
|
||||
"Exception while attacking %s using %s: %s",
|
||||
machine,
|
||||
exploiter.__class__.__name__,
|
||||
exc,
|
||||
)
|
||||
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
||||
return True
|
||||
except FailedExploitationError as e:
|
||||
logger.info(
|
||||
"Failed exploiting %r with exploiter %s, %s",
|
||||
machine,
|
||||
exploiter.__class__.__name__,
|
||||
e,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"Exception while attacking %s using %s: %s",
|
||||
machine,
|
||||
exploiter.__class__.__name__,
|
||||
exc,
|
||||
)
|
||||
finally:
|
||||
exploiter.send_exploit_telemetry(result)
|
||||
return False
|
||||
|
||||
def successfully_exploited(self, machine, exploiter, RUNS_AGENT_ON_SUCCESS=True):
|
||||
"""
|
||||
Workflow of registering successfully exploited machine
|
||||
:param machine: machine that was exploited
|
||||
:param exploiter: exploiter that succeeded
|
||||
"""
|
||||
if RUNS_AGENT_ON_SUCCESS:
|
||||
self._exploited_machines.add(machine)
|
||||
|
||||
logger.info("Successfully propagated to %s using %s", machine, exploiter.__class__.__name__)
|
||||
|
||||
# check if max-exploitation limit is reached
|
||||
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
|
||||
self._keep_running = False
|
||||
|
||||
logger.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
|
||||
|
||||
def set_default_port(self):
|
||||
try:
|
||||
self._default_server_port = self._default_server.split(":")[1]
|
||||
except KeyError:
|
||||
self._default_server_port = ""
|
||||
|
||||
def set_default_server(self):
|
||||
"""
|
||||
Sets the default server for the Monkey to communicate back to.
|
||||
:raises PlannedShutdownException if couldn't find the server.
|
||||
"""
|
||||
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
|
||||
raise PlannedShutdownException(
|
||||
"Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)
|
||||
)
|
||||
self._default_server = WormConfiguration.current_server
|
||||
logger.debug("default server set to: %s" % self._default_server)
|
||||
|
||||
def log_arguments(self):
|
||||
arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()])
|
||||
logger.info(f"Monkey started with arguments: {arg_string}")
|
||||
|
||||
@staticmethod
|
||||
def run_ransomware():
|
||||
try:
|
||||
ransomware_payload = build_ransomware_payload(WormConfiguration.ransomware)
|
||||
ransomware_payload.run_payload()
|
||||
except Exception as ex:
|
||||
logger.error(f"An unexpected error occurred while running the ransomware payload: {ex}")
|
||||
|
|
|
@ -220,17 +220,48 @@ class MockPuppet(IPuppet):
|
|||
self, name: str, host: str, options: Dict, interrupt: threading.Event
|
||||
) -> ExploiterResultData:
|
||||
logger.debug(f"exploit_hosts({name}, {host}, {options})")
|
||||
attempts = [
|
||||
{
|
||||
"result": False,
|
||||
"user": "Administrator",
|
||||
"password": "",
|
||||
"lm_hash": "",
|
||||
"ntlm_hash": "",
|
||||
"ssh_key": host,
|
||||
},
|
||||
{
|
||||
"result": False,
|
||||
"user": "root",
|
||||
"password": "",
|
||||
"lm_hash": "",
|
||||
"ntlm_hash": "",
|
||||
"ssh_key": host,
|
||||
},
|
||||
]
|
||||
info_powershell = {
|
||||
"display_name": "PowerShell",
|
||||
"started": "2021-11-25T15:57:06.307696",
|
||||
"finished": "2021-11-25T15:58:33.788238",
|
||||
"vulnerable_urls": [],
|
||||
"vulnerable_ports": [],
|
||||
"executed_cmds": [
|
||||
{
|
||||
"cmd": "/tmp/monkey m0nk3y -s 10.10.10.10:5000 -d 1 >git s /dev/null 2>&1 &",
|
||||
"powershell": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
info_ssh = {
|
||||
"display_name": "SSH",
|
||||
"started": "2021-11-25T15:57:06.307696",
|
||||
"finished": "2021-11-25T15:58:33.788238",
|
||||
"vulnerable_urls": [],
|
||||
"vulnerable_ports": [22],
|
||||
"executed_cmds": [],
|
||||
}
|
||||
successful_exploiters = {
|
||||
DOT_1: {
|
||||
"PowerShellExploiter": ExploiterResultData(
|
||||
True, {"info": "important success stuff"}, ["attempt 1"]
|
||||
)
|
||||
},
|
||||
DOT_3: {
|
||||
"SSHExploiter": ExploiterResultData(
|
||||
False, {"info": "important failure stuff"}, ["attempt 2"]
|
||||
)
|
||||
},
|
||||
DOT_1: {"PowerShellExploiter": ExploiterResultData(True, info_powershell, attempts)},
|
||||
DOT_3: {"SSHExploiter": ExploiterResultData(False, info_ssh, attempts)},
|
||||
}
|
||||
|
||||
return successful_exploiters[host][name]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
class PlannedShutdownError(Exception):
|
||||
pass
|
|
@ -1,2 +0,0 @@
|
|||
class PlannedShutdownException(Exception):
|
||||
pass
|
|
@ -3,7 +3,7 @@ import signal
|
|||
|
||||
from infection_monkey.i_master import IMaster
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
|
||||
from infection_monkey.utils.exceptions.planned_shutdown_error import PlannedShutdownError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -16,7 +16,7 @@ class StopSignalHandler:
|
|||
self._handle_signal(signum)
|
||||
# Windows signal handlers must return boolean. Only raising this exception for POSIX
|
||||
# signals.
|
||||
raise PlannedShutdownException("Monkey Agent got an interrupt signal")
|
||||
raise PlannedShutdownError("Monkey Agent got an interrupt signal")
|
||||
|
||||
def handle_windows_signals(self, signum):
|
||||
import win32con
|
||||
|
|
Loading…
Reference in New Issue