Merge pull request #478 from guardicore/feature/refactor_fingerprinting

Feature/refactor fingerprinting
This commit is contained in:
Daniel Goldberg 2019-11-14 11:11:57 +02:00 committed by GitHub
commit e1b31e00a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 144 additions and 103 deletions

View File

@ -33,9 +33,6 @@ class Configuration(object):
if self._depth_from_commandline and key == "depth":
continue
# handle in cases
if key == 'finger_classes':
class_objects = [getattr(network_import, val) for val in value]
setattr(self, key, class_objects)
elif key == 'exploiter_classes':
class_objects = [getattr(exploit_import, val) for val in value]
setattr(self, key, class_objects)

View File

@ -7,7 +7,7 @@ from infection_monkey.exploit 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
from infection_monkey.network import SMBFinger
from infection_monkey.network.smbfinger import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from common.utils.exploit_enum import ExploitType
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem

View File

@ -17,7 +17,7 @@ from impacket.dcerpc.v5 import transport
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 DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from infection_monkey.network import SMBFinger
from infection_monkey.network.smbfinger import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from . import HostExploiter
@ -234,7 +234,8 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
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.dropper_target_path_win_32)
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)

View File

@ -6,6 +6,7 @@ import sys
import time
import infection_monkey.tunnel as tunnel
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
from infection_monkey.utils.environment import is_windows_os
@ -145,7 +146,7 @@ class InfectionMonkey(object):
self._exploiters = WormConfiguration.exploiter_classes
self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes]
self._fingerprint = HostFinger.get_instances()
if not self._keep_running or not WormConfiguration.alive:
break
@ -192,9 +193,7 @@ class InfectionMonkey(object):
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if self.try_exploiting(machine, exploiter):
host_exploited = True
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
break

View File

@ -0,0 +1,33 @@
from abc import abstractmethod
from infection_monkey.config import WormConfiguration
from infection_monkey.utils.plugins.plugin import Plugin
import infection_monkey.network
class HostFinger(Plugin):
@staticmethod
def base_package_file():
return infection_monkey.network.__file__
@staticmethod
def base_package_name():
return infection_monkey.network.__package__
@property
@abstractmethod
def _SCANNED_SERVICE(self):
pass
def init_service(self, services, service_key, port):
services[service_key] = {}
services[service_key]['display_name'] = self._SCANNED_SERVICE
services[service_key]['port'] = port
@abstractmethod
def get_host_fingerprint(self, host):
raise NotImplementedError()
@staticmethod
def should_run(class_name: str) -> bool:
return class_name in WormConfiguration.finger_classes

View File

@ -0,0 +1,8 @@
from abc import ABCMeta, abstractmethod
class HostScanner(metaclass=ABCMeta):
@property
@abstractmethod
def is_host_alive(self, host):
raise NotImplementedError()

View File

@ -1,36 +1 @@
from abc import ABCMeta, abstractmethod
__author__ = 'itamar'
class HostScanner(object, metaclass=ABCMeta):
@abstractmethod
def is_host_alive(self, host):
raise NotImplementedError()
class HostFinger(object, metaclass=ABCMeta):
@property
@abstractmethod
def _SCANNED_SERVICE(self):
pass
def init_service(self, services, service_key, port):
services[service_key] = {}
services[service_key]['display_name'] = self._SCANNED_SERVICE
services[service_key]['port'] = port
@abstractmethod
def get_host_fingerprint(self, host):
raise NotImplementedError()
from infection_monkey.network.ping_scanner import PingScanner
from infection_monkey.network.tcp_scanner import TcpScanner
from infection_monkey.network.smbfinger import SMBFinger
from infection_monkey.network.sshfinger import SSHFinger
from infection_monkey.network.httpfinger import HTTPFinger
from infection_monkey.network.elasticfinger import ElasticFinger
from infection_monkey.network.mysqlfinger import MySQLFinger
from infection_monkey.network.info import local_ips, get_free_tcp_port
from infection_monkey.network.mssql_fingerprint import MSSQLFinger

View File

@ -6,9 +6,8 @@ import requests
from requests.exceptions import Timeout, ConnectionError
import infection_monkey.config
from infection_monkey.network.HostFinger import HostFinger
from common.data.network_consts import ES_SERVICE
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostFinger
ES_PORT = 9200
ES_HTTP_TIMEOUT = 5
@ -31,7 +30,6 @@ class ElasticFinger(HostFinger):
:param host:
:return: Success/failure, data is saved in the host struct
"""
assert isinstance(host, VictimHost)
try:
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:

View File

@ -1,6 +1,5 @@
import infection_monkey.config
from infection_monkey.network import HostFinger
from infection_monkey.model.host import VictimHost
from infection_monkey.network.HostFinger import HostFinger
import logging
LOG = logging.getLogger(__name__)
@ -21,7 +20,6 @@ class HTTPFinger(HostFinger):
pass
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
from requests import head
from requests.exceptions import Timeout, ConnectionError
from contextlib import closing

View File

@ -2,8 +2,7 @@ import errno
import logging
import socket
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostFinger
from infection_monkey.network.HostFinger import HostFinger
import infection_monkey.config
__author__ = 'Maor Rayzin'
@ -30,7 +29,6 @@ class MSSQLFinger(HostFinger):
Discovered server information written to the Host info struct.
True if success, False otherwise.
"""
assert isinstance(host, VictimHost)
# Create a UDP socket and sets a timeout
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

View File

