From 55d7eba2d8c1e47f21354d70b39aaf3cfb4a57dd Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Thu, 21 Nov 2019 15:20:53 +0200
Subject: [PATCH 1/8] Separate collecting the classes from instancing them.
 Required for exploiter plugin

---
 .../infection_monkey/utils/plugins/plugin.py  | 28 +++++++++++++++----
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py
index 21d3134bf..872ac33db 100644
--- a/monkey/infection_monkey/utils/plugins/plugin.py
+++ b/monkey/infection_monkey/utils/plugins/plugin.py
@@ -4,7 +4,7 @@ import logging
 from abc import ABCMeta, abstractmethod
 from os.path import dirname, basename, isfile, join
 import glob
-from typing import Sequence, TypeVar, Type
+from typing import Sequence, TypeVar, Type, Callable
 
 LOG = logging.getLogger(__name__)
 
@@ -25,11 +25,11 @@ class Plugin(metaclass=ABCMeta):
         raise NotImplementedError()
 
     @classmethod
-    def get_instances(cls) -> Sequence[Type[Plugin_type]]:
+    def get_classes(cls) -> Sequence[Callable]:
         """
-        Returns the type objects from base_package_spec.
+        Returns the class objects from base_package_spec
         base_package name and file must refer to the same package otherwise bad results
-        :return: A list of parent_class objects.
+        :return: A list of parent_class classes.
         """
         objects = []
         candidate_files = _get_candidate_files(cls.base_package_file())
@@ -47,13 +47,29 @@ class Plugin(metaclass=ABCMeta):
                 LOG.debug("Checking if should run object {}".format(class_object.__name__))
                 try:
                     if class_object.should_run(class_object.__name__):
-                        instance = class_object()
-                        objects.append(instance)
+                        objects.append(class_object)
                         LOG.debug("Added {} to list".format(class_object.__name__))
                 except Exception as e:
                     LOG.warning("Exception {} when checking if {} should run".format(str(e), class_object.__name__))
         return objects
 
+    @classmethod
+    def get_instances(cls) -> Sequence[Type[Plugin_type]]:
+        """
+        Returns the type objects from base_package_spec.
+        base_package name and file must refer to the same package otherwise bad results
+        :return: A list of parent_class objects.
+        """
+        class_objects = cls.get_classes()
+        instances = []
+        for class_object in class_objects:
+            try:
+                instance = class_object()
+                instances.append(instance)
+            except Exception as e:
+                LOG.warning("Exception {} when initializing {}".format(str(e), class_object.__name__))
+        return instances
+
     @staticmethod
     @abstractmethod
     def base_package_file():

From 0f8e8925b3599195a45d53b548c1613c3508b43e Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Thu, 21 Nov 2019 19:06:20 +0200
Subject: [PATCH 2/8] Basic tests for plugins

---
 .../utils/plugins/pluginTests/BadImport.py    |  5 +++
 .../utils/plugins/pluginTests/BadInit.py      |  7 +++++
 .../plugins/pluginTests/PluginTestClass.py    | 22 +++++++++++++
 .../plugins/pluginTests/PluginWorking.py      |  5 +++
 .../utils/plugins/pluginTests/__init__.py     |  0
 .../utils/plugins/plugin_test.py              | 31 +++++++++++++++++++
 6 files changed, 70 insertions(+)
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/__init__.py
 create mode 100644 monkey/infection_monkey/utils/plugins/plugin_test.py

diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py
new file mode 100644
index 000000000..14508aec1
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py
@@ -0,0 +1,5 @@
+from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
+
+
+class SomeDummyPlugin:
+    pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
new file mode 100644
index 000000000..b7909b33a
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
@@ -0,0 +1,7 @@
+from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
+
+
+class badPluginInit(TestPlugin):
+
+    def __init__(self):
+        raise Exception("TestException")
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
new file mode 100644
index 000000000..a3785e875
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py
@@ -0,0 +1,22 @@
+from infection_monkey.utils.plugins.plugin import Plugin
+import infection_monkey.utils.plugins.pluginTests
+
+
+class TestPlugin(Plugin):
+    classes_to_load = []
+
+    @staticmethod
+    def should_run(class_name):
+        """
+        Decides if post breach action is enabled in config
+        :return: True if it needs to be ran, false otherwise
+        """
+        return class_name in TestPlugin.classes_to_load
+
+    @staticmethod
+    def base_package_file():
+        return infection_monkey.utils.plugins.pluginTests.__file__
+
+    @staticmethod
+    def base_package_name():
+        return infection_monkey.utils.plugins.pluginTests.__package__
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
new file mode 100644
index 000000000..d762a1f82
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
@@ -0,0 +1,5 @@
+from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
+
+
+class pluginWorking(TestPlugin):
+    pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py
new file mode 100644
index 000000000..67077a8ad
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/plugin_test.py
@@ -0,0 +1,31 @@
+from unittest import TestCase
+
+from infection_monkey.utils.plugins.pluginTests.PluginWorking import pluginWorking
+from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin
+from infection_monkey.utils.plugins.pluginTests.BadInit import badPluginInit
+from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
+
+
+class PluginTester(TestCase):
+
+    def setUp(self):
+        pass
+
+    def test_bad_init(self):
+        TestPlugin.classes_to_load = [badPluginInit.__name__]
+        to_init = TestPlugin.get_classes()
+        self.assertEqual(len(to_init), 1)
+        objects = TestPlugin.get_instances()
+        self.assertEqual(len(objects), 0)
+
+    def test_bad_import(self):
+        TestPlugin.classes_to_load = [SomeDummyPlugin.__name__]
+        to_init = TestPlugin.get_classes()
+        self.assertEqual(len(to_init), 0)
+
+    def test_flow(self):
+        TestPlugin.classes_to_load = [pluginWorking.__name__]
+        to_init = TestPlugin.get_classes()
+        self.assertEqual(len(to_init), 1)
+        objects = TestPlugin.get_instances()
+        self.assertEqual(len(objects), 1)

From 43d4c36507ddc5e63175722088c1d26e86aae52d Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Thu, 21 Nov 2019 15:21:41 +0200
Subject: [PATCH 3/8] Refactor exploiters into plugins. Change configuration to
 support it and remove unneeded complex importing. Changed main monkey code to
 support exploiter plugins.

---
 monkey/infection_monkey/config.py             | 15 +--
 .../infection_monkey/exploit/HostExploiter.py | 98 +++++++++++++++++++
 monkey/infection_monkey/exploit/__init__.py   | 92 -----------------
 monkey/infection_monkey/exploit/mssqlexec.py  |  2 +-
 monkey/infection_monkey/exploit/sambacry.py   |  2 +-
 monkey/infection_monkey/exploit/shellshock.py |  2 +-
 monkey/infection_monkey/exploit/smbexec.py    |  2 +-
 monkey/infection_monkey/exploit/sshexec.py    |  2 +-
 monkey/infection_monkey/exploit/vsftpd.py     |  2 +-
 monkey/infection_monkey/exploit/web_rce.py    |  2 +-
 monkey/infection_monkey/exploit/weblogic.py   |  2 +-
 .../infection_monkey/exploit/win_ms08_067.py  |  2 +-
 monkey/infection_monkey/exploit/wmiexec.py    |  2 +-
 monkey/infection_monkey/monkey.py             |  8 +-
 14 files changed, 116 insertions(+), 117 deletions(-)
 create mode 100644 monkey/infection_monkey/exploit/HostExploiter.py

diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 5e52022cd..19682f2b4 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -20,10 +20,6 @@ HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
 
 class Configuration(object):
     def from_kv(self, formatted_data):
-        # now we won't work at <2.7 for sure
-        network_import = importlib.import_module('infection_monkey.network')
-        exploit_import = importlib.import_module('infection_monkey.exploit')
-
         unknown_items = []
         for key, value in list(formatted_data.items()):
             if key.startswith('_'):
@@ -32,15 +28,10 @@ class Configuration(object):
                 continue
             if self._depth_from_commandline and key == "depth":
                 continue
-            # handle in cases
-            elif key == 'exploiter_classes':
-                class_objects = [getattr(exploit_import, val) for val in value]
-                setattr(self, key, class_objects)
+            if hasattr(self, key):
+                setattr(self, key, value)
             else:
-                if hasattr(self, key):
-                    setattr(self, key, value)
-                else:
-                    unknown_items.append(key)
+                unknown_items.append(key)
         return unknown_items
 
     def from_json(self, json_data):
diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py
new file mode 100644
index 000000000..50f4167d8
--- /dev/null
+++ b/monkey/infection_monkey/exploit/HostExploiter.py
@@ -0,0 +1,98 @@
+from abc import abstractmethod
+
+from infection_monkey.config import WormConfiguration
+from common.utils.exploit_enum import ExploitType
+from datetime import datetime
+
+from infection_monkey.utils.plugins.plugin import Plugin
+import infection_monkey.exploit
+
+__author__ = 'itamar'
+
+
+class HostExploiter(Plugin):
+    @staticmethod
+    def should_run(class_name):
+        """
+        Decides if post breach action is enabled in config
+        :return: True if it needs to be ran, false otherwise
+        """
+        return class_name in WormConfiguration.exploiter_classes
+
+    @staticmethod
+    def base_package_file():
+        return infection_monkey.exploit.__file__
+
+    @staticmethod
+    def base_package_name():
+        return infection_monkey.exploit.__package__
+
+    _TARGET_OS_TYPE = []
+
+    # Usual values are 'vulnerability' or 'brute_force'
+    EXPLOIT_TYPE = ExploitType.VULNERABILITY
+
+    @property
+    @abstractmethod
+    def _EXPLOITED_SERVICE(self):
+        pass
+
+    def __init__(self, host):
+        self._config = WormConfiguration
+        self.exploit_info = {'display_name': self._EXPLOITED_SERVICE,
+                             'started': '',
+                             'finished': '',
+                             'vulnerable_urls': [],
+                             'vulnerable_ports': [],
+                             'executed_cmds': []}
+        self.exploit_attempts = []
+        self.host = host
+
+    def set_start_time(self):
+        self.exploit_info['started'] = datetime.now().isoformat()
+
+    def set_finish_time(self):
+        self.exploit_info['finished'] = datetime.now().isoformat()
+
+    def is_os_supported(self):
+        return self.host.os.get('type') in self._TARGET_OS_TYPE
+
+    def send_exploit_telemetry(self, result):
+        from infection_monkey.telemetry.exploit_telem import ExploitTelem
+        ExploitTelem(self, result).send()
+
+    def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''):
+        self.exploit_attempts.append({'result': result, 'user': user, 'password': password,
+                                      'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key})
+
+    def exploit_host(self):
+        self.pre_exploit()
+        try:
+            result = self._exploit_host()
+        finally:
+            self.post_exploit()
+        return result
+
+    def pre_exploit(self):
+        self.set_start_time()
+
+    def post_exploit(self):
+        self.set_finish_time()
+
+    @abstractmethod
+    def _exploit_host(self):
+        raise NotImplementedError()
+
+    def add_vuln_url(self, url):
+        self.exploit_info['vulnerable_urls'].append(url)
+
+    def add_vuln_port(self, port):
+        self.exploit_info['vulnerable_ports'].append(port)
+
+    def add_executed_cmd(self, cmd):
+        """
+        Appends command to exploiter's info.
+        :param cmd: String of executed command. e.g. 'echo Example'
+        """
+        powershell = True if "powershell" in cmd.lower() else False
+        self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py
index 306350437..e69de29bb 100644
--- a/monkey/infection_monkey/exploit/__init__.py
+++ b/monkey/infection_monkey/exploit/__init__.py
@@ -1,92 +0,0 @@
-from abc import ABCMeta, abstractmethod, abstractproperty
-import infection_monkey.config
-from common.utils.exploit_enum import ExploitType
-from datetime import datetime
-
-__author__ = 'itamar'
-
-
-class HostExploiter(object, metaclass=ABCMeta):
-    _TARGET_OS_TYPE = []
-
-    # Usual values are 'vulnerability' or 'brute_force'
-    EXPLOIT_TYPE = ExploitType.VULNERABILITY
-
-    @property
-    @abstractmethod
-    def _EXPLOITED_SERVICE(self):
-        pass
-
-    def __init__(self, host):
-        self._config = infection_monkey.config.WormConfiguration
-        self.exploit_info = {'display_name': self._EXPLOITED_SERVICE,
-                             'started': '',
-                             'finished': '',
-                             'vulnerable_urls': [],
-                             'vulnerable_ports': [],
-                             'executed_cmds': []}
-        self.exploit_attempts = []
-        self.host = host
-
-    def set_start_time(self):
-        self.exploit_info['started'] = datetime.now().isoformat()
-
-    def set_finish_time(self):
-        self.exploit_info['finished'] = datetime.now().isoformat()
-
-    def is_os_supported(self):
-        return self.host.os.get('type') in self._TARGET_OS_TYPE
-
-    def send_exploit_telemetry(self, result):
-        from infection_monkey.telemetry.exploit_telem import ExploitTelem
-        ExploitTelem(self, result).send()
-
-    def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''):
-        self.exploit_attempts.append({'result': result, 'user': user, 'password': password,
-                                      'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key})
-
-    def exploit_host(self):
-        self.pre_exploit()
-        try:
-            result = self._exploit_host()
-        finally:
-            self.post_exploit()
-        return result
-
-    def pre_exploit(self):
-        self.set_start_time()
-
-    def post_exploit(self):
-        self.set_finish_time()
-
-    @abstractmethod
-    def _exploit_host(self):
-        raise NotImplementedError()
-
-    def add_vuln_url(self, url):
-        self.exploit_info['vulnerable_urls'].append(url)
-
-    def add_vuln_port(self, port):
-        self.exploit_info['vulnerable_ports'].append(port)
-
-    def add_executed_cmd(self, cmd):
-        """
-        Appends command to exploiter's info.
-        :param cmd: String of executed command. e.g. 'echo Example'
-        """
-        powershell = True if "powershell" in cmd.lower() else False
-        self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
-
-
-from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
-from infection_monkey.exploit.wmiexec import WmiExploiter
-from infection_monkey.exploit.smbexec import SmbExploiter
-from infection_monkey.exploit.sshexec import SSHExploiter
-from infection_monkey.exploit.shellshock import ShellShockExploiter
-from infection_monkey.exploit.sambacry import SambaCryExploiter
-from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter
-from infection_monkey.exploit.struts2 import Struts2Exploiter
-from infection_monkey.exploit.weblogic import WebLogicExploiter
-from infection_monkey.exploit.hadoop import HadoopExploiter
-from infection_monkey.exploit.mssqlexec import MSSQLExploiter
-from infection_monkey.exploit.vsftpd import VSFTPDExploiter
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index 16e69dff0..9d2aff5b0 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -6,7 +6,7 @@ from time import sleep
 import pymssql
 
 from common.utils.exploit_enum import ExploitType
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
 from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
 from infection_monkey.model import DROPPER_ARG
diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py
index 4e1d71fd9..4820d0f05 100644
--- a/monkey/infection_monkey/exploit/sambacry.py
+++ b/monkey/infection_monkey/exploit/sambacry.py
@@ -16,7 +16,7 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_
 from impacket.smbconnection import SMBConnection
 
 import infection_monkey.monkeyfs as monkeyfs
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.model import DROPPER_ARG
 from infection_monkey.network.smbfinger import SMB_SERVICE
 from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py
index f65974dbd..203d39b9b 100644
--- a/monkey/infection_monkey/exploit/shellshock.py
+++ b/monkey/infection_monkey/exploit/shellshock.py
@@ -7,7 +7,7 @@ from random import choice
 import requests
 
 from common.utils.attack_utils import ScanStatus
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
 from infection_monkey.model import DROPPER_ARG
 from infection_monkey.exploit.shellshock_resources import CGI_FILES
diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py
index fef8dad05..f53e1ac38 100644
--- a/monkey/infection_monkey/exploit/smbexec.py
+++ b/monkey/infection_monkey/exploit/smbexec.py
@@ -3,7 +3,7 @@ from logging import getLogger
 from impacket.dcerpc.v5 import transport, scmr
 from impacket.smbconnection import SMB_DIALECT
 
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
 from infection_monkey.exploit.tools.smb_tools import SmbTools
 from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py
index a7e9571b5..4fbc484eb 100644
--- a/monkey/infection_monkey/exploit/sshexec.py
+++ b/monkey/infection_monkey/exploit/sshexec.py
@@ -5,7 +5,7 @@ import time
 import paramiko
 
 import infection_monkey.monkeyfs as monkeyfs
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
 from infection_monkey.model import MONKEY_ARG
 from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py
index d4116c96c..82954b99b 100644
--- a/monkey/infection_monkey/exploit/vsftpd.py
+++ b/monkey/infection_monkey/exploit/vsftpd.py
@@ -8,7 +8,7 @@ import socket
 import time
 
 from common.utils.attack_utils import ScanStatus
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth
 from infection_monkey.exploit.tools.http_tools import HTTPTools
 from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py
index b894acf43..bef428f4d 100644
--- a/monkey/infection_monkey/exploit/web_rce.py
+++ b/monkey/infection_monkey/exploit/web_rce.py
@@ -3,7 +3,7 @@ import re
 from posixpath import join
 from abc import abstractmethod
 
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
 from infection_monkey.exploit.tools.http_tools import HTTPTools
 from infection_monkey.model import CHECK_COMMAND, ID_STRING, GET_ARCH_LINUX, GET_ARCH_WINDOWS, BITSADMIN_CMDLINE_HTTP, \
diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py
index 08b642942..7ea80b372 100644
--- a/monkey/infection_monkey/exploit/weblogic.py
+++ b/monkey/infection_monkey/exploit/weblogic.py
@@ -7,7 +7,7 @@ from requests import post, exceptions
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
 from infection_monkey.exploit.web_rce import WebRCE
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.network.tools import get_interface_to_target
 from infection_monkey.network.info import get_free_tcp_port
 from http.server import BaseHTTPRequestHandler, HTTPServer
diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py
index b84cf3391..f296091d6 100644
--- a/monkey/infection_monkey/exploit/win_ms08_067.py
+++ b/monkey/infection_monkey/exploit/win_ms08_067.py
@@ -19,7 +19,7 @@ from infection_monkey.exploit.tools.smb_tools import SmbTools
 from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
 from infection_monkey.network.smbfinger import SMBFinger
 from infection_monkey.network.tools import check_tcp_port
-from . import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 
 LOG = getLogger(__name__)
 
diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py
index cc286bfcd..adaf524e2 100644
--- a/monkey/infection_monkey/exploit/wmiexec.py
+++ b/monkey/infection_monkey/exploit/wmiexec.py
@@ -5,7 +5,7 @@ import traceback
 
 from impacket.dcerpc.v5.rpcrt import DCERPCException
 
-from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.HostExploiter import HostExploiter
 from infection_monkey.exploit.tools.helpers import get_target_monkey, \
     get_monkey_depth, build_monkey_commandline
 from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 3d0b9d3d7..d9af43cae 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -30,6 +30,7 @@ from infection_monkey.network.tools import get_interface_to_target
 from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
 from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
 from common.utils.attack_utils import ScanStatus, UsageEnum
+from infection_monkey.exploit.HostExploiter import HostExploiter
 
 __author__ = 'itamar'
 
@@ -144,10 +145,10 @@ class InfectionMonkey(object):
 
             self._network.initialize()
 
-            self._exploiters = WormConfiguration.exploiter_classes
-
             self._fingerprint = HostFinger.get_instances()
 
+            self._exploiters = HostExploiter.get_classes()
+
             if not self._keep_running or not WormConfiguration.alive:
                 break
 
@@ -183,7 +184,8 @@ class InfectionMonkey(object):
                 if self._default_server:
                     if self._network.on_island(self._default_server):
                         machine.set_default_server(get_interface_to_target(machine.ip_addr) +
-                                                   (':' + self._default_server_port if self._default_server_port else ''))
+                                                   (
+                                                       ':' + self._default_server_port if self._default_server_port else ''))
                     else:
                         machine.set_default_server(self._default_server)
                     LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))

