diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py index 59b40a089..605799ce3 100644 --- a/chaos_monkey/network/info.py +++ b/chaos_monkey/network/info.py @@ -1,81 +1,56 @@ import os import sys -import array import socket import struct import psutil import ipaddress +import itertools +import netifaces from subprocess import check_output from random import randint -if sys.platform == "win32": - import netifaces +def get_host_subnets(): + """ + Returns a list of subnets visible to host (omitting loopback and auto conf networks) + Each subnet item contains the host IP in that network + the subnet. + :return: List of dict, keys are "addr" and "subnet" + """ + ipv4_nets = [netifaces.ifaddresses(interface)[netifaces.AF_INET] + for interface in netifaces.interfaces() + if netifaces.AF_INET in netifaces.ifaddresses(interface) + ] + # flatten + ipv4_nets = itertools.chain.from_iterable(ipv4_nets) + # remove loopback + ipv4_nets = [network for network in ipv4_nets if network['addr'] != '127.0.0.1'] + # remove auto conf + ipv4_nets = [network for network in ipv4_nets if not network['addr'].startswith('169.254')] + for network in ipv4_nets: + if 'broadcast' in network: + network.pop('broadcast') + return ipv4_nets + + +if sys.platform == "win32": def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] - def get_host_subnets(only_ips=False): - network_adapters = [] - valid_ips = local_ips() - if only_ips: - return valid_ips - interfaces = [netifaces.ifaddresses(x) for x in netifaces.interfaces()] - for inte in interfaces: - if netifaces.AF_INET in inte: - for add in inte[netifaces.AF_INET]: - if "netmask" in add and add["addr"] in valid_ips: - network_adapters.append((add["addr"], add["netmask"])) - return network_adapters - def get_routes(): raise NotImplementedError() else: from fcntl import ioctl - def get_host_subnets(only_ips=False): - """Get the list of Linux network adapters.""" - max_bytes = 8096 - is_64bits = sys.maxsize > 2 ** 32 - if is_64bits: - offset1 = 16 - offset2 = 40 - else: - offset1 = 32 - offset2 = 32 - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - names = array.array('B', '\0' * max_bytes) - outbytes = struct.unpack('iL', ioctl( - sock.fileno(), - 0x8912, - struct.pack('iL', max_bytes, names.buffer_info()[0])))[0] - adapter_names = [names.tostring()[n_cnt:n_cnt + offset1].split('\0', 1)[0] - for n_cnt in xrange(0, outbytes, offset2)] - network_adapters = [] - for adapter_name in adapter_names: - ip_address = socket.inet_ntoa(ioctl( - sock.fileno(), - 0x8915, - struct.pack('256s', adapter_name))[20:24]) - if ip_address.startswith('127'): - continue - subnet_mask = socket.inet_ntoa(ioctl( - sock.fileno(), - 0x891b, - struct.pack('256s', adapter_name))[20:24]) - - if only_ips: - network_adapters.append(ip_address) - else: - network_adapters.append((ip_address, subnet_mask)) - - return network_adapters def local_ips(): - return get_host_subnets(only_ips=True) + ipv4_nets = get_host_subnets() + valid_ips = [network['addr'] for network in ipv4_nets] + return valid_ips + def get_routes(): # based on scapy implementation for route parsing LOOPBACK_NAME = "lo" @@ -141,6 +116,11 @@ def get_free_tcp_port(min_range=1000, max_range=65535): def check_internet_access(services): + """ + Checks if any of the services are accessible, over ICMP + :param services: List of IPs/hostnames + :return: boolean depending on internet access + """ ping_str = "-n 1" if sys.platform.startswith("win") else "-c 1" for host in services: if os.system("ping " + ping_str + " " + host) == 0: @@ -149,19 +129,24 @@ def check_internet_access(services): def get_ips_from_interfaces(): + """ + Returns a list of IPs accessible in the host in each network interface, in the subnet. + Limits to a single class C if the network is larger + :return: List of IPs, marked as strings. + """ res = [] ifs = get_host_subnets() - for interface in ifs: - ipint = ipaddress.ip_interface(u"%s/%s" % interface) + for net_interface in ifs: + host_addr = ipaddress.ip_address(net_interface['addr']) + ip_interface = ipaddress.ip_interface(u"%s/%s" % (net_interface['addr'], net_interface['netmask'])) # limit subnet scans to class C only - if ipint.network.num_addresses > 255: - ipint = ipaddress.ip_interface(u"%s/24" % interface[0]) - for addr in ipint.network.hosts(): - if str(addr) == interface[0]: - continue - res.append(str(addr)) + if ip_interface.network.num_addresses > 255: + ip_interface = ipaddress.ip_interface(u"%s/24" % net_interface['addr']) + addrs = [str(addr) for addr in ip_interface.network.hosts() if addr != host_addr] + res.extend(addrs) return res + if sys.platform == "win32": def get_ip_for_connection(target_ip): return None @@ -171,7 +156,7 @@ else: query_str = 'ip route get %s' % target_ip resp = check_output(query_str.split()) substr = resp.split() - src = substr[substr.index('src')+1] + src = substr[substr.index('src') + 1] return src except Exception: - return None \ No newline at end of file + return None diff --git a/chaos_monkey/system_info/__init__.py b/chaos_monkey/system_info/__init__.py index 2238fb542..b9a16d459 100644 --- a/chaos_monkey/system_info/__init__.py +++ b/chaos_monkey/system_info/__init__.py @@ -2,6 +2,7 @@ import sys import socket import psutil from enum import IntEnum +from network.info import get_host_subnets, local_ips __author__ = 'uri' @@ -45,9 +46,19 @@ class InfoCollector(object): self.info = {} def get_hostname(self): - self.info['hostname'] = socket.gethostname() + """ + Adds the fully qualified computer hostname to the system information. + :return: Nothing + """ + self.info['hostname'] = socket.getfqdn() def get_process_list(self): + """ + Adds process information from the host to the system information. + Currently lists process name, ID, parent ID, command line + and the full image path of each process. + :return: Nothing + """ processes = {} for process in psutil.process_iter(): try: @@ -69,3 +80,12 @@ class InfoCollector(object): } pass self.info['process_list'] = processes + + def get_network_info(self): + """ + Adds network information from the host to the system information. + Currently updates with a list of networks accessible from host, + containing host ip and the subnet range. + :return: None + """ + self.info['network_info'] = {'networks': get_host_subnets()} diff --git a/chaos_monkey/system_info/linux_info_collector.py b/chaos_monkey/system_info/linux_info_collector.py index 64a31c865..6c7570fc0 100644 --- a/chaos_monkey/system_info/linux_info_collector.py +++ b/chaos_monkey/system_info/linux_info_collector.py @@ -14,4 +14,5 @@ class LinuxInfoCollector(InfoCollector): def get_info(self): self.get_hostname() self.get_process_list() + self.get_network_info() return self.info diff --git a/chaos_monkey/system_info/windows_info_collector.py b/chaos_monkey/system_info/windows_info_collector.py index 5cb1253ab..2ba26fd34 100644 --- a/chaos_monkey/system_info/windows_info_collector.py +++ b/chaos_monkey/system_info/windows_info_collector.py @@ -14,6 +14,7 @@ class WindowsInfoCollector(InfoCollector): def get_info(self): self.get_hostname() self.get_process_list() + self.get_network_info() mimikatz_collector = MimikatzCollector() self.info["credentials"] = mimikatz_collector.get_logon_info() return self.info diff --git a/monkey_island/readme.txt b/monkey_island/readme.txt index 0f7ed34ed..b85fe036b 100644 --- a/monkey_island/readme.txt +++ b/monkey_island/readme.txt @@ -1,35 +1,34 @@ How to set C&C server: ---------------- On Windows ----------------: -1. Install python 2.7 - https://www.python.org/download/releases/2.7 -2. Download & Run get-pip.py - https://bootstrap.pypa.io/get-pip.py -3. Run: - setx path "%path%;C:\Python27\;C:\Python27\Scripts" - python -m pip install flask - python -m pip install Flask-Pymongo - python -m pip install Flask-Restful - python -m pip install python-dateutil - mkdir MonkeyIsland\bin - mkdir MonkeyIsland\db - mkdir MonkeyIsland\cc\binaries -4. Put monkey binaries in MonkeyIsland\cc\binaries: +1. Create bin folder + 1.1. create folder "bin" under monkey_island +2. Place portable version of Python 2.7 + 2.1. Download and install from: https://www.python.org/download/releases/2.7/ + 2.2. Download & Run get-pip.py from: https://bootstrap.pypa.io/get-pip.py + 2.3. Install required python libraries using "python -m pip install -r monkey_island\requirements.txt" + 2.4. Copy Contents from Installation path (Usually C:\Python27) to monkey_island\bin\Python27 + 2.5. Copy Python27.dll from System32 folder (Usually C:\Windows\System32) to monkey_island\bin\Python27 + 2.6. (Optional) You may uninstall Python27 if you like. +3. Place portable version of mongodb + 3.1. Download from: http://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip + 3.2. Extract contents from bin folder to monkey_island\bin\mongodb. +4. Place portable version of OpenSSL + 4.1. Download from: http://downloads.sourceforge.net/gnuwin32/openssl-0.9.8h-1-bin.zip + 4.2. Extract content from bin folder to monkey_island\bin\openssl +5. Download and install Microsoft Visual C++ Redisutable for Visual Studio 2017 + 5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572 +6. Generate SSL Certificate + 6.1. run create_certificate.bat when your current working directory is monkey_island +7. Put chaos monkey binaries in monkey_island\cc\binaries (create folder if it doesn't exist): monkey-linux-64 - monkey binary for linux 64bit monkey-linux-32 - monkey binary for linux 32bit monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-64.exe - monkey binary for windows 64bit -4. Download MongoDB & Extract to MonkeyIsland\bin\mongodb - http://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip -5. Install OpenSSL - https://slproweb.com/download/Win64OpenSSL_Light-1_0_2d.exe -6. Generate SSL Certificate, run create_certificate.bat when your current working directory is MonkeyIsland -7. Copy monkey island server to MonkeyIsland\cc How to run: -1. start run_mongodb.bat -2. start run_cc.bat -3. to clear db, run clear_db.bat +1. start monkey_island\windows\run_server.bat (when your current working directory is monkey_island) +2. to clear db, run clear_db.bat ---------------- On Linux ----------------: 1. Create the following directories: diff --git a/monkey_island/windows/clear_db.bat b/monkey_island/windows/clear_db.bat index af6d4ec73..8597f3d32 100644 --- a/monkey_island/windows/clear_db.bat +++ b/monkey_island/windows/clear_db.bat @@ -1,5 +1,4 @@ @echo Are you sure? (Press Any Key) @pause @rmdir /s /q db -@mkdir db -@pause \ No newline at end of file +@mkdir db \ No newline at end of file diff --git a/monkey_island/windows/create_certificate.bat b/monkey_island/windows/create_certificate.bat index 87071197d..ac6555f0b 100644 --- a/monkey_island/windows/create_certificate.bat +++ b/monkey_island/windows/create_certificate.bat @@ -1,4 +1,3 @@ -C:\OpenSSL-Win64\bin\openssl.exe genrsa -out cc\server.key 1024 -C:\OpenSSL-Win64\bin\openssl.exe req -new -config C:\OpenSSL-Win64\bin\openssl.cfg -key cc\server.key -out cc\server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" -C:\OpenSSL-Win64\bin\openssl.exe x509 -req -days 366 -in cc\server.csr -signkey cc\server.key -out cc\server.crt -pause \ No newline at end of file +bin\openssl\openssl.exe genrsa -out cc\server.key 1024 +bin\openssl\openssl.exe req -new -config bin\openssl\openssl.cfg -key cc\server.key -out cc\server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" +bin\openssl\openssl.exe x509 -req -days 366 -in cc\server.csr -signkey cc\server.key -out cc\server.crt \ No newline at end of file diff --git a/monkey_island/windows/run_cc.bat b/monkey_island/windows/run_cc.bat index 597ca9ca7..c16c9fc6b 100644 --- a/monkey_island/windows/run_cc.bat +++ b/monkey_island/windows/run_cc.bat @@ -1,4 +1,4 @@ @title C^&C Server -@cd cc -@main.py -@pause \ No newline at end of file +@pushd cc +@..\bin\Python27\python main.py +@popd \ No newline at end of file diff --git a/monkey_island/windows/run_mongodb.bat b/monkey_island/windows/run_mongodb.bat index ef8876980..ca33c22d7 100644 --- a/monkey_island/windows/run_mongodb.bat +++ b/monkey_island/windows/run_mongodb.bat @@ -1,3 +1,2 @@ @title MongoDB -@bin\mongodb\mongod.exe --dbpath db -@pause \ No newline at end of file +@bin\mongodb\mongod.exe --dbpath db \ No newline at end of file diff --git a/monkey_island/windows/run_server.bat b/monkey_island/windows/run_server.bat new file mode 100644 index 000000000..e2d7b70c1 --- /dev/null +++ b/monkey_island/windows/run_server.bat @@ -0,0 +1,3 @@ +if not exist db mkdir db +start windows\run_mongodb.bat +start windows\run_cc.bat \ No newline at end of file