forked from p15670423/monkey
Merge pull request #1785 from guardicore/1611-interruptable-exploiters
1611 interruptable exploiters
This commit is contained in:
commit
33f2bac275
|
@ -81,7 +81,7 @@ class Configuration(object):
|
|||
# monkey config
|
||||
###########################
|
||||
# sets whether or not the monkey is alive. if false will stop scanning and exploiting
|
||||
alive = True
|
||||
should_stop = False
|
||||
|
||||
# depth of propagation
|
||||
depth = 2
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"inaccessible_subnets": [],
|
||||
"blocked_ips": [],
|
||||
"current_server": "192.0.2.0:5000",
|
||||
"alive": true,
|
||||
"should_stop": false,
|
||||
"collect_system_info": true,
|
||||
"should_use_mimikatz": true,
|
||||
"depth": 2,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import threading
|
||||
from abc import abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
@ -66,12 +67,14 @@ class HostExploiter:
|
|||
telemetry_messenger: ITelemetryMessenger,
|
||||
agent_repository: IAgentRepository,
|
||||
options: Dict,
|
||||
interrupt: threading.Event,
|
||||
):
|
||||
self.host = host
|
||||
self.current_depth = current_depth
|
||||
self.telemetry_messenger = telemetry_messenger
|
||||
self.agent_repository = agent_repository
|
||||
self.options = options
|
||||
self.interrupt = interrupt
|
||||
|
||||
self.pre_exploit()
|
||||
try:
|
||||
|
@ -91,6 +94,15 @@ class HostExploiter:
|
|||
)
|
||||
self.set_start_time()
|
||||
|
||||
def is_interrupted(self):
|
||||
# This method should be refactored to raise an exception to reduce duplication in the
|
||||
# "if is_interrupted: return self.exploitation_results"
|
||||
# Ideally the user should only do "check_for_interrupt()"
|
||||
if self.interrupt.is_set():
|
||||
logger.info("Exploiter has been interrupted")
|
||||
self.exploit_result.error_message = "Exploiter has been interrupted"
|
||||
return self.interrupt.is_set()
|
||||
|
||||
def post_exploit(self):
|
||||
self.set_finish_time()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import threading
|
||||
from typing import Dict, Type
|
||||
|
||||
from infection_monkey.model import VictimHost
|
||||
|
@ -26,10 +27,17 @@ class ExploiterWrapper:
|
|||
self._telemetry_messenger = telemetry_messenger
|
||||
self._agent_repository = agent_repository
|
||||
|
||||
def exploit_host(self, host: VictimHost, current_depth: int, options: Dict):
|
||||
def exploit_host(
|
||||
self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event
|
||||
):
|
||||
exploiter = self._exploit_class()
|
||||
return exploiter.exploit_host(
|
||||
host, current_depth, self._telemetry_messenger, self._agent_repository, options
|
||||
host,
|
||||
current_depth,
|
||||
self._telemetry_messenger,
|
||||
self._agent_repository,
|
||||
options,
|
||||
interrupt,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
|
|
@ -49,6 +49,7 @@ class WmiTools(object):
|
|||
if not domain:
|
||||
domain = host.ip_addr
|
||||
|
||||
# Impacket has a hard-coded timeout of 30 seconds
|
||||
dcom = DCOMConnection(
|
||||
host.ip_addr,
|
||||
username=username,
|
||||
|
|
|
@ -15,6 +15,7 @@ from infection_monkey.utils.brute_force import (
|
|||
get_credential_string,
|
||||
)
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
from infection_monkey.utils.threading import interruptable_iter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,8 +29,15 @@ class WmiExploiter(HostExploiter):
|
|||
def _exploit_host(self) -> ExploiterResultData:
|
||||
|
||||
creds = generate_brute_force_combinations(self.options["credentials"])
|
||||
intp_creds = interruptable_iter(
|
||||
creds,
|
||||
self.interrupt,
|
||||
"WMI exploiter has been interrupted",
|
||||
logging.INFO,
|
||||
)
|
||||
|
||||
for user, password, lm_hash, ntlm_hash in intp_creds:
|
||||
|
||||
for user, password, lm_hash, ntlm_hash in creds:
|
||||
creds_for_log = get_credential_string([user, password, lm_hash, ntlm_hash])
|
||||
logger.debug(f"Attempting to connect to {self.host} using WMI with {creds_for_log}")
|
||||
|
||||
|
@ -60,21 +68,11 @@ class WmiExploiter(HostExploiter):
|
|||
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
|
||||
self.exploit_result.exploitation_success = True
|
||||
|
||||
# query process list and check if monkey already running on victim
|
||||
process_list = WmiTools.list_object(
|
||||
wmi_connection,
|
||||
"Win32_Process",
|
||||
fields=("Caption",),
|
||||
where=f"Name='{ntpath.split(self.options['dropper_target_path_win_64'])[-1]}'",
|
||||
)
|
||||
if process_list:
|
||||
wmi_connection.close()
|
||||
|
||||
logger.debug("Skipping %r - already infected", self.host)
|
||||
return self.exploit_result
|
||||
|
||||
downloaded_agent = self.agent_repository.get_agent_binary(self.host.os["type"])
|
||||
|
||||
if self.is_interrupted():
|
||||
return self.exploit_result
|
||||
|
||||
remote_full_path = SmbTools.copy_file(
|
||||
self.host,
|
||||
downloaded_agent,
|
||||
|
@ -120,7 +118,7 @@ class WmiExploiter(HostExploiter):
|
|||
self.add_vuln_port(port="unknown")
|
||||
self.exploit_result.propagation_success = True
|
||||
else:
|
||||
logger.debug(
|
||||
error_message = (
|
||||
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
|
||||
"cmdline=%r)",
|
||||
remote_full_path,
|
||||
|
@ -129,6 +127,8 @@ class WmiExploiter(HostExploiter):
|
|||
result.ReturnValue,
|
||||
cmdline,
|
||||
)
|
||||
logger.debug(error_message)
|
||||
self.exploit_result.error_message = error_message
|
||||
|
||||
result.RemRelease()
|
||||
wmi_connection.close()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from infection_monkey.i_puppet import PluginType, UnknownPluginError
|
||||
|
||||
|
@ -27,7 +28,7 @@ class PluginRegistry:
|
|||
|
||||
logger.debug(f"Plugin '{plugin_name}' loaded")
|
||||
|
||||
def get_plugin(self, plugin_name: str, plugin_type: PluginType) -> object:
|
||||
def get_plugin(self, plugin_name: str, plugin_type: PluginType) -> Any:
|
||||
try:
|
||||
plugin = self._registry[plugin_type][plugin_name]
|
||||
except KeyError:
|
||||
|
|
|
@ -66,7 +66,7 @@ class Puppet(IPuppet):
|
|||
interrupt: threading.Event,
|
||||
) -> ExploiterResultData:
|
||||
exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER)
|
||||
return exploiter.exploit_host(host, current_depth, options)
|
||||
return exploiter.exploit_host(host, current_depth, options, interrupt)
|
||||
|
||||
def run_payload(self, name: str, options: Dict, interrupt: threading.Event):
|
||||
payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD)
|
||||
|
|
|
@ -8,6 +8,6 @@ class Config(EmbeddedDocument):
|
|||
See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist
|
||||
"""
|
||||
|
||||
alive = BooleanField()
|
||||
should_stop = BooleanField()
|
||||
meta = {"strict": False}
|
||||
pass
|
||||
|
|
|
@ -19,11 +19,10 @@ INTERNAL = {
|
|||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alive": {
|
||||
"title": "Alive",
|
||||
"should_stop": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Is the monkey alive",
|
||||
"default": False,
|
||||
"description": "Was stop command issued for this monkey",
|
||||
},
|
||||
"aws_keys": {
|
||||
"type": "object",
|
||||
|
|
|
@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def set_stop_all(time: float):
|
||||
for monkey in Monkey.objects():
|
||||
monkey.config.alive = False
|
||||
monkey.config.should_stop = True
|
||||
monkey.save()
|
||||
agent_controls = AgentControls.objects.first()
|
||||
agent_controls.last_stop_all = time
|
||||
|
@ -25,11 +25,16 @@ def set_stop_all(time: float):
|
|||
|
||||
def should_agent_die(guid: int) -> bool:
|
||||
monkey = Monkey.objects(guid=str(guid)).first()
|
||||
return _is_monkey_marked_dead(monkey) or _is_monkey_killed_manually(monkey)
|
||||
return _should_agent_stop(monkey) or _is_monkey_killed_manually(monkey)
|
||||
|
||||
|
||||
def _is_monkey_marked_dead(monkey: Monkey) -> bool:
|
||||
return not monkey.config.alive
|
||||
def _should_agent_stop(monkey: Monkey) -> bool:
|
||||
if monkey.config.should_stop:
|
||||
# Only stop the agent once, to allow further runs on that machine
|
||||
monkey.config.should_stop = False
|
||||
monkey.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_monkey_killed_manually(monkey: Monkey) -> bool:
|
||||
|
|
|
@ -249,7 +249,7 @@ class NodeService:
|
|||
|
||||
# Cancel the force kill once monkey died
|
||||
if is_dead:
|
||||
props_to_set["config.alive"] = True
|
||||
props_to_set["config.should_stop"] = False
|
||||
|
||||
mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False)
|
||||
|
||||
|
|
|
@ -88,10 +88,11 @@ class MapPageComponent extends AuthComponent {
|
|||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({kill_time: Date.now()})
|
||||
// Python uses floating point seconds, Date.now uses milliseconds, so convert
|
||||
body: JSON.stringify({kill_time: Date.now() / 1000.0})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {this.setState({killPressed: true}); console.log(res)});
|
||||
.then(res => {this.setState({killPressed: true})});
|
||||
};
|
||||
|
||||
renderKillDialogModal = () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import threading
|
||||
from io import BytesIO
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
@ -43,6 +44,7 @@ def powershell_arguments():
|
|||
"current_depth": 2,
|
||||
"telemetry_messenger": MagicMock(),
|
||||
"agent_repository": mock_agent_repository,
|
||||
"interrupt": threading.Event(),
|
||||
}
|
||||
return arguments
|
||||
|
||||
|
|
|
@ -10,21 +10,21 @@ from monkey_island.cc.services.infection_lifecycle import should_agent_die
|
|||
@pytest.mark.usefixtures("uses_database")
|
||||
def test_should_agent_die_by_config(monkeypatch):
|
||||
monkey = Monkey(guid=str(uuid.uuid4()))
|
||||
monkey.config = Config(alive=False)
|
||||
monkey.config = Config(should_stop=True)
|
||||
monkey.save()
|
||||
assert should_agent_die(monkey.guid)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"monkey_island.cc.services.infection_lifecycle._is_monkey_killed_manually", lambda _: False
|
||||
)
|
||||
monkey.config.alive = True
|
||||
monkey.config.should_stop = True
|
||||
monkey.save()
|
||||
assert not should_agent_die(monkey.guid)
|
||||
|
||||
|
||||
def create_monkey(launch_time):
|
||||
monkey = Monkey(guid=str(uuid.uuid4()))
|
||||
monkey.config = Config(alive=True)
|
||||
monkey.config = Config(should_stop=False)
|
||||
monkey.launch_time = launch_time
|
||||
monkey.save()
|
||||
return monkey
|
||||
|
|
Loading…
Reference in New Issue