From af275020cd89c9e50722af3304100afe43fa4fb7 Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Thu, 21 Nov 2019 19:07:29 +0200
Subject: [PATCH 4/8] Add PyInstaller Hook

---
 .../pyinstaller_hooks/hook-infection_monkey.exploit.py        | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py

diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py
new file mode 100644
index 000000000..e759a4116
--- /dev/null
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py
@@ -0,0 +1,4 @@
+from PyInstaller.utils.hooks import collect_submodules, collect_data_files
+
+hiddenimports = collect_submodules('infection_monkey.exploit')
+datas = (collect_data_files('infection_monkey.exploit', include_py_files=True))

From dae5c789b5544f25fbe6363bc962286dd6469f3f Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Sun, 24 Nov 2019 10:40:33 +0200
Subject: [PATCH 5/8] Revert bad PEP fix

---
 monkey/infection_monkey/monkey.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index d9af43cae..80d2d8642 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -184,8 +184,7 @@ class InfectionMonkey(object):
                 if self._default_server:
                     if self._network.on_island(self._default_server):
                         machine.set_default_server(get_interface_to_target(machine.ip_addr) +
-                                                   (
-                                                       ':' + self._default_server_port if self._default_server_port else ''))
+                                                   (':' + self._default_server_port if self._default_server_port else ''))
                     else:
                         machine.set_default_server(self._default_server)
                     LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))

