From 2bbd5d48241f26c791b37a0a46dc49624470e215 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 3 Oct 2017 15:47:07 +0300 Subject: [PATCH 1/6] Fix SambaCry .close() bug --- chaos_monkey/exploit/sambacry.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/chaos_monkey/exploit/sambacry.py b/chaos_monkey/exploit/sambacry.py index 4bebbd6a1..ab27728ff 100644 --- a/chaos_monkey/exploit/sambacry.py +++ b/chaos_monkey/exploit/sambacry.py @@ -100,7 +100,6 @@ class SambaCryExploiter(HostExploiter): smb_client = self.connect_to_server(host.ip_addr, creds) self.upload_module(smb_client, host, share, depth) self.trigger_module(smb_client, share) - smb_client.close() except (impacket.smbconnection.SessionError, SessionError): LOG.debug( "Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds))) @@ -125,7 +124,6 @@ class SambaCryExploiter(HostExploiter): # Ignore exception to try and delete as much as possible pass smb_client.disconnectTree(tree_id) - smb_client.close() def get_trigger_result(self, ip, share, creds): """ @@ -147,7 +145,6 @@ class SambaCryExploiter(HostExploiter): pass smb_client.disconnectTree(tree_id) - smb_client.close() return file_content def get_writable_shares_creds_dict(self, ip): @@ -159,6 +156,8 @@ class SambaCryExploiter(HostExploiter): writable_shares_creds_dict = {} credentials_list = self.get_credentials_list() + LOG.debug("SambaCry credential list: %s" % str(credentials_list)) + for credentials in credentials_list: try: smb_client = self.connect_to_server(ip, credentials) @@ -169,7 +168,6 @@ class SambaCryExploiter(HostExploiter): if self.is_share_writable(smb_client, share): writable_shares_creds_dict[share] = credentials - smb_client.close() except (impacket.smbconnection.SessionError, SessionError): # If failed using some credentials, try others. pass From 65f5dbeaaf9d6528e91406dc41ea0b1879fe83e9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 3 Oct 2017 15:47:50 +0300 Subject: [PATCH 2/6] Sleep only *between* life cycles --- chaos_monkey/config.py | 2 ++ chaos_monkey/example.conf | 1 + chaos_monkey/monkey.py | 36 ++++++++++++++++------------- monkey_island/cc/services/config.py | 6 +++++ 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index 6b49d3bb3..c1cd618ef 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -173,6 +173,8 @@ class Configuration(object): # addresses of internet servers to ping and check if the monkey has internet acccess. internet_services = ["monkey.guardicore.com", "www.google.com"] + keep_tunnel_open_time = 60 + ########################### # scanners config ########################### diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 285bffd11..b738cff75 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -6,6 +6,7 @@ "monkey.guardicore.com", "www.google.com" ], + "keep_tunnel_open_time": 60, "range_class": "RelativeRange", "range_fixed": [ "" diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index daabad0ee..426d121eb 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -1,17 +1,18 @@ -import sys -import os -import time -import logging -import tunnel import argparse +import logging +import os import subprocess -from system_singleton import SystemSingleton -from network.firewall import app as firewall -from control import ControlClient +import sys +import time + +import tunnel from config import WormConfiguration -from network.network_scanner import NetworkScanner +from control import ControlClient from model import DELAY_DELETE_CMD +from network.firewall import app as firewall +from network.network_scanner import NetworkScanner from system_info import SystemInfoCollector +from system_singleton import SystemSingleton __author__ = 'itamar' @@ -101,7 +102,7 @@ class ChaosMonkey(object): else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) - for _ in xrange(WormConfiguration.max_iterations): + for iteration_index in xrange(WormConfiguration.max_iterations): ControlClient.keepalive() ControlClient.load_control_config() @@ -146,7 +147,6 @@ class ChaosMonkey(object): LOG.debug("Skipping %r - exploitation failed before", machine) continue - if monkey_tunnel: monkey_tunnel.set_tunnel_for_host(machine) if self._default_server: @@ -196,8 +196,10 @@ class ChaosMonkey(object): else: self._fail_exploitation_machines.add(machine) - if not is_empty: - time.sleep(WormConfiguration.timeout_between_iterations) + 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) if self._keep_running and WormConfiguration.alive: LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations) @@ -206,8 +208,10 @@ class ChaosMonkey(object): # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to # connect to the tunnel - if last_exploit_time and (time.time() - last_exploit_time < 60): - time.sleep(time.time() - last_exploit_time) + 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) if monkey_tunnel: monkey_tunnel.stop() @@ -242,7 +246,7 @@ class ChaosMonkey(object): close_fds=True, startupinfo=startupinfo) else: os.remove(sys.executable) - except Exception, exc: + except Exception as exc: LOG.error("Exception in self delete: %s", exc) LOG.info("Monkey is shutting down") diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 5e4d5abe0..6807d5d86 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -277,6 +277,12 @@ SCHEMA = { "type": "string", "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", "description": "The name of the mutex used to determine whether the monkey is already running" + }, + "keep_tunnel_open_time": { + "title": "Keep tunnel open time", + "type": "integer", + "default": 60, + "description": "Time to keep tunnel open before going down since last exploit (in seconds)" } } }, From 14eec1ba99fd7f6b9ffbb6389695349b4620a256 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 3 Oct 2017 16:18:34 +0300 Subject: [PATCH 3/6] Log stack trace of exceptions thrown from exploit --- chaos_monkey/monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 426d121eb..a71993f7c 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -172,8 +172,8 @@ class ChaosMonkey(object): 'exploiter': exploiter.__class__.__name__}) except Exception as exc: - LOG.error("Exception while attacking %s using %s: %s", - machine, exploiter.__class__.__name__, exc) + LOG.exception("Exception while attacking %s using %s: %s", + machine, exploiter.__class__.__name__, exc) ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, 'exploiter': exploiter.__class__.__name__}) continue From 0c971da15c40b56ea642a71b6e343c6e5f1b8fb9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 3 Oct 2017 17:08:23 +0300 Subject: [PATCH 4/6] linux's implementation of local_ips returns array of strs instead of unicodes This fixes SambaCry Linux->Linux exploit among other things --- chaos_monkey/network/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py index 605799ce3..e438f37b3 100644 --- a/chaos_monkey/network/info.py +++ b/chaos_monkey/network/info.py @@ -48,7 +48,7 @@ else: def local_ips(): ipv4_nets = get_host_subnets() - valid_ips = [network['addr'] for network in ipv4_nets] + valid_ips = [network['addr'].encode('utf-8').strip() for network in ipv4_nets] return valid_ips From dc27467cd74993a85a93ac3f706bc253cbbd5b75 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 8 Oct 2017 16:13:42 +0300 Subject: [PATCH 5/6] Updated compilation instructions --- chaos_monkey/readme.txt | 73 +++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/chaos_monkey/readme.txt b/chaos_monkey/readme.txt index 4f84ecce1..4a3a965dd 100644 --- a/chaos_monkey/readme.txt +++ b/chaos_monkey/readme.txt @@ -1,35 +1,58 @@ -How to create a monkey build environment: +How to build a monkey binary from scratch. + +The monkey is composed of three seperate parts. +* The Infection Monkey itself - PyInstaller compressed python archives +* Sambacry binaries - Two linux binaries, 32/64 bit. +* Mimikatz binaries - Two windows binaries, 32/64 bit. + +--- Windows --- -Windows: 1. Install python 2.7. Preferably you should use ActiveState Python which includes pywin32 built in. - You must use an up to date version, atleast version 2.7.10 - http://www.activestate.com/activepython/downloads - https://www.python.org/downloads/release/python-2712/ -2. install pywin32-219.win32-py2.7.exe at least - http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/ + You must use an up to date version, at least version 2.7.10 + http://www.activestate.com/activepython/downloads + https://www.python.org/downloads/release/python-2712/ + If not using ActiveState, install pywin32, minimum build 219 + http://sourceforge.net/projects/pywin32/files/pywin32 3. a. install VCForPython27.msi - http://www.microsoft.com/en-us/download/details.aspx?id=44266 + https://aka.ms/vcpython27 b. if not installed, install Microsoft Visual C++ 2010 SP1 Redistributable Package 32bit: http://www.microsoft.com/en-us/download/details.aspx?id=8328 64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523 -4. Download & Run get-pip.py - https://bootstrap.pypa.io/get-pip.py -5. Run: - Install the python packages listed in requirements.txt. Using pip install -r requirements.txt -7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe: - http://upx.sourceforge.net/download/upx391w.zip -8. Run [source-path]\monkey\chaos_monkey\build_windows.bat to build, output is in dist\monkey.exe +4. Download the dependent python packages using + pip install -r requirements.txt +5. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe: + https://github.com/upx/upx/releases/download/v3.94/upx394w.zip +6. To build the final exe: + 1 cd [code location]/chaos_monkey + build_windows.bat + output is in dist\monkey.exe + +--- Linux --- + +Tested on Ubuntu 16.04 and 17.04. -Linux (Tested on Ubuntu 12.04): 1. Run: - sudo apt-get update - sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 - Install the python packages listed in requirements.txt. - Using pip install -r requirements.txt - sudo apt-get install winbind -2. Put source code in /home/user/Code/monkey/chaos_monkey + sudo apt-get update + sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 + Install the python packages listed in requirements.txt. + Using pip install -r requirements.txt + sudo apt-get install winbind dnet-common +2. Put source code in Code/monkey/chaos_monkey 3. To build, run in terminal: - cd /home/user/Code/monkey/chaos_monkey - chmod +x build_linux.sh - ./build_linux.sh + cd [code location]/chaos_monkey + chmod +x build_linux.sh + ./build_linux.sh output is in dist/monkey + +-- Sambacry -- + +Sambacry requires two standalone binaries to execute remotely. +Compiling them requires gcc +cd [code location]/chaos_monkey/monkey_utils/sambacry_monkey_runner +./build.sh + +-- Mimikatz -- + +Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from +https://github.com/guardicore/mimikatz/releases/tag/1.0.0 +Download both 32 and 64 bit DLLs and place them under [code location]\chaos_monkey\bin From bf5fb10838fb44fef3e76e876fb04db543df8b59 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 8 Oct 2017 19:23:34 +0300 Subject: [PATCH 6/6] Fix CR --- chaos_monkey/monkey.py | 7 ++----- chaos_monkey/network/info.py | 5 +++-- monkey_island/cc/services/config.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index a71993f7c..17fc17bdd 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -81,8 +81,6 @@ class ChaosMonkey(object): if monkey_tunnel: monkey_tunnel.start() - last_exploit_time = None - ControlClient.send_telemetry("state", {'done': False}) self._default_server = WormConfiguration.current_server @@ -180,7 +178,6 @@ class ChaosMonkey(object): if successful_exploiter: self._exploited_machines.add(machine) - last_exploit_time = time.time() ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__, 'exploiter': successful_exploiter.__class__.__name__}) @@ -208,8 +205,8 @@ class ChaosMonkey(object): # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to # connect to the tunnel - 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) + if len(self._exploited_machines) > 0: + time_to_sleep = WormConfiguration.keep_tunnel_open_time LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep) time.sleep(time_to_sleep) diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py index e438f37b3..0c841dc9f 100644 --- a/chaos_monkey/network/info.py +++ b/chaos_monkey/network/info.py @@ -29,6 +29,8 @@ def get_host_subnets(): for network in ipv4_nets: if 'broadcast' in network: network.pop('broadcast') + for attr in network: + network[attr] = network[attr].encode('utf-8').strip() return ipv4_nets @@ -47,8 +49,7 @@ else: def local_ips(): - ipv4_nets = get_host_subnets() - valid_ips = [network['addr'].encode('utf-8').strip() for network in ipv4_nets] + valid_ips = [network['addr'] for network in get_host_subnets()] return valid_ips diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 6807d5d86..dc9ce6a9e 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -282,7 +282,7 @@ SCHEMA = { "title": "Keep tunnel open time", "type": "integer", "default": 60, - "description": "Time to keep tunnel open before going down since last exploit (in seconds)" + "description": "Time to keep tunnel open before going down after last exploit (in seconds)" } } },