@ -2,8 +2,7 @@ import logging
import socket
import infection_monkey.config
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostFinger
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string
MYSQL_PORT = 3306
@ -28,7 +27,6 @@ class MySQLFinger(HostFinger):
:param host:
:return: Success/failure, data is saved in the host struct
"""
assert isinstance(host, VictimHost)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.SOCKET_TIMEOUT)

View File

@ -6,7 +6,8 @@ from common.network.network_range import NetworkRange
from infection_monkey.config import WormConfiguration
from infection_monkey.model.victim_host_generator import VictimHostGenerator
from infection_monkey.network.info import local_ips, get_interfaces_ranges
from infection_monkey.network import TcpScanner, PingScanner
from infection_monkey.network.tcp_scanner import TcpScanner
from infection_monkey.network.ping_scanner import PingScanner
LOG = logging.getLogger(__name__)

View File

@ -5,8 +5,9 @@ import subprocess
import sys
import infection_monkey.config
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.HostScanner import HostScanner
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostScanner, HostFinger
__author__ = 'itamar'
@ -28,7 +29,6 @@ class PingScanner(HostScanner, HostFinger):
self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE)
def is_host_alive(self, host):
assert isinstance(host, VictimHost)
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:
@ -42,7 +42,6 @@ class PingScanner(HostScanner, HostFinger):
stderr=self._devnull)
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:

View File

@ -3,8 +3,7 @@ import struct
import logging
from odict import odict
from infection_monkey.network import HostFinger
from infection_monkey.model.host import VictimHost
from infection_monkey.network.HostFinger import HostFinger
SMB_PORT = 445
SMB_SERVICE = 'tcp-445'
@ -114,7 +113,6 @@ class SMBFinger(HostFinger):
self._config = WormConfiguration
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

View File

@ -1,8 +1,7 @@
import re
import infection_monkey.config
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostFinger
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.tools import check_tcp_port
SSH_PORT = 22
@ -34,7 +33,6 @@ class SSHFinger(HostFinger):
break
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
for name, data in list(host.services.items()):
banner = data.get('banner', '')

View File

@ -2,7 +2,8 @@ from itertools import zip_longest
from random import shuffle
import infection_monkey.config
from infection_monkey.network import HostScanner, HostFinger
from infection_monkey.network.HostFinger import HostFinger
from infection_monkey.network.HostScanner import HostScanner
from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service
__author__ = 'itamar'

View File

@ -1,11 +0,0 @@
from os.path import dirname, basename, isfile, join
import glob
def get_pba_files():
"""
Gets all files under current directory(/actions)
:return: list of all files without .py ending
"""
files = glob.glob(join(dirname(__file__), "*.py"))
return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]

View File

@ -6,7 +6,8 @@ from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
from infection_monkey.utils.plugins.plugin import Plugin
import infection_monkey.post_breach.actions
LOG = logging.getLogger(__name__)
__author__ = 'VakarisZ'
@ -14,11 +15,19 @@ __author__ = 'VakarisZ'
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
class PBA(object):
class PBA(Plugin):
"""
Post breach action object. Can be extended to support more than command execution on target machine.
"""
@staticmethod
def base_package_name():
return infection_monkey.post_breach.actions.__package__
@staticmethod
def base_package_file():
return infection_monkey.post_breach.actions.__file__
def __init__(self, name="unknown", linux_cmd="", windows_cmd=""):
"""
:param name: Name of post breach action.

View File

@ -1,9 +1,8 @@
import logging
import inspect
import importlib
from infection_monkey.post_breach.pba import PBA
from infection_monkey.post_breach.actions import get_pba_files
from typing import Sequence
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.post_breach.pba import PBA
LOG = logging.getLogger(__name__)
@ -34,25 +33,8 @@ class PostBreach(object):
LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list)))
@staticmethod
def config_to_pba_list():
def config_to_pba_list() -> Sequence[PBA]:
"""
Passes config to each post breach action class and aggregates results into a list.
:return: A list of PBA objects.
"""
pba_list = []
pba_files = get_pba_files()
# Go through all of files in ./actions
for pba_file in pba_files:
# Import module from that file
module = importlib.import_module(PATH_TO_ACTIONS + pba_file)
# Get all classes in a module
pba_classes = [m[1] for m in inspect.getmembers(module, inspect.isclass)
if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))]
# Get post breach action object from class
for pba_class in pba_classes:
LOG.debug("Checking if should run PBA {}".format(pba_class.__name__))
if pba_class.should_run(pba_class.__name__):
pba = pba_class()
pba_list.append(pba)
LOG.debug("Added PBA {} to PBA list".format(pba_class.__name__))
return pba_list
return PBA.get_instances()

View File

@ -0,0 +1,4 @@
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
hiddenimports = collect_submodules('infection_monkey.network')
datas = (collect_data_files('infection_monkey.network', include_py_files=True))

View File

@ -0,0 +1,65 @@
import importlib
import inspect
import logging
from abc import ABCMeta, abstractmethod
from os.path import dirname, basename, isfile, join
import glob
from typing import Sequence, TypeVar, Type
LOG = logging.getLogger(__name__)
def _get_candidate_files(base_package_file):
files = glob.glob(join(dirname(base_package_file), "*.py"))
return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
Plugin_type = TypeVar('Plugin_type', bound='Plugin')
class Plugin(metaclass=ABCMeta):
@staticmethod
@abstractmethod
def should_run(class_name: str) -> bool:
raise NotImplementedError()
@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.
"""
objects = []
candidate_files = _get_candidate_files(cls.base_package_file())
LOG.info("looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()))
# Go through all of files
for file in candidate_files:
# Import module from that file
module = importlib.import_module('.' + file, cls.base_package_name())
# Get all classes in a module
# m[1] because return object is (name,class)
classes = [m[1] for m in inspect.getmembers(module, inspect.isclass)
if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls))]
# Get object from class
for class_object in classes:
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)
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
@staticmethod
@abstractmethod
def base_package_file():
pass
@staticmethod
@abstractmethod
def base_package_name():
pass