From f118de3d211e665c2337730a65af82327b341ed5 Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Sun, 24 Nov 2019 10:42:56 +0200
Subject: [PATCH 6/8] Rename test classes to match PEP. Add test case with
 multiple classes in file. Remove setup in test runner.

---
 .../utils/plugins/pluginTests/BadInit.py        |  2 +-
 .../utils/plugins/pluginTests/ComboFile.py      | 15 +++++++++++++++
 .../utils/plugins/pluginTests/PluginWorking.py  |  2 +-
 .../utils/plugins/plugin_test.py                | 17 +++++++++++------
 4 files changed, 28 insertions(+), 8 deletions(-)
 create mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py

diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
index b7909b33a..18e83c052 100644
--- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py
@@ -1,7 +1,7 @@
 from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
 
 
-class badPluginInit(TestPlugin):
+class BadPluginInit(TestPlugin):
 
     def __init__(self):
         raise Exception("TestException")
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py
new file mode 100644
index 000000000..2d73cd65b
--- /dev/null
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py
@@ -0,0 +1,15 @@
+from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
+
+
+class NoInheritance:
+    pass
+
+
+class BadInit(TestPlugin):
+
+    def __init__(self):
+        raise Exception("TestException")
+
+
+class ProperClass(TestPlugin):
+    pass
diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
index d762a1f82..a3fe237b6 100644
--- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
+++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py
@@ -1,5 +1,5 @@
 from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
 
 
-class pluginWorking(TestPlugin):
+class PluginWorking(TestPlugin):
     pass
diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py
index 67077a8ad..7841c6c6a 100644
--- a/monkey/infection_monkey/utils/plugins/plugin_test.py
+++ b/monkey/infection_monkey/utils/plugins/plugin_test.py
@@ -1,18 +1,23 @@
 from unittest import TestCase
 
-from infection_monkey.utils.plugins.pluginTests.PluginWorking import pluginWorking
+from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass
+from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking
 from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin
-from infection_monkey.utils.plugins.pluginTests.BadInit import badPluginInit
+from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit
 from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin
 
 
 class PluginTester(TestCase):
 
-    def setUp(self):
-        pass
+    def test_combo_file(self):
+        TestPlugin.classes_to_load = [BadInit.__name__, ProperClass.__name__]
+        to_init = TestPlugin.get_classes()
+        self.assertEqual(len(to_init), 2)
+        objects = TestPlugin.get_instances()
+        self.assertEqual(len(objects), 1)
 
     def test_bad_init(self):
-        TestPlugin.classes_to_load = [badPluginInit.__name__]
+        TestPlugin.classes_to_load = [BadPluginInit.__name__]
         to_init = TestPlugin.get_classes()
         self.assertEqual(len(to_init), 1)
         objects = TestPlugin.get_instances()
@@ -24,7 +29,7 @@ class PluginTester(TestCase):
         self.assertEqual(len(to_init), 0)
 
     def test_flow(self):
-        TestPlugin.classes_to_load = [pluginWorking.__name__]
+        TestPlugin.classes_to_load = [PluginWorking.__name__]
         to_init = TestPlugin.get_classes()
         self.assertEqual(len(to_init), 1)
         objects = TestPlugin.get_instances()

From 6dca7c7764e721f5c9f1dbc8ad212e92193bb82b Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Sun, 24 Nov 2019 10:58:46 +0200
Subject: [PATCH 7/8] Remove unused imports

---
 monkey/infection_monkey/config.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 19682f2b4..2d2a93939 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -2,11 +2,9 @@ import hashlib
 import os
 import json
 import sys
-import types
 import uuid
 from abc import ABCMeta
 from itertools import product
-import importlib
 
 __author__ = 'itamar'
 

From 731f568de5c76357a0d2dd335e661ad71bf109a1 Mon Sep 17 00:00:00 2001
From: Daniel Goldberg <danielg@guardicore.com>
Date: Tue, 26 Nov 2019 16:47:43 +0200
Subject: [PATCH 8/8] Rename PluginType

---
 monkey/infection_monkey/utils/plugins/plugin.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py
index 872ac33db..d80ebccc1 100644
--- a/monkey/infection_monkey/utils/plugins/plugin.py
+++ b/monkey/infection_monkey/utils/plugins/plugin.py
@@ -14,7 +14,7 @@ def _get_candidate_files(base_package_file):
     return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
 
 
-Plugin_type = TypeVar('Plugin_type', bound='Plugin')
+PluginType = TypeVar('PluginType', bound='Plugin')
 
 
 class Plugin(metaclass=ABCMeta):
@@ -54,7 +54,7 @@ class Plugin(metaclass=ABCMeta):
         return objects
 
     @classmethod
-    def get_instances(cls) -> Sequence[Type[Plugin_type]]:
+    def get_instances(cls) -> Sequence[Type[PluginType]]:
         """
         Returns the type objects from base_package_spec.
         base_package name and file must refer to the same package otherwise